-
Notifications
You must be signed in to change notification settings - Fork 1
Expand file tree
/
Copy pathbuild.cjs
More file actions
263 lines (239 loc) · 9.58 KB
/
Copy pathbuild.cjs
File metadata and controls
263 lines (239 loc) · 9.58 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
const fs = require('fs');
const path = require('path');
const inputPath = path.join(__dirname, 'node_modules', '@jlceda', 'pro-api-types', 'index.d.ts');
const outputPath = path.join(__dirname, 'iframe', 'script', 'eda_coder', 'EDA_Codes.js');
const outputDir = path.dirname(outputPath);
if (!fs.existsSync(outputDir)) {
fs.mkdirSync(outputDir, { recursive: true });
}
let content;
try {
content = fs.readFileSync(inputPath, 'utf8');
} catch (err) {
console.error('无法读取 index.d.ts,请确认 @jlceda/pro-api-types 已安装。');
process.exit(1);
}
// 仅将第一段(下划线前)小写,其余保留
// DMT_Board → dmt_Board, LIB_Classification → lib_Classification
function formatClassName(className) {
const parts = className.split('_');
if (parts.length === 0) return className.toLowerCase();
parts[0] = parts[0].toLowerCase();
return parts.join('_');
}
// 解析 /** ... */ 块内的行(startIndex 指向 /** 的下一行)
// 返回 { jsdoc, endIndex },endIndex 指向 */ 所在行
function parseJSDocLines(lines, startIndex) {
let description = '';
let remarks = '';
const params = [];
let returns = '';
const tags = [];
let j = startIndex;
while (j < lines.length) {
const l = lines[j].trim();
if (l === '*/') break;
const stripped = l.replace(/^\*\s?/, '');
if (stripped.startsWith('@param')) {
const m = stripped.match(/^@param\s+(?:\{[^}]*\}\s+)?([a-zA-Z_$][\w$]*)\??\s*(?:-\s*)?(.*)$/);
if (m) params.push({ name: m[1], description: m[2].trim().replace(/\r/g, '') });
} else if (stripped.startsWith('@returns') || stripped.startsWith('@return')) {
const m = stripped.match(/^@returns?\s+(.*)/);
if (m && !returns) returns = m[1].trim().replace(/\r/g, '');
} else if (stripped.startsWith('@remarks')) {
const m = stripped.match(/^@remarks\s+(.*)/);
if (m) remarks = (remarks ? remarks + ' ' : '') + m[1].trim().replace(/\r/g, '');
} else if (stripped.startsWith('@')) {
const m = stripped.match(/^@(\w+)/);
if (m && !['param', 'returns', 'return', 'remarks'].includes(m[1])) tags.push(m[1]);
} else if (stripped && !description) {
description = stripped.replace(/\r/g, '');
}
j++;
}
return { jsdoc: { description, remarks, params, returns, tags }, endIndex: j };
}
// ============================================================
// 主解析:行级扫描(每次全量重新生成,不做增量合并)
// 实际文件结构:declare global { enum/class/interface ... }
// class 和 enum 均无 declare 前缀,直接裸写在 declare global 块内
// ============================================================
const lines = content.split('\n');
const newResults = [];
let currentClass = null;
let currentEnum = null;
let pendingJsdoc = null;
for (let i = 0; i < lines.length; i++) {
const line = lines[i].trim();
// ── 单行 JSDoc /** ... */ ─────────────────────────────────
if (line.startsWith('/**') && line.endsWith('*/')) {
const commentText = line.slice(3, -2).trim();
pendingJsdoc = { description: commentText.replace(/\r/g, ''), remarks: '', params: [], returns: '', tags: [] };
continue;
}
// ── 多行 JSDoc /** ────────────────────────────────────────
if (line === '/**') {
const { jsdoc, endIndex } = parseJSDocLines(lines, i + 1);
pendingJsdoc = jsdoc;
i = endIndex; // 跳到 */ 行,for 循环再 +1
continue;
}
// ── class 声明(裸 class,位于 declare global 内)─────────
// 同时兼容 declare class(防御性)
const classMatch = line.match(/^(?:declare\s+)?class\s+(\w+)/);
if (classMatch && line.includes('{')) {
currentClass = classMatch[1];
currentEnum = null;
const classPath = `eda.${formatClassName(currentClass)}`;
if (pendingJsdoc && pendingJsdoc.description) {
const item = { methodPath: classPath, description: pendingJsdoc.description, parameters: [] };
if (pendingJsdoc.remarks) item.remarks = pendingJsdoc.remarks;
if (pendingJsdoc.tags.length) item.tags = pendingJsdoc.tags;
newResults.push(item);
}
pendingJsdoc = null;
continue;
}
// ── enum 声明(裸 enum,位于 declare global 内)───────────
// 同时兼容 declare enum / declare const enum(防御性)
const enumMatch = line.match(/^(?:declare\s+)?(?:const\s+)?enum\s+(\w+)/);
if (enumMatch && line.includes('{')) {
currentEnum = enumMatch[1];
currentClass = null;
const enumPath = `eda.${formatClassName(currentEnum)}`;
if (pendingJsdoc && pendingJsdoc.description) {
const item = { methodPath: enumPath, description: pendingJsdoc.description, parameters: [], isEnum: true };
if (pendingJsdoc.remarks) item.remarks = pendingJsdoc.remarks;
if (pendingJsdoc.tags.length) item.tags = pendingJsdoc.tags;
newResults.push(item);
}
pendingJsdoc = null;
continue;
}
// ── 块结束 } ──────────────────────────────────────────────
// .d.ts 中方法签名以 ; 结尾,内联对象的 } 后跟 ): 或 }> 等,
// 因此裸 } 可靠地标识 class/enum/interface 的结束
if (line === '}') {
currentClass = null;
currentEnum = null;
pendingJsdoc = null;
continue;
}
// ── class 内部 ────────────────────────────────────────────
if (currentClass) {
if (line.startsWith('/* Excluded from this release type:')) {
pendingJsdoc = null;
continue;
}
// 跳过 private / protected / constructor
if (/^(?:private|protected|constructor\s*\()/.test(line)) {
pendingJsdoc = null;
continue;
}
// 方法:word( 开头(含 async word()
const methodMatch = line.match(/^(?:async\s+)?(\w+)\s*\(/);
if (methodMatch && pendingJsdoc && pendingJsdoc.description) {
// 收集完整签名(可能跨多行),检测返回类型是否为 Promise
let sigText = line;
if (!line.trimEnd().endsWith(';')) {
// 跟踪括号嵌套深度,避免参数对象内的 ; 导致提前终止
let depth = 0;
for (const ch of line) {
if (ch === '{' || ch === '(') depth++;
if (ch === '}' || ch === ')') depth--;
}
let k = i + 1;
while (k < lines.length && k < i + 20) {
sigText += '\n' + lines[k];
const trimmed = lines[k].trimEnd();
for (const ch of trimmed) {
if (ch === '{' || ch === '(') depth++;
if (ch === '}' || ch === ')') depth--;
}
if (depth <= 0 && trimmed.endsWith(';')) break;
k++;
}
}
const returnIdx = sigText.lastIndexOf('):');
const isAsync =
returnIdx !== -1 &&
sigText.substring(returnIdx).trimStart().startsWith('): Promise');
const item = {
methodPath: `eda.${formatClassName(currentClass)}.${methodMatch[1]}`,
description: pendingJsdoc.description,
parameters: pendingJsdoc.params,
};
item.isAsync = isAsync;
if (pendingJsdoc.remarks) item.remarks = pendingJsdoc.remarks;
if (pendingJsdoc.returns) item.returns = pendingJsdoc.returns;
if (pendingJsdoc.tags.length) item.tags = pendingJsdoc.tags;
newResults.push(item);
pendingJsdoc = null;
continue;
}
// 非空非注释行清除 pendingJsdoc
if (line && !line.startsWith('*') && !line.startsWith('//')) {
pendingJsdoc = null;
}
continue;
}
// ── enum 内部 ─────────────────────────────────────────────
if (currentEnum) {
// 枚举成员:NAME = value, 或 NAME,
const memberMatch = line.match(/^([A-Z_][A-Z0-9_]*)\s*(?:=\s*[^,]+)?,?$/);
if (memberMatch) {
const enumPath = `eda.${formatClassName(currentEnum)}`;
newResults.push({
methodPath: `${enumPath}.${memberMatch[1]}`,
description: pendingJsdoc ? pendingJsdoc.description || '' : '',
parameters: [],
isEnumMember: true,
enumType: enumPath,
});
pendingJsdoc = null;
} else if (line && !line.startsWith('*') && !line.startsWith('//')) {
pendingJsdoc = null;
}
continue;
}
// ── 顶层其他行(interface、declare global 等)─────────────
// 非 JSDoc、非 class、非 enum 的行清除 pendingJsdoc,
// 防止 interface 内的 JSDoc 污染后续 class 的注册
if (line && !line.startsWith('*') && !line.startsWith('//')) {
pendingJsdoc = null;
}
}
// === 排序 ===
newResults.sort((a, b) => a.methodPath.localeCompare(b.methodPath));
// === 辅助函数:根据 methodPath 判断类型并返回后缀 ===
function getSuffix(methodPath) {
if (methodPath.includes('sch_')) {
return '(原理图)';
}
if (methodPath.includes('pcb_')) {
return '(PCB)';
}
return '';
}
// === 输出(每次全量重新生成) ===
const outputObject = newResults.map((item) => {
const suffix = getSuffix(item.methodPath);
const description = item.description + (suffix ? ` ${suffix}` : '');
const obj = {
methodPath: item.methodPath,
description: description,
parameters: (item.parameters || []).map((p) => ({ name: p.name, description: p.description })),
};
if (item.returns) obj.returns = item.returns;
if (item.remarks) obj.remarks = item.remarks;
if (item.tags && item.tags.length) obj.tags = item.tags;
if (item.isEnum) obj.isEnum = true;
if (item.isEnumMember) {
obj.isEnumMember = true;
obj.enumType = item.enumType;
}
if (item.isAsync !== undefined) obj.isAsync = item.isAsync;
return obj;
});
fs.writeFileSync(outputPath, 'edcode = ' + JSON.stringify(outputObject, null, '\t') + ';\n', 'utf8');
console.log(`全部重新生成 ${newResults.length} 条 API 补全数据`);