@zhhoceanfly/protoview 0.0.0-stage → 0.1.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +51 -3
- package/bin/protoview.js +3 -0
- package/dist/cli.js +115 -0
- package/dist/core/catalog.js +493 -0
- package/dist/core/catalog.js.map +1 -0
- package/dist/core/clientModel.js +111 -0
- package/dist/core/clientModel.js.map +1 -0
- package/dist/core/grpcInvoke.js +178 -0
- package/dist/core/grpcInvoke.js.map +1 -0
- package/dist/core/grpcStress.js +83 -0
- package/dist/core/grpcStress.js.map +1 -0
- package/dist/core/hostCore.js +292 -0
- package/dist/core/hostCore.js.map +1 -0
- package/dist/core/httpInvoke.js +152 -0
- package/dist/core/httpInvoke.js.map +1 -0
- package/dist/core/invokeDispatch.js +26 -0
- package/dist/core/invokeDispatch.js.map +1 -0
- package/dist/core/meta.js +158 -0
- package/dist/core/meta.js.map +1 -0
- package/dist/core/parser.js +638 -0
- package/dist/core/parser.js.map +1 -0
- package/dist/core/sourceCore.js +235 -0
- package/dist/core/sourceCore.js.map +1 -0
- package/dist/core/sseInvoke.js +244 -0
- package/dist/core/sseInvoke.js.map +1 -0
- package/dist/core/wsInvoke.js +326 -0
- package/dist/core/wsInvoke.js.map +1 -0
- package/dist/server.js +159 -0
- package/dist/uploadProtoDir.js +78 -0
- package/package.json +55 -6
- package/public/app.js +2025 -0
- package/public/donate-wechat.png +0 -0
- package/public/flow.js +1481 -0
- package/public/index.html +424 -0
- package/public/styles.css +1481 -0
package/README.md
CHANGED
|
@@ -1,3 +1,51 @@
|
|
|
1
|
-
#
|
|
2
|
-
|
|
3
|
-
|
|
1
|
+
# @zhhoceanfly/protoview
|
|
2
|
+
|
|
3
|
+
ProtoView Client **CLI**:在浏览器里调试 gRPC / HTTP / SSE / WebSocket。适合 Rider 等无法安装 VS Code 扩展的 IDE。
|
|
4
|
+
|
|
5
|
+
与 [Open VSX 扩展](https://open-vsx.org/extension/zoceanfly/protoview) 共用同一套 Client 页面与调用逻辑。
|
|
6
|
+
|
|
7
|
+
## 安装
|
|
8
|
+
|
|
9
|
+
```bash
|
|
10
|
+
npm i -g @zhhoceanfly/protoview
|
|
11
|
+
# 或
|
|
12
|
+
npx @zhhoceanfly/protoview
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
需要 **Node.js 18+**。Git 来源需本机已安装 `git`。
|
|
16
|
+
|
|
17
|
+
## 用法
|
|
18
|
+
|
|
19
|
+
```bash
|
|
20
|
+
protoview # 扫描当前目录 .proto,打开浏览器
|
|
21
|
+
protoview --proto ./api # 指定目录
|
|
22
|
+
protoview --git https://github.com/org/repo.git --branch main
|
|
23
|
+
protoview --port 3920 --no-open
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
默认地址:`http://127.0.0.1:3920/`
|
|
27
|
+
|
|
28
|
+
环境配置保存在工作区目录下的 `.protoview/environments.json`。
|
|
29
|
+
|
|
30
|
+
## 仓库内开发
|
|
31
|
+
|
|
32
|
+
在 ProtoView 仓库根目录:
|
|
33
|
+
|
|
34
|
+
```bash
|
|
35
|
+
npm run dev:cli
|
|
36
|
+
npm run dev:cli -- --proto ./api --port 3920
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
改 `client/` 后刷新浏览器;改 TypeScript 会自动编译并重启服务。正式发布仍用 `npm run build:cli`。
|
|
40
|
+
|
|
41
|
+
## 与扩展的差异
|
|
42
|
+
|
|
43
|
+
| | VS Code / Cursor 扩展 | 本 CLI |
|
|
44
|
+
| --- | --- | --- |
|
|
45
|
+
| Client 调试 | 有 | 有 |
|
|
46
|
+
| 侧边栏 / 语言服务 | 有 | 无 |
|
|
47
|
+
| 链路导入导出 | 系统对话框 | 浏览器下载 / 选文件 |
|
|
48
|
+
|
|
49
|
+
## License
|
|
50
|
+
|
|
51
|
+
MIT
|
package/bin/protoview.js
ADDED
package/dist/cli.js
ADDED
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
35
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
+
const path = __importStar(require("path"));
|
|
37
|
+
const server_1 = require("./server");
|
|
38
|
+
function parseArgs(argv) {
|
|
39
|
+
const args = {
|
|
40
|
+
proto: process.cwd(),
|
|
41
|
+
git: "",
|
|
42
|
+
branch: "main",
|
|
43
|
+
port: 3920,
|
|
44
|
+
open: true,
|
|
45
|
+
showGoogle: false,
|
|
46
|
+
help: false,
|
|
47
|
+
};
|
|
48
|
+
for (let i = 0; i < argv.length; i++) {
|
|
49
|
+
const a = argv[i];
|
|
50
|
+
if (a === "-h" || a === "--help")
|
|
51
|
+
args.help = true;
|
|
52
|
+
else if (a === "--proto" || a === "-p")
|
|
53
|
+
args.proto = path.resolve(argv[++i] || ".");
|
|
54
|
+
else if (a === "--git" || a === "-g")
|
|
55
|
+
args.git = argv[++i] || "";
|
|
56
|
+
else if (a === "--branch" || a === "-b")
|
|
57
|
+
args.branch = argv[++i] || "main";
|
|
58
|
+
else if (a === "--port")
|
|
59
|
+
args.port = Math.max(1, Number(argv[++i] || 3920) || 3920);
|
|
60
|
+
else if (a === "--no-open")
|
|
61
|
+
args.open = false;
|
|
62
|
+
else if (a === "--show-google")
|
|
63
|
+
args.showGoogle = true;
|
|
64
|
+
else if (!a.startsWith("-"))
|
|
65
|
+
args.proto = path.resolve(a);
|
|
66
|
+
}
|
|
67
|
+
return args;
|
|
68
|
+
}
|
|
69
|
+
function printHelp() {
|
|
70
|
+
console.log(`ProtoView Client CLI
|
|
71
|
+
|
|
72
|
+
Usage:
|
|
73
|
+
protoview [options] [dir]
|
|
74
|
+
|
|
75
|
+
Options:
|
|
76
|
+
--proto, -p <dir> Scan .proto under directory (default: cwd)
|
|
77
|
+
--git, -g <url> Use Git repo as proto source (clone to temp dir)
|
|
78
|
+
--branch, -b <name> Git branch (default: main)
|
|
79
|
+
--port <n> HTTP port (default: 3920)
|
|
80
|
+
--no-open Do not open browser
|
|
81
|
+
--show-google Include google.* packages in tree
|
|
82
|
+
-h, --help Show help
|
|
83
|
+
|
|
84
|
+
Examples:
|
|
85
|
+
protoview
|
|
86
|
+
protoview --proto ./api
|
|
87
|
+
protoview --git https://github.com/org/repo.git --branch main
|
|
88
|
+
`);
|
|
89
|
+
}
|
|
90
|
+
async function main() {
|
|
91
|
+
const args = parseArgs(process.argv.slice(2));
|
|
92
|
+
if (args.help) {
|
|
93
|
+
printHelp();
|
|
94
|
+
return;
|
|
95
|
+
}
|
|
96
|
+
const publicDir = process.env.PROTOVIEW_PUBLIC_DIR
|
|
97
|
+
? path.resolve(process.env.PROTOVIEW_PUBLIC_DIR)
|
|
98
|
+
: path.join(__dirname, "..", "public");
|
|
99
|
+
if (process.env.PROTOVIEW_DEV === "1") {
|
|
100
|
+
console.log(`[protoview] DEV publicDir=${publicDir}`);
|
|
101
|
+
}
|
|
102
|
+
await (0, server_1.startServer)({
|
|
103
|
+
publicDir,
|
|
104
|
+
workspaceRoot: args.proto,
|
|
105
|
+
port: args.port,
|
|
106
|
+
openBrowser: args.open,
|
|
107
|
+
showGoogle: args.showGoogle,
|
|
108
|
+
gitUrl: args.git,
|
|
109
|
+
gitBranch: args.branch,
|
|
110
|
+
});
|
|
111
|
+
}
|
|
112
|
+
main().catch((error) => {
|
|
113
|
+
console.error(error instanceof Error ? error.message : error);
|
|
114
|
+
process.exit(1);
|
|
115
|
+
});
|
|
@@ -0,0 +1,493 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.isWellKnownPackage = isWellKnownPackage;
|
|
4
|
+
exports.buildCatalog = buildCatalog;
|
|
5
|
+
exports.findSymbolAt = findSymbolAt;
|
|
6
|
+
exports.findDefinitionAt = findDefinitionAt;
|
|
7
|
+
const parser_1 = require("./parser");
|
|
8
|
+
const SERVICE_RANK = {
|
|
9
|
+
ApiService: 0,
|
|
10
|
+
PublicService: 1,
|
|
11
|
+
InternalService: 2,
|
|
12
|
+
};
|
|
13
|
+
function isWellKnownPackage(name) {
|
|
14
|
+
return name === "google" || name.startsWith("google.");
|
|
15
|
+
}
|
|
16
|
+
function buildCatalog(files, errors = []) {
|
|
17
|
+
const byFile = new Map(files.map((file) => [normalize(file.path), file]));
|
|
18
|
+
const messagesByPkg = new Map();
|
|
19
|
+
const enumsByPkg = new Map();
|
|
20
|
+
for (const file of files) {
|
|
21
|
+
for (const message of (0, parser_1.flattenMessages)(file.messages)) {
|
|
22
|
+
push(messagesByPkg, file.package, { message, file });
|
|
23
|
+
}
|
|
24
|
+
for (const item of (0, parser_1.flattenEnums)(file.messages, file.enums)) {
|
|
25
|
+
push(enumsByPkg, file.package, { item, file });
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
const hits = new Map();
|
|
29
|
+
const addHit = (hit) => {
|
|
30
|
+
const key = `${hit.package}|${hit.fullName}`;
|
|
31
|
+
const list = hits.get(key) ?? [];
|
|
32
|
+
list.push(hit);
|
|
33
|
+
hits.set(key, list);
|
|
34
|
+
};
|
|
35
|
+
const draftMessages = [];
|
|
36
|
+
const draftEnums = [];
|
|
37
|
+
for (const [pkg, list] of messagesByPkg) {
|
|
38
|
+
const names = countNames(list.map((item) => item.message.fullName));
|
|
39
|
+
for (const { message, file } of list) {
|
|
40
|
+
const id = symbolId("msg", pkg, message.fullName, file.relPath, names.get(message.fullName) > 1);
|
|
41
|
+
addHit({ kind: "message", id, package: pkg, fullName: message.fullName, file: file.path });
|
|
42
|
+
draftMessages.push({
|
|
43
|
+
id,
|
|
44
|
+
package: pkg,
|
|
45
|
+
name: message.name,
|
|
46
|
+
fullName: message.fullName,
|
|
47
|
+
comment: message.comment,
|
|
48
|
+
file: file.path,
|
|
49
|
+
relPath: file.relPath,
|
|
50
|
+
line: message.loc.line,
|
|
51
|
+
endLine: message.loc.endLine,
|
|
52
|
+
fields: [],
|
|
53
|
+
nested: [
|
|
54
|
+
...message.nestedMessages.map((nested) => ({
|
|
55
|
+
id: "",
|
|
56
|
+
kind: "message",
|
|
57
|
+
name: nested.fullName,
|
|
58
|
+
comment: nested.comment,
|
|
59
|
+
})),
|
|
60
|
+
...message.nestedEnums.map((nested) => ({
|
|
61
|
+
id: "",
|
|
62
|
+
kind: "enum",
|
|
63
|
+
name: nested.fullName,
|
|
64
|
+
comment: nested.comment,
|
|
65
|
+
})),
|
|
66
|
+
],
|
|
67
|
+
usages: [],
|
|
68
|
+
});
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
for (const [pkg, list] of enumsByPkg) {
|
|
72
|
+
const names = countNames(list.map((item) => item.item.fullName));
|
|
73
|
+
for (const { item, file } of list) {
|
|
74
|
+
const id = symbolId("enum", pkg, item.fullName, file.relPath, names.get(item.fullName) > 1);
|
|
75
|
+
addHit({ kind: "enum", id, package: pkg, fullName: item.fullName, file: file.path });
|
|
76
|
+
draftEnums.push({
|
|
77
|
+
id,
|
|
78
|
+
package: pkg,
|
|
79
|
+
name: item.name,
|
|
80
|
+
fullName: item.fullName,
|
|
81
|
+
comment: item.comment,
|
|
82
|
+
file: file.path,
|
|
83
|
+
relPath: file.relPath,
|
|
84
|
+
line: item.loc.line,
|
|
85
|
+
endLine: item.loc.endLine,
|
|
86
|
+
values: item.values.map((value) => ({
|
|
87
|
+
name: value.name,
|
|
88
|
+
number: value.number,
|
|
89
|
+
comment: value.comment,
|
|
90
|
+
line: value.loc.line,
|
|
91
|
+
})),
|
|
92
|
+
usages: [],
|
|
93
|
+
});
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
const messageById = new Map(draftMessages.map((item) => [item.id, item]));
|
|
97
|
+
const enumById = new Map(draftEnums.map((item) => [item.id, item]));
|
|
98
|
+
const resolve = (from, typeName, scope = "") => {
|
|
99
|
+
const raw = typeName.replace(/^\./, "");
|
|
100
|
+
if (!raw || (0, parser_1.isScalar)(raw) || raw.startsWith("map<"))
|
|
101
|
+
return undefined;
|
|
102
|
+
const parts = raw.split(".");
|
|
103
|
+
const pick = (list) => {
|
|
104
|
+
if (!list?.length)
|
|
105
|
+
return undefined;
|
|
106
|
+
return (list.find((hit) => normalize(hit.file) === normalize(from.path)) ??
|
|
107
|
+
list.find((hit) => imported(from, hit.file, files, byFile)) ??
|
|
108
|
+
list[0]);
|
|
109
|
+
};
|
|
110
|
+
if (scope && !typeName.startsWith(".")) {
|
|
111
|
+
const chain = scope.split(".");
|
|
112
|
+
for (let i = chain.length; i >= 1; i--) {
|
|
113
|
+
const nestedName = `${chain.slice(0, i).join(".")}.${raw}`;
|
|
114
|
+
const nested = pick(hits.get(`${from.package}|${nestedName}`));
|
|
115
|
+
if (nested)
|
|
116
|
+
return nested;
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
const direct = pick(hits.get(`${from.package}|${raw}`));
|
|
120
|
+
if (direct)
|
|
121
|
+
return direct;
|
|
122
|
+
if (parts.length >= 2) {
|
|
123
|
+
for (let i = 1; i < parts.length; i++) {
|
|
124
|
+
const pkg = parts.slice(0, i).join(".");
|
|
125
|
+
const rest = parts.slice(i).join(".");
|
|
126
|
+
const found = pick(hits.get(`${pkg}|${rest}`));
|
|
127
|
+
if (found)
|
|
128
|
+
return found;
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
for (const spec of from.imports) {
|
|
132
|
+
const importedFile = resolveImport(from, spec, files, byFile);
|
|
133
|
+
if (!importedFile)
|
|
134
|
+
continue;
|
|
135
|
+
const found = pick(hits.get(`${importedFile.package}|${raw}`))
|
|
136
|
+
?? (parts.length >= 2 ? pick(hits.get(`${importedFile.package}|${parts[parts.length - 1]}`)) : undefined);
|
|
137
|
+
if (found && found.package === importedFile.package)
|
|
138
|
+
return found;
|
|
139
|
+
}
|
|
140
|
+
const short = parts[parts.length - 1];
|
|
141
|
+
const global = [...hits.values()].flat().filter((hit) => hit.fullName === short || hit.fullName.endsWith(`.${short}`));
|
|
142
|
+
const unique = new Map(global.map((hit) => [hit.id, hit]));
|
|
143
|
+
if (unique.size === 1)
|
|
144
|
+
return [...unique.values()][0];
|
|
145
|
+
return undefined;
|
|
146
|
+
};
|
|
147
|
+
const linkType = (from, typeName, scope = "") => {
|
|
148
|
+
const hit = resolve(from, typeName, scope);
|
|
149
|
+
return { text: typeName.replace(/^\./, ""), navId: hit?.id };
|
|
150
|
+
};
|
|
151
|
+
const toField = (from, field, scope, oneof) => ({
|
|
152
|
+
name: field.name,
|
|
153
|
+
number: field.number,
|
|
154
|
+
comment: field.comment,
|
|
155
|
+
line: field.loc.line,
|
|
156
|
+
file: from.path,
|
|
157
|
+
prefix: field.repeated ? "repeated" : field.optional ? "optional" : "",
|
|
158
|
+
key: field.mapKey ? linkType(from, field.mapKey, scope) : undefined,
|
|
159
|
+
value: linkType(from, field.mapValue ?? field.typeName, scope),
|
|
160
|
+
oneof,
|
|
161
|
+
});
|
|
162
|
+
const fileOfMessage = new Map();
|
|
163
|
+
for (const [pkg, list] of messagesByPkg) {
|
|
164
|
+
const names = countNames(list.map((item) => item.message.fullName));
|
|
165
|
+
for (const { message, file } of list) {
|
|
166
|
+
const id = symbolId("msg", pkg, message.fullName, file.relPath, names.get(message.fullName) > 1);
|
|
167
|
+
fileOfMessage.set(id, file);
|
|
168
|
+
const view = messageById.get(id);
|
|
169
|
+
if (!view)
|
|
170
|
+
continue;
|
|
171
|
+
view.fields = message.members.flatMap((member) => (member.kind === "field"
|
|
172
|
+
? [toField(file, member.field, message.fullName)]
|
|
173
|
+
: member.oneof.fields.map((field) => toField(file, field, message.fullName, member.oneof.name))));
|
|
174
|
+
view.nested = view.nested.map((nested) => {
|
|
175
|
+
const hit = resolve(file, nested.name) ?? resolve(file, `${pkg}.${nested.name}`);
|
|
176
|
+
return { ...nested, id: hit?.id ?? "" };
|
|
177
|
+
});
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
const packages = new Map();
|
|
181
|
+
const ensurePkg = (name) => {
|
|
182
|
+
let pkg = packages.get(name);
|
|
183
|
+
if (!pkg) {
|
|
184
|
+
pkg = {
|
|
185
|
+
name,
|
|
186
|
+
id: `pkg:${name}`,
|
|
187
|
+
files: [],
|
|
188
|
+
services: [],
|
|
189
|
+
messages: [],
|
|
190
|
+
enums: [],
|
|
191
|
+
wellKnown: isWellKnownPackage(name),
|
|
192
|
+
};
|
|
193
|
+
packages.set(name, pkg);
|
|
194
|
+
}
|
|
195
|
+
return pkg;
|
|
196
|
+
};
|
|
197
|
+
for (const file of files) {
|
|
198
|
+
const pkg = ensurePkg(file.package);
|
|
199
|
+
pkg.files.push({ relPath: file.relPath, path: file.path });
|
|
200
|
+
}
|
|
201
|
+
for (const message of draftMessages)
|
|
202
|
+
ensurePkg(message.package).messages.push(message);
|
|
203
|
+
for (const item of draftEnums)
|
|
204
|
+
ensurePkg(item.package).enums.push(item);
|
|
205
|
+
const services = [];
|
|
206
|
+
const rpcs = [];
|
|
207
|
+
for (const file of files) {
|
|
208
|
+
const serviceNames = countNames(file.services.map((service) => service.name));
|
|
209
|
+
for (const service of file.services) {
|
|
210
|
+
const serviceId = symbolId("svc", file.package, service.name, file.relPath, (serviceNames.get(service.name) ?? 1) > 1 || sameServiceElsewhere(files, file, service));
|
|
211
|
+
const view = {
|
|
212
|
+
id: serviceId,
|
|
213
|
+
package: file.package,
|
|
214
|
+
name: service.name,
|
|
215
|
+
comment: service.comment,
|
|
216
|
+
file: file.path,
|
|
217
|
+
relPath: file.relPath,
|
|
218
|
+
line: service.loc.line,
|
|
219
|
+
endLine: service.loc.endLine,
|
|
220
|
+
rpcs: [],
|
|
221
|
+
};
|
|
222
|
+
for (const rpc of service.rpcs) {
|
|
223
|
+
const request = linkType(file, rpc.request);
|
|
224
|
+
const response = linkType(file, rpc.response);
|
|
225
|
+
const rpcView = {
|
|
226
|
+
id: `${serviceId}:${rpc.name}:${rpc.loc.line}`,
|
|
227
|
+
package: file.package,
|
|
228
|
+
serviceId,
|
|
229
|
+
service: service.name,
|
|
230
|
+
name: rpc.name,
|
|
231
|
+
comment: rpc.comment,
|
|
232
|
+
file: file.path,
|
|
233
|
+
relPath: file.relPath,
|
|
234
|
+
line: rpc.loc.line,
|
|
235
|
+
endLine: rpc.loc.endLine,
|
|
236
|
+
request,
|
|
237
|
+
response,
|
|
238
|
+
requestStream: rpc.requestStream,
|
|
239
|
+
responseStream: rpc.responseStream,
|
|
240
|
+
httpVerb: rpc.httpVerb,
|
|
241
|
+
httpPath: rpc.httpPath,
|
|
242
|
+
httpBody: rpc.httpBody,
|
|
243
|
+
timeout: rpc.timeout,
|
|
244
|
+
auth: rpc.auth,
|
|
245
|
+
closed: rpc.closed,
|
|
246
|
+
badges: rpc.badges,
|
|
247
|
+
source: rpc.source,
|
|
248
|
+
requestMessageId: request.navId,
|
|
249
|
+
responseMessageId: response.navId,
|
|
250
|
+
};
|
|
251
|
+
view.rpcs.push(rpcView);
|
|
252
|
+
rpcs.push(rpcView);
|
|
253
|
+
addUsage(messageById, request.navId, {
|
|
254
|
+
navId: rpcView.id,
|
|
255
|
+
label: `${service.name}.${rpc.name}`,
|
|
256
|
+
detail: "请求",
|
|
257
|
+
});
|
|
258
|
+
addUsage(messageById, response.navId, {
|
|
259
|
+
navId: rpcView.id,
|
|
260
|
+
label: `${service.name}.${rpc.name}`,
|
|
261
|
+
detail: "响应",
|
|
262
|
+
});
|
|
263
|
+
}
|
|
264
|
+
services.push(view);
|
|
265
|
+
ensurePkg(file.package).services.push(view);
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
for (const message of draftMessages) {
|
|
269
|
+
const file = fileOfMessage.get(message.id);
|
|
270
|
+
if (!file)
|
|
271
|
+
continue;
|
|
272
|
+
for (const field of message.fields) {
|
|
273
|
+
for (const type of [field.key, field.value]) {
|
|
274
|
+
if (!type?.navId)
|
|
275
|
+
continue;
|
|
276
|
+
const usage = {
|
|
277
|
+
navId: message.id,
|
|
278
|
+
label: `${message.fullName}.${field.name}`,
|
|
279
|
+
detail: "字段",
|
|
280
|
+
};
|
|
281
|
+
addUsage(messageById, type.navId, usage);
|
|
282
|
+
addUsage(enumById, type.navId, usage);
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
}
|
|
286
|
+
for (const pkg of packages.values()) {
|
|
287
|
+
pkg.files.sort((a, b) => a.relPath.localeCompare(b.relPath));
|
|
288
|
+
pkg.services.sort(compareService);
|
|
289
|
+
pkg.messages.sort((a, b) => a.fullName.localeCompare(b.fullName));
|
|
290
|
+
pkg.enums.sort((a, b) => a.fullName.localeCompare(b.fullName));
|
|
291
|
+
}
|
|
292
|
+
const byId = new Map();
|
|
293
|
+
const packageList = [...packages.values()].sort(comparePackage);
|
|
294
|
+
for (const pkg of packageList) {
|
|
295
|
+
byId.set(pkg.id, pkg);
|
|
296
|
+
for (const service of pkg.services) {
|
|
297
|
+
byId.set(service.id, service);
|
|
298
|
+
for (const rpc of service.rpcs)
|
|
299
|
+
byId.set(rpc.id, rpc);
|
|
300
|
+
}
|
|
301
|
+
for (const message of pkg.messages)
|
|
302
|
+
byId.set(message.id, message);
|
|
303
|
+
for (const item of pkg.enums)
|
|
304
|
+
byId.set(item.id, item);
|
|
305
|
+
}
|
|
306
|
+
return { packages: packageList, byId, errors };
|
|
307
|
+
}
|
|
308
|
+
function findSymbolAt(catalog, filePath, line) {
|
|
309
|
+
const target = normalize(filePath);
|
|
310
|
+
let best;
|
|
311
|
+
const consider = (id, file, start, end) => {
|
|
312
|
+
if (normalize(file) !== target || line < start || line > end)
|
|
313
|
+
return;
|
|
314
|
+
const span = Math.max(0, end - start);
|
|
315
|
+
if (!best || span < best.span)
|
|
316
|
+
best = { id, span };
|
|
317
|
+
};
|
|
318
|
+
for (const item of catalog.byId.values()) {
|
|
319
|
+
if ("messages" in item && "services" in item)
|
|
320
|
+
continue;
|
|
321
|
+
if ("fields" in item) {
|
|
322
|
+
consider(item.id, item.file, item.line, item.endLine);
|
|
323
|
+
}
|
|
324
|
+
else if ("values" in item) {
|
|
325
|
+
consider(item.id, item.file, item.line, item.endLine);
|
|
326
|
+
}
|
|
327
|
+
else if ("rpcs" in item) {
|
|
328
|
+
consider(item.id, item.file, item.line, item.endLine);
|
|
329
|
+
for (const rpc of item.rpcs)
|
|
330
|
+
consider(rpc.id, rpc.file, rpc.line, rpc.endLine);
|
|
331
|
+
}
|
|
332
|
+
}
|
|
333
|
+
return best?.id;
|
|
334
|
+
}
|
|
335
|
+
/** Resolve go-to-definition for the identifier under cursor (type / rpc / service / enum). */
|
|
336
|
+
function findDefinitionAt(catalog, filePath, line, word) {
|
|
337
|
+
const token = word.replace(/^\./, "").trim();
|
|
338
|
+
if (!token)
|
|
339
|
+
return undefined;
|
|
340
|
+
const filePkg = packageOfFile(catalog, filePath);
|
|
341
|
+
for (const pkg of catalog.packages) {
|
|
342
|
+
for (const message of pkg.messages) {
|
|
343
|
+
for (const field of message.fields) {
|
|
344
|
+
if (normalize(message.file) !== normalize(filePath) || field.line !== line)
|
|
345
|
+
continue;
|
|
346
|
+
for (const linked of [field.value, field.key]) {
|
|
347
|
+
if (!linked?.navId)
|
|
348
|
+
continue;
|
|
349
|
+
if (linked.text === token || linked.text.endsWith(`.${token}`) || token.endsWith(`.${linked.text}`)) {
|
|
350
|
+
const hit = catalog.byId.get(linked.navId);
|
|
351
|
+
if (hit && "line" in hit && "file" in hit)
|
|
352
|
+
return { file: hit.file, line: hit.line };
|
|
353
|
+
}
|
|
354
|
+
}
|
|
355
|
+
}
|
|
356
|
+
}
|
|
357
|
+
for (const service of pkg.services) {
|
|
358
|
+
for (const rpc of service.rpcs) {
|
|
359
|
+
if (normalize(rpc.file) !== normalize(filePath) || line < rpc.line || line > rpc.endLine)
|
|
360
|
+
continue;
|
|
361
|
+
for (const linked of [rpc.request, rpc.response]) {
|
|
362
|
+
if (!linked.navId)
|
|
363
|
+
continue;
|
|
364
|
+
if (linked.text === token || linked.text.endsWith(`.${token}`)) {
|
|
365
|
+
const hit = catalog.byId.get(linked.navId);
|
|
366
|
+
if (hit && "line" in hit && "file" in hit)
|
|
367
|
+
return { file: hit.file, line: hit.line };
|
|
368
|
+
}
|
|
369
|
+
}
|
|
370
|
+
if (rpc.name === token)
|
|
371
|
+
return { file: rpc.file, line: rpc.line };
|
|
372
|
+
}
|
|
373
|
+
if (service.name === token && normalize(service.file) === normalize(filePath)) {
|
|
374
|
+
return { file: service.file, line: service.line };
|
|
375
|
+
}
|
|
376
|
+
}
|
|
377
|
+
}
|
|
378
|
+
const candidates = [];
|
|
379
|
+
for (const item of catalog.byId.values()) {
|
|
380
|
+
if ("messages" in item && "services" in item)
|
|
381
|
+
continue;
|
|
382
|
+
if (!("name" in item) || !("file" in item) || !("line" in item))
|
|
383
|
+
continue;
|
|
384
|
+
const name = "fullName" in item && typeof item.fullName === "string" ? item.fullName : item.name;
|
|
385
|
+
if (item.name !== token && name !== token && !name.endsWith(`.${token}`))
|
|
386
|
+
continue;
|
|
387
|
+
let score = 0;
|
|
388
|
+
if (item.name === token)
|
|
389
|
+
score += 2;
|
|
390
|
+
if ("package" in item && item.package === filePkg)
|
|
391
|
+
score += 3;
|
|
392
|
+
if (normalize(item.file) === normalize(filePath))
|
|
393
|
+
score += 1;
|
|
394
|
+
candidates.push({ loc: { file: item.file, line: item.line }, score });
|
|
395
|
+
}
|
|
396
|
+
candidates.sort((a, b) => b.score - a.score);
|
|
397
|
+
if (candidates[0])
|
|
398
|
+
return candidates[0].loc;
|
|
399
|
+
const enclosing = findSymbolAt(catalog, filePath, line);
|
|
400
|
+
if (!enclosing)
|
|
401
|
+
return undefined;
|
|
402
|
+
const hit = catalog.byId.get(enclosing);
|
|
403
|
+
if (hit && "file" in hit && "line" in hit)
|
|
404
|
+
return { file: hit.file, line: hit.line };
|
|
405
|
+
return undefined;
|
|
406
|
+
}
|
|
407
|
+
function packageOfFile(catalog, filePath) {
|
|
408
|
+
const target = normalize(filePath);
|
|
409
|
+
for (const pkg of catalog.packages) {
|
|
410
|
+
if (pkg.files.some((file) => normalize(file.path) === target))
|
|
411
|
+
return pkg.name;
|
|
412
|
+
for (const service of pkg.services) {
|
|
413
|
+
if (normalize(service.file) === target)
|
|
414
|
+
return pkg.name;
|
|
415
|
+
}
|
|
416
|
+
}
|
|
417
|
+
return undefined;
|
|
418
|
+
}
|
|
419
|
+
function addUsage(map, id, usage) {
|
|
420
|
+
if (!id)
|
|
421
|
+
return;
|
|
422
|
+
const target = map.get(id);
|
|
423
|
+
if (!target)
|
|
424
|
+
return;
|
|
425
|
+
if (target.usages.some((item) => item.navId === usage.navId && item.detail === usage.detail && item.label === usage.label)) {
|
|
426
|
+
return;
|
|
427
|
+
}
|
|
428
|
+
target.usages.push(usage);
|
|
429
|
+
}
|
|
430
|
+
function symbolId(kind, pkg, name, relPath, collision) {
|
|
431
|
+
const base = `${kind}:${pkg}:${name}`;
|
|
432
|
+
return collision ? `${base}:${relPath.replace(/\\/g, "/")}` : base;
|
|
433
|
+
}
|
|
434
|
+
function countNames(names) {
|
|
435
|
+
const map = new Map();
|
|
436
|
+
for (const name of names)
|
|
437
|
+
map.set(name, (map.get(name) ?? 0) + 1);
|
|
438
|
+
return map;
|
|
439
|
+
}
|
|
440
|
+
function sameServiceElsewhere(files, file, service) {
|
|
441
|
+
return files.some((other) => other !== file && other.package === file.package && other.services.some((item) => item.name === service.name));
|
|
442
|
+
}
|
|
443
|
+
function resolveImport(from, spec, files, byFile) {
|
|
444
|
+
const norm = spec.replace(/\\/g, "/");
|
|
445
|
+
const fromDir = from.path.replace(/\\/g, "/").split("/").slice(0, -1).join("/");
|
|
446
|
+
const joined = normalizePath(`${fromDir}/${norm}`);
|
|
447
|
+
const direct = byFile.get(normalize(joined));
|
|
448
|
+
if (direct)
|
|
449
|
+
return direct;
|
|
450
|
+
const matches = files.filter((file) => {
|
|
451
|
+
const rel = file.path.replace(/\\/g, "/");
|
|
452
|
+
return rel.endsWith(`/${norm}`) || rel.endsWith(norm);
|
|
453
|
+
});
|
|
454
|
+
if (matches.length === 1)
|
|
455
|
+
return matches[0];
|
|
456
|
+
return matches.find((file) => file.path.replace(/\\/g, "/").endsWith(`/${norm}`));
|
|
457
|
+
}
|
|
458
|
+
function imported(from, targetPath, files, byFile) {
|
|
459
|
+
return from.imports.some((spec) => {
|
|
460
|
+
const file = resolveImport(from, spec, files, byFile);
|
|
461
|
+
return file ? normalize(file.path) === normalize(targetPath) : false;
|
|
462
|
+
});
|
|
463
|
+
}
|
|
464
|
+
function compareService(a, b) {
|
|
465
|
+
const rank = (SERVICE_RANK[a.name] ?? 9) - (SERVICE_RANK[b.name] ?? 9);
|
|
466
|
+
if (rank !== 0)
|
|
467
|
+
return rank;
|
|
468
|
+
return a.name.localeCompare(b.name) || a.relPath.localeCompare(b.relPath);
|
|
469
|
+
}
|
|
470
|
+
function comparePackage(a, b) {
|
|
471
|
+
if (a.wellKnown !== b.wellKnown)
|
|
472
|
+
return a.wellKnown ? 1 : -1;
|
|
473
|
+
return a.name.localeCompare(b.name);
|
|
474
|
+
}
|
|
475
|
+
function push(map, key, value) {
|
|
476
|
+
const list = map.get(key) ?? [];
|
|
477
|
+
list.push(value);
|
|
478
|
+
map.set(key, list);
|
|
479
|
+
}
|
|
480
|
+
function normalize(filePath) {
|
|
481
|
+
return filePath.replace(/\\/g, "/").toLowerCase();
|
|
482
|
+
}
|
|
483
|
+
function normalizePath(filePath) {
|
|
484
|
+
const parts = [];
|
|
485
|
+
for (const part of filePath.replace(/\\/g, "/").split("/")) {
|
|
486
|
+
if (part === "..")
|
|
487
|
+
parts.pop();
|
|
488
|
+
else if (part && part !== ".")
|
|
489
|
+
parts.push(part);
|
|
490
|
+
}
|
|
491
|
+
return parts.join("/");
|
|
492
|
+
}
|
|
493
|
+
//# sourceMappingURL=catalog.js.map
|