@chengzhao-dev/dsh-cpp-workflow 0.6.0 → 0.6.2
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.en.md +2 -2
- package/README.md +2 -2
- package/lib/client.js +328 -143
- package/lib/comment-policy.js +14 -6
- package/package.json +1 -1
- package/skills/building-cpp/SKILL.md +2 -2
- package/skills/configuring-cmake/SKILL.md +2 -2
- package/skills/developing-cpp/SKILL.md +2 -2
- package/skills/developing-cpp/references/comments.md +5 -6
- package/skills/developing-cpp/references/google-cpp-style.md +1 -1
- package/skills/developing-cpp/scripts/checking-comments.mjs +10 -25
- package/skills/developing-cpp/steps/03-declaration.md +1 -1
- package/skills/developing-cpp/steps/04-implementation.md +1 -1
package/README.en.md
CHANGED
|
@@ -19,13 +19,13 @@ The exception material follows the C++ Core Guidelines' Error handling section a
|
|
|
19
19
|
Pinned npm install:
|
|
20
20
|
|
|
21
21
|
```bash
|
|
22
|
-
dsh plugin --profile web add npm:@chengzhao-dev/dsh-cpp-workflow@0.6.
|
|
22
|
+
dsh plugin --profile web add npm:@chengzhao-dev/dsh-cpp-workflow@0.6.2
|
|
23
23
|
```
|
|
24
24
|
|
|
25
25
|
The matching Git tag:
|
|
26
26
|
|
|
27
27
|
```bash
|
|
28
|
-
dsh plugin --profile web add github:chengzhao-dev/dsh-cpp-workflow#v0.6.
|
|
28
|
+
dsh plugin --profile web add github:chengzhao-dev/dsh-cpp-workflow#v0.6.2
|
|
29
29
|
```
|
|
30
30
|
|
|
31
31
|
The desktop app owns the `desktop` profile. If the command-line `add` is rejected, add the plugin in the app settings and use the same pinned npm version. A Git tag install uses the prebuilt `lib/` committed in the repository and runs no build on the target machine; the package has no install script that needs approval.
|
package/README.md
CHANGED
|
@@ -19,13 +19,13 @@
|
|
|
19
19
|
npm 安装钉死版本:
|
|
20
20
|
|
|
21
21
|
```bash
|
|
22
|
-
dsh plugin --profile web add npm:@chengzhao-dev/dsh-cpp-workflow@0.6.
|
|
22
|
+
dsh plugin --profile web add npm:@chengzhao-dev/dsh-cpp-workflow@0.6.2
|
|
23
23
|
```
|
|
24
24
|
|
|
25
25
|
同版本的 Git 标签:
|
|
26
26
|
|
|
27
27
|
```bash
|
|
28
|
-
dsh plugin --profile web add github:chengzhao-dev/dsh-cpp-workflow#v0.6.
|
|
28
|
+
dsh plugin --profile web add github:chengzhao-dev/dsh-cpp-workflow#v0.6.2
|
|
29
29
|
```
|
|
30
30
|
|
|
31
31
|
桌面应用独占 `desktop` profile。命令行 `add` 被拒绝时,在应用设置里添加插件,地址填同一个 npm 版本。Git 标签安装使用仓库内已构建的 `lib/`,不在目标机器上重新构建;包内也不含需要授权执行的安装脚本。
|
package/lib/client.js
CHANGED
|
@@ -57,6 +57,10 @@ const TAG_LABEL = {
|
|
|
57
57
|
tparam: '@tparam',
|
|
58
58
|
retval: '@retval',
|
|
59
59
|
};
|
|
60
|
+
/** 预览里的示例摘要:用户没填 @brief 时用它,避免预览出现空标签。 */
|
|
61
|
+
const SAMPLE_BRIEF = '<一句话说明本文件>';
|
|
62
|
+
/** 预览里的示例文件名:只是占位符,不对应任何工程的真实文件。 */
|
|
63
|
+
const SAMPLE_FILE = '<文件名>';
|
|
60
64
|
/** 出厂配置:文件头只留 @file 与 @brief,契约标签全开,隐私标签全关。 */
|
|
61
65
|
const DEFAULT_CONFIG = {
|
|
62
66
|
brief: { enabled: true, value: '' },
|
|
@@ -103,7 +107,7 @@ function normalizeConfig(raw) {
|
|
|
103
107
|
*/
|
|
104
108
|
function renderFileHeader(raw, fileName) {
|
|
105
109
|
const config = normalizeConfig(raw);
|
|
106
|
-
const name = text(fileName) ||
|
|
110
|
+
const name = text(fileName) || SAMPLE_FILE;
|
|
107
111
|
const lines = [`/// @file ${name}`];
|
|
108
112
|
if (config.brief.enabled) {
|
|
109
113
|
lines.push(config.brief.value ? `/// @brief ${config.brief.value}` : '/// @brief');
|
|
@@ -119,15 +123,19 @@ function renderFileHeader(raw, fileName) {
|
|
|
119
123
|
*/
|
|
120
124
|
function previewParts(raw, sampleName) {
|
|
121
125
|
const config = normalizeConfig(raw);
|
|
122
|
-
const header = renderFileHeader(config, sampleName ||
|
|
126
|
+
const header = renderFileHeader(config, sampleName || SAMPLE_FILE);
|
|
127
|
+
// 预览是示例:@brief 开着但用户没填句子时,用示例文件自己的摘要顶替。
|
|
128
|
+
const briefIndex = header.indexOf('/// @brief');
|
|
129
|
+
if (briefIndex !== -1)
|
|
130
|
+
header[briefIndex] = `/// @brief ${SAMPLE_BRIEF}`;
|
|
123
131
|
const contract = [];
|
|
124
132
|
if (config.param)
|
|
125
|
-
contract.push('/// @param
|
|
133
|
+
contract.push('/// @param row 目标行号。');
|
|
126
134
|
if (config.returns)
|
|
127
135
|
contract.push('/// @return 放置成功时为 true。');
|
|
128
136
|
if (config.throws)
|
|
129
|
-
contract.push('/// @throws std::out_of_range
|
|
130
|
-
const declaration = '[[nodiscard]] bool
|
|
137
|
+
contract.push('/// @throws std::out_of_range 行号越界时抛出。');
|
|
138
|
+
const declaration = '[[nodiscard]] bool Place(int row, int column);';
|
|
131
139
|
return { header, contract, declaration };
|
|
132
140
|
}
|
|
133
141
|
/**
|
|
@@ -135,7 +143,7 @@ function previewParts(raw, sampleName) {
|
|
|
135
143
|
*/
|
|
136
144
|
function previewText(raw, sampleName) {
|
|
137
145
|
const { header, contract, declaration } = previewParts(raw, sampleName);
|
|
138
|
-
const lines = [...header, '///', '///
|
|
146
|
+
const lines = [...header, '///', '/// 放置成功时返回 true;行号越界或目标格子已占时返回 false。'];
|
|
139
147
|
if (contract.length > 0)
|
|
140
148
|
lines.push('///', ...contract);
|
|
141
149
|
lines.push(declaration);
|
|
@@ -187,28 +195,36 @@ function snapshotText(raw) {
|
|
|
187
195
|
// 客户端半侧:设置导航里的「C++ 工程流程」页与插件页的配置卡片共用同一个组件。
|
|
188
196
|
// 标签策略在本文件上方内联(由 scripts/building-client.mjs 从 src/comment-policy.js
|
|
189
197
|
// 拼进来),所以预览、默认值与服务端快照永远同源;这里只做表单与排版。
|
|
198
|
+
//
|
|
199
|
+
// 版式参照 VS Code 设置页:左侧标签与说明、右侧控件,行间细分隔线;
|
|
200
|
+
// 修改过默认值的行在标签前加一个圆点,底部固定一条保存栏。
|
|
190
201
|
const React = require('react')
|
|
191
202
|
const h = React.createElement
|
|
192
203
|
|
|
193
204
|
const PACKAGE_NAME = '@chengzhao-dev/dsh-cpp-workflow'
|
|
194
|
-
const SAMPLE_FILE = 'board.h'
|
|
195
205
|
|
|
196
206
|
const STRINGS = {
|
|
197
207
|
zh: {
|
|
198
208
|
nav: 'C++ 工程流程',
|
|
199
209
|
title: '文档注释',
|
|
200
|
-
intro: '
|
|
210
|
+
intro: '设置文件头与函数声明注释里默认写出的 Doxygen 标签;改动影响之后写出的注释,已有源码保持原样。',
|
|
201
211
|
header: '文件头',
|
|
212
|
+
headerHint: '每个手写源文件开头的一段 /// 注释。',
|
|
202
213
|
contract: '函数契约',
|
|
214
|
+
contractHint: '函数声明注释里要求的参数与失败信息。',
|
|
203
215
|
extra: '更多标签',
|
|
204
|
-
extraHint: '
|
|
216
|
+
extraHint: '需要时在单个函数上补写。',
|
|
205
217
|
file: '@file',
|
|
206
|
-
fileHint: '
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
218
|
+
fileHint: '取自当前文件名。',
|
|
219
|
+
briefHint: '一句话说明这个文件定义或实现什么;留空则由文件自述。',
|
|
220
|
+
authorHint: '作者署名;留空不写。',
|
|
221
|
+
dateHint: '创建或修改日期;留空不写。',
|
|
222
|
+
copyrightHint: '版权声明;留空不写。',
|
|
223
|
+
versionHint: '文件版本号;留空不写。',
|
|
224
|
+
emptyValue: '留空不写',
|
|
225
|
+
briefEmpty: '留空则由文件自述',
|
|
210
226
|
preview: '预览',
|
|
211
|
-
previewHint: '
|
|
227
|
+
previewHint: '按当前开关生成;文件名以占位符表示。',
|
|
212
228
|
save: '保存',
|
|
213
229
|
reset: '重置',
|
|
214
230
|
saved: '已保存,下一次读取技能时生效。',
|
|
@@ -216,24 +232,31 @@ const STRINGS = {
|
|
|
216
232
|
unsaved: '有未保存的改动。',
|
|
217
233
|
loading: '正在读取配置…',
|
|
218
234
|
unreadable: '当前账户不能修改这份配置。',
|
|
219
|
-
readOnly: '
|
|
220
|
-
|
|
235
|
+
readOnly: '此处显示出厂值;修改在「插件」页的配置卡片里进行。',
|
|
236
|
+
openSettings: '打开插件配置',
|
|
237
|
+
modified: '与出厂值不同',
|
|
221
238
|
},
|
|
222
239
|
en: {
|
|
223
240
|
nav: 'C++ Workflow',
|
|
224
241
|
title: 'Documentation comments',
|
|
225
|
-
intro: '
|
|
242
|
+
intro: 'Choose the Doxygen tags written by default in file headers and declaration comments. Changes affect new comments; existing sources stay as they are.',
|
|
226
243
|
header: 'File header',
|
|
244
|
+
headerHint: 'The /// block at the top of every hand-written source.',
|
|
227
245
|
contract: 'Function contract',
|
|
246
|
+
contractHint: 'Parameter and failure tags required in declaration comments.',
|
|
228
247
|
extra: 'More tags',
|
|
229
|
-
extraHint: '
|
|
248
|
+
extraHint: 'Add them per function when needed.',
|
|
230
249
|
file: '@file',
|
|
231
|
-
fileHint: '
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
250
|
+
fileHint: 'Taken from the current file name.',
|
|
251
|
+
briefHint: 'One sentence about what this file defines; leave empty and the file writes its own.',
|
|
252
|
+
authorHint: 'Author credit; left empty, omitted.',
|
|
253
|
+
dateHint: 'Creation or change date; left empty, omitted.',
|
|
254
|
+
copyrightHint: 'Copyright notice; left empty, omitted.',
|
|
255
|
+
versionHint: 'File version; left empty, omitted.',
|
|
256
|
+
emptyValue: 'Empty means omit',
|
|
257
|
+
briefEmpty: 'Empty: the file writes its own',
|
|
235
258
|
preview: 'Preview',
|
|
236
|
-
previewHint: '
|
|
259
|
+
previewHint: 'Generated from the switches; the file name is a placeholder.',
|
|
237
260
|
save: 'Save',
|
|
238
261
|
reset: 'Reset',
|
|
239
262
|
saved: 'Saved; the next skill read uses it.',
|
|
@@ -241,8 +264,9 @@ const STRINGS = {
|
|
|
241
264
|
unsaved: 'Unsaved changes.',
|
|
242
265
|
loading: 'Reading configuration…',
|
|
243
266
|
unreadable: 'This account cannot edit the configuration.',
|
|
244
|
-
readOnly: '
|
|
245
|
-
|
|
267
|
+
readOnly: 'Factory values are shown here; edit them on the Plugins page.',
|
|
268
|
+
openSettings: 'Open plugin settings',
|
|
269
|
+
modified: 'Differs from the factory value',
|
|
246
270
|
},
|
|
247
271
|
}
|
|
248
272
|
|
|
@@ -256,92 +280,188 @@ function t() {
|
|
|
256
280
|
}
|
|
257
281
|
|
|
258
282
|
const palette = {
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
283
|
+
label: 'var(--dsw-alias-label-primary, inherit)',
|
|
284
|
+
muted: 'var(--dsw-alias-label-secondary, #6b7280)',
|
|
285
|
+
line: 'var(--dsw-alias-border-l2, rgba(127, 127, 127, 0.24))',
|
|
286
|
+
surface: 'var(--dsw-alias-bg-layer-2, rgba(127, 127, 127, 0.06))',
|
|
287
|
+
field: 'var(--dsw-alias-bg-layer-1, rgba(127, 127, 127, 0.04))',
|
|
288
|
+
brand: 'var(--dsw-alias-brand-primary, #4f6bed)',
|
|
262
289
|
mono: 'ui-monospace, SFMono-Regular, Menlo, Consolas, monospace',
|
|
263
290
|
}
|
|
264
291
|
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
292
|
+
const descriptionStyle = () => ({ margin: '2px 0 0', fontSize: 12, lineHeight: 1.55, color: palette.muted })
|
|
293
|
+
const sectionTitleStyle = () => ({ margin: '0 0 2px', fontSize: 13, fontWeight: 600, color: palette.label })
|
|
294
|
+
|
|
295
|
+
function Section(props) {
|
|
296
|
+
return h(
|
|
297
|
+
'section',
|
|
298
|
+
{
|
|
299
|
+
style: {
|
|
300
|
+
marginTop: props.first ? 16 : 22,
|
|
301
|
+
paddingTop: props.first ? 0 : 14,
|
|
302
|
+
borderTop: props.first ? 'none' : `1px solid ${palette.line}`,
|
|
303
|
+
},
|
|
304
|
+
},
|
|
305
|
+
h('h4', { style: sectionTitleStyle() }, props.title),
|
|
306
|
+
props.description ? h('p', { style: descriptionStyle() }, props.description) : null,
|
|
307
|
+
h('div', { style: { marginTop: 6 } }, props.children),
|
|
308
|
+
)
|
|
274
309
|
}
|
|
275
310
|
|
|
276
|
-
function
|
|
277
|
-
return
|
|
311
|
+
function Row(props) {
|
|
312
|
+
return h(
|
|
313
|
+
'div',
|
|
314
|
+
{
|
|
315
|
+
style: {
|
|
316
|
+
display: 'grid',
|
|
317
|
+
gridTemplateColumns: 'minmax(0, 1fr) minmax(160px, 240px)',
|
|
318
|
+
columnGap: 24,
|
|
319
|
+
alignItems: 'start',
|
|
320
|
+
padding: '10px 0',
|
|
321
|
+
borderBottom: `1px solid ${palette.line}`,
|
|
322
|
+
},
|
|
323
|
+
},
|
|
324
|
+
h(
|
|
325
|
+
'div',
|
|
326
|
+
{ style: { minWidth: 0 } },
|
|
327
|
+
h(
|
|
328
|
+
'div',
|
|
329
|
+
{ style: { display: 'flex', alignItems: 'center', gap: 6, minHeight: 20 } },
|
|
330
|
+
props.modified
|
|
331
|
+
? h('span', {
|
|
332
|
+
key: 'dot',
|
|
333
|
+
title: props.modifiedLabel,
|
|
334
|
+
'aria-label': props.modifiedLabel,
|
|
335
|
+
style: { width: 6, height: 6, borderRadius: '50%', background: palette.brand, flex: 'none' },
|
|
336
|
+
})
|
|
337
|
+
: null,
|
|
338
|
+
h('span', { style: { fontSize: 13, lineHeight: '20px', color: palette.label, fontFamily: props.mono ? palette.mono : 'inherit' } }, props.label),
|
|
339
|
+
),
|
|
340
|
+
props.description ? h('p', { style: descriptionStyle() }, props.description) : null,
|
|
341
|
+
),
|
|
342
|
+
h(
|
|
343
|
+
'div',
|
|
344
|
+
{ style: { display: 'flex', alignItems: 'center', justifyContent: 'flex-end', gap: 8, minHeight: 20 } },
|
|
345
|
+
props.control,
|
|
346
|
+
),
|
|
347
|
+
)
|
|
278
348
|
}
|
|
279
349
|
|
|
280
|
-
function
|
|
350
|
+
function Checkbox(props) {
|
|
281
351
|
return h(
|
|
282
|
-
'
|
|
283
|
-
{
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
checked: Boolean(props.checked),
|
|
352
|
+
'button',
|
|
353
|
+
{
|
|
354
|
+
type: 'button',
|
|
355
|
+
role: 'checkbox',
|
|
356
|
+
'aria-checked': Boolean(props.checked),
|
|
357
|
+
'aria-label': props.label,
|
|
287
358
|
disabled: Boolean(props.disabled),
|
|
288
|
-
|
|
289
|
-
style: {
|
|
290
|
-
|
|
291
|
-
|
|
359
|
+
onClick: () => props.onChange(!props.checked),
|
|
360
|
+
style: {
|
|
361
|
+
flex: 'none',
|
|
362
|
+
width: 16,
|
|
363
|
+
height: 16,
|
|
364
|
+
padding: 0,
|
|
365
|
+
borderRadius: 4,
|
|
366
|
+
border: `1px solid ${props.checked ? palette.brand : palette.line}`,
|
|
367
|
+
background: props.checked ? palette.brand : 'transparent',
|
|
368
|
+
display: 'inline-flex',
|
|
369
|
+
alignItems: 'center',
|
|
370
|
+
justifyContent: 'center',
|
|
371
|
+
cursor: props.disabled ? 'default' : 'pointer',
|
|
372
|
+
opacity: props.disabled ? 0.55 : 1,
|
|
373
|
+
transition: 'background-color 0.12s ease, border-color 0.12s ease',
|
|
374
|
+
},
|
|
375
|
+
},
|
|
376
|
+
h(
|
|
377
|
+
'svg',
|
|
378
|
+
{ width: 11, height: 11, viewBox: '0 0 12 12', 'aria-hidden': 'true' },
|
|
379
|
+
h('path', {
|
|
380
|
+
d: 'M2.6 6.3 5 8.7l4.4-5.2',
|
|
381
|
+
fill: 'none',
|
|
382
|
+
stroke: '#fff',
|
|
383
|
+
strokeWidth: 1.7,
|
|
384
|
+
strokeLinecap: 'round',
|
|
385
|
+
strokeLinejoin: 'round',
|
|
386
|
+
opacity: props.checked ? 1 : 0,
|
|
387
|
+
}),
|
|
388
|
+
),
|
|
292
389
|
)
|
|
293
390
|
}
|
|
294
391
|
|
|
295
|
-
function
|
|
392
|
+
function ValueRow(props) {
|
|
296
393
|
const s = props.strings
|
|
297
394
|
const entry = props.entry
|
|
298
|
-
return h(
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
395
|
+
return h(Row, {
|
|
396
|
+
label: TAG_LABEL[props.tag],
|
|
397
|
+
mono: true,
|
|
398
|
+
description: props.hint,
|
|
399
|
+
modified: props.modified,
|
|
400
|
+
modifiedLabel: s.modified,
|
|
401
|
+
control: [
|
|
402
|
+
h(Checkbox, {
|
|
403
|
+
key: 'check',
|
|
303
404
|
label: TAG_LABEL[props.tag],
|
|
304
405
|
checked: entry.enabled,
|
|
406
|
+
disabled: props.disabled,
|
|
305
407
|
onChange: (enabled) => props.onChange({ ...entry, enabled }),
|
|
306
408
|
}),
|
|
307
|
-
h('
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
409
|
+
h('input', {
|
|
410
|
+
key: 'input',
|
|
411
|
+
type: 'text',
|
|
412
|
+
value: entry.value,
|
|
413
|
+
placeholder: props.tag === 'brief' ? s.briefEmpty : s.emptyValue,
|
|
414
|
+
disabled: props.disabled || !entry.enabled,
|
|
415
|
+
'aria-label': TAG_LABEL[props.tag],
|
|
416
|
+
onChange: (event) => props.onChange({ ...entry, value: event.target.value }),
|
|
417
|
+
style: {
|
|
418
|
+
width: '100%',
|
|
419
|
+
minWidth: 0,
|
|
420
|
+
fontSize: 13,
|
|
421
|
+
lineHeight: '18px',
|
|
422
|
+
padding: '3px 7px',
|
|
423
|
+
borderRadius: 4,
|
|
424
|
+
border: `1px solid ${palette.line}`,
|
|
425
|
+
background: entry.enabled && !props.disabled ? palette.field : 'transparent',
|
|
426
|
+
color: entry.enabled ? 'inherit' : palette.muted,
|
|
427
|
+
fontFamily: palette.mono,
|
|
428
|
+
boxSizing: 'border-box',
|
|
429
|
+
},
|
|
430
|
+
}),
|
|
431
|
+
],
|
|
432
|
+
})
|
|
433
|
+
}
|
|
434
|
+
|
|
435
|
+
function FlagRow(props) {
|
|
436
|
+
return h(Row, {
|
|
437
|
+
label: TAG_LABEL[props.tag],
|
|
438
|
+
mono: true,
|
|
439
|
+
description: props.hint,
|
|
440
|
+
modified: props.modified,
|
|
441
|
+
modifiedLabel: props.strings.modified,
|
|
442
|
+
control: h(Checkbox, {
|
|
443
|
+
label: TAG_LABEL[props.tag],
|
|
444
|
+
checked: Boolean(props.checked),
|
|
445
|
+
disabled: props.disabled,
|
|
446
|
+
onChange: (value) => props.onChange(value),
|
|
326
447
|
}),
|
|
327
|
-
)
|
|
448
|
+
})
|
|
328
449
|
}
|
|
329
450
|
|
|
330
451
|
function Preview(props) {
|
|
331
452
|
const s = props.strings
|
|
332
|
-
const text = previewText(props.config, SAMPLE_FILE)
|
|
333
453
|
return h(
|
|
334
454
|
'section',
|
|
335
|
-
{ 'aria-label': s.preview, style: { marginTop:
|
|
336
|
-
h('h4', { style:
|
|
337
|
-
h('p', { style:
|
|
455
|
+
{ 'aria-label': s.preview, style: { marginTop: 22, paddingTop: 14, borderTop: `1px solid ${palette.line}` } },
|
|
456
|
+
h('h4', { style: sectionTitleStyle() }, s.preview),
|
|
457
|
+
h('p', { style: descriptionStyle() }, s.previewHint),
|
|
338
458
|
h(
|
|
339
459
|
'pre',
|
|
340
460
|
{
|
|
341
461
|
style: {
|
|
342
462
|
margin: '8px 0 0',
|
|
343
463
|
padding: '10px 12px',
|
|
344
|
-
borderRadius:
|
|
464
|
+
borderRadius: 6,
|
|
345
465
|
background: palette.surface,
|
|
346
466
|
border: `1px solid ${palette.line}`,
|
|
347
467
|
overflowX: 'auto',
|
|
@@ -349,9 +469,10 @@ function Preview(props) {
|
|
|
349
469
|
lineHeight: 1.65,
|
|
350
470
|
fontFamily: palette.mono,
|
|
351
471
|
whiteSpace: 'pre',
|
|
472
|
+
color: palette.label,
|
|
352
473
|
},
|
|
353
474
|
},
|
|
354
|
-
|
|
475
|
+
previewText(props.config),
|
|
355
476
|
),
|
|
356
477
|
)
|
|
357
478
|
}
|
|
@@ -366,6 +487,10 @@ function diffOperations(draft, base) {
|
|
|
366
487
|
return operations
|
|
367
488
|
}
|
|
368
489
|
|
|
490
|
+
function isModified(key, draft) {
|
|
491
|
+
return JSON.stringify(draft[key]) !== JSON.stringify(DEFAULT_CONFIG[key])
|
|
492
|
+
}
|
|
493
|
+
|
|
369
494
|
function ConfigPage(props) {
|
|
370
495
|
const s = t()
|
|
371
496
|
const form = props.form
|
|
@@ -420,94 +545,121 @@ function ConfigPage(props) {
|
|
|
420
545
|
}
|
|
421
546
|
|
|
422
547
|
const editable = Boolean(form)
|
|
423
|
-
const
|
|
424
|
-
|
|
548
|
+
const headerHints = {
|
|
549
|
+
brief: s.briefHint,
|
|
550
|
+
author: s.authorHint,
|
|
551
|
+
date: s.dateHint,
|
|
552
|
+
copyright: s.copyrightHint,
|
|
553
|
+
version: s.versionHint,
|
|
554
|
+
}
|
|
425
555
|
|
|
426
556
|
return h(
|
|
427
557
|
'div',
|
|
428
|
-
{ style: { display: 'flex', flexDirection: 'column',
|
|
558
|
+
{ style: { display: 'flex', flexDirection: 'column', padding: '2px 0 8px', maxWidth: 760, color: palette.label } },
|
|
429
559
|
h('h3', { style: { margin: '0 0 2px', fontSize: 15, fontWeight: 600 } }, s.title),
|
|
430
|
-
h('p', { style:
|
|
560
|
+
h('p', { style: descriptionStyle() }, s.intro),
|
|
431
561
|
!editable
|
|
432
|
-
? h(
|
|
562
|
+
? h(
|
|
563
|
+
'div',
|
|
564
|
+
{
|
|
565
|
+
role: 'status',
|
|
566
|
+
style: {
|
|
567
|
+
display: 'flex',
|
|
568
|
+
alignItems: 'center',
|
|
569
|
+
gap: 10,
|
|
570
|
+
flexWrap: 'wrap',
|
|
571
|
+
marginTop: 10,
|
|
572
|
+
padding: '8px 10px',
|
|
573
|
+
borderRadius: 6,
|
|
574
|
+
background: palette.surface,
|
|
575
|
+
border: `1px solid ${palette.line}`,
|
|
576
|
+
},
|
|
577
|
+
},
|
|
578
|
+
h('p', { style: { ...descriptionStyle(), margin: 0, flex: '1 1 200px' } }, s.readOnly),
|
|
579
|
+
props.openPluginPage
|
|
580
|
+
? h('button', { type: 'button', onClick: props.openPluginPage, style: secondaryButton(true) }, s.openSettings)
|
|
581
|
+
: null,
|
|
582
|
+
)
|
|
433
583
|
: null,
|
|
434
584
|
editable && !ready && status !== 'missing'
|
|
435
|
-
? h('p', { role: 'status', style:
|
|
585
|
+
? h('p', { role: 'status', style: descriptionStyle() }, status === 'loading' ? s.loading : s.unreadable)
|
|
436
586
|
: null,
|
|
437
587
|
h(
|
|
438
|
-
|
|
439
|
-
{
|
|
440
|
-
h(
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
)
|
|
447
|
-
|
|
448
|
-
h(HeaderTagRow, {
|
|
588
|
+
Section,
|
|
589
|
+
{ title: s.header, description: s.headerHint, first: true },
|
|
590
|
+
h(Row, {
|
|
591
|
+
label: s.file,
|
|
592
|
+
mono: true,
|
|
593
|
+
description: s.fileHint,
|
|
594
|
+
control: null,
|
|
595
|
+
}),
|
|
596
|
+
...HEADER_TAGS.map((tag) =>
|
|
597
|
+
h(ValueRow, {
|
|
449
598
|
key: tag,
|
|
450
599
|
tag,
|
|
600
|
+
hint: headerHints[tag],
|
|
451
601
|
strings: s,
|
|
602
|
+
disabled: !editable,
|
|
603
|
+
modified: editable && isModified(tag, draft),
|
|
452
604
|
entry: draft[tag],
|
|
453
605
|
onChange: (entry) => update({ [tag]: entry }),
|
|
454
606
|
}),
|
|
455
607
|
),
|
|
456
608
|
),
|
|
457
609
|
h(
|
|
458
|
-
|
|
459
|
-
{
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
onChange: (value) => update({ [tag]: value }),
|
|
472
|
-
}),
|
|
473
|
-
h('p', { style: hintStyle() }, TAG_TEXT[tag]),
|
|
474
|
-
),
|
|
475
|
-
),
|
|
610
|
+
Section,
|
|
611
|
+
{ title: s.contract, description: s.contractHint },
|
|
612
|
+
...CONTRACT_TAGS.map((tag) =>
|
|
613
|
+
h(FlagRow, {
|
|
614
|
+
key: tag,
|
|
615
|
+
tag,
|
|
616
|
+
hint: TAG_TEXT[tag],
|
|
617
|
+
strings: s,
|
|
618
|
+
disabled: !editable,
|
|
619
|
+
modified: editable && isModified(tag, draft),
|
|
620
|
+
checked: draft[tag],
|
|
621
|
+
onChange: (value) => update({ [tag]: value }),
|
|
622
|
+
}),
|
|
476
623
|
),
|
|
477
624
|
),
|
|
478
625
|
h(
|
|
479
|
-
|
|
480
|
-
{
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
checked: draft[tag],
|
|
493
|
-
onChange: (value) => update({ [tag]: value }),
|
|
494
|
-
}),
|
|
495
|
-
),
|
|
496
|
-
),
|
|
626
|
+
Section,
|
|
627
|
+
{ title: s.extra, description: s.extraHint },
|
|
628
|
+
...EXTRA_TAGS.map((tag) =>
|
|
629
|
+
h(FlagRow, {
|
|
630
|
+
key: tag,
|
|
631
|
+
tag,
|
|
632
|
+
hint: TAG_TEXT[tag],
|
|
633
|
+
strings: s,
|
|
634
|
+
disabled: !editable,
|
|
635
|
+
modified: editable && isModified(tag, draft),
|
|
636
|
+
checked: draft[tag],
|
|
637
|
+
onChange: (value) => update({ [tag]: value }),
|
|
638
|
+
}),
|
|
497
639
|
),
|
|
498
640
|
),
|
|
499
641
|
h(Preview, { config: draft, strings: s }),
|
|
500
642
|
editable
|
|
501
643
|
? h(
|
|
502
644
|
'div',
|
|
503
|
-
{
|
|
645
|
+
{
|
|
646
|
+
style: {
|
|
647
|
+
display: 'flex',
|
|
648
|
+
alignItems: 'center',
|
|
649
|
+
gap: 10,
|
|
650
|
+
marginTop: 20,
|
|
651
|
+
paddingTop: 12,
|
|
652
|
+
borderTop: `1px solid ${palette.line}`,
|
|
653
|
+
flexWrap: 'wrap',
|
|
654
|
+
},
|
|
655
|
+
},
|
|
504
656
|
h(
|
|
505
657
|
'button',
|
|
506
658
|
{
|
|
507
659
|
type: 'button',
|
|
508
660
|
onClick: save,
|
|
509
|
-
disabled: busy || !
|
|
510
|
-
style:
|
|
661
|
+
disabled: busy || !ready || !dirty,
|
|
662
|
+
style: primaryButton(dirty && ready && !busy),
|
|
511
663
|
},
|
|
512
664
|
s.save,
|
|
513
665
|
),
|
|
@@ -517,22 +669,55 @@ function ConfigPage(props) {
|
|
|
517
669
|
type: 'button',
|
|
518
670
|
onClick: reset,
|
|
519
671
|
disabled: busy || !dirty,
|
|
520
|
-
style:
|
|
672
|
+
style: secondaryButton(dirty && !busy),
|
|
521
673
|
},
|
|
522
674
|
s.reset,
|
|
523
675
|
),
|
|
524
|
-
h('p', { role: 'status', style: { ...
|
|
676
|
+
h('p', { role: 'status', style: { ...descriptionStyle(), margin: 0, flex: '1 1 200px' } }, message || (dirty ? s.unsaved : '')),
|
|
525
677
|
)
|
|
526
678
|
: null,
|
|
527
679
|
)
|
|
528
680
|
}
|
|
529
681
|
|
|
530
|
-
function
|
|
682
|
+
function primaryButton(enabled) {
|
|
683
|
+
return {
|
|
684
|
+
fontSize: 13,
|
|
685
|
+
lineHeight: '20px',
|
|
686
|
+
padding: '4px 12px',
|
|
687
|
+
borderRadius: 4,
|
|
688
|
+
border: '1px solid transparent',
|
|
689
|
+
background: enabled ? palette.brand : 'var(--dsw-alias-bg-layer-3, rgba(127, 127, 127, 0.2))',
|
|
690
|
+
color: enabled ? '#fff' : palette.muted,
|
|
691
|
+
cursor: enabled ? 'pointer' : 'default',
|
|
692
|
+
}
|
|
693
|
+
}
|
|
694
|
+
|
|
695
|
+
function secondaryButton(enabled) {
|
|
696
|
+
return {
|
|
697
|
+
fontSize: 13,
|
|
698
|
+
lineHeight: '20px',
|
|
699
|
+
padding: '4px 12px',
|
|
700
|
+
borderRadius: 4,
|
|
701
|
+
border: `1px solid ${palette.line}`,
|
|
702
|
+
background: 'transparent',
|
|
703
|
+
color: enabled ? palette.label : palette.muted,
|
|
704
|
+
cursor: enabled ? 'pointer' : 'default',
|
|
705
|
+
}
|
|
706
|
+
}
|
|
707
|
+
|
|
708
|
+
function page(ownerProps, openPluginPage) {
|
|
531
709
|
const props = ownerProps || {}
|
|
532
|
-
return h(ConfigPage, { ...props,
|
|
710
|
+
return h(ConfigPage, { ...props, openPluginPage })
|
|
533
711
|
}
|
|
534
712
|
|
|
535
713
|
function apply(ctx) {
|
|
714
|
+
let openPluginPage
|
|
715
|
+
try {
|
|
716
|
+
const navigation = typeof ctx.get === 'function' ? ctx.get('pluginNavigation') : undefined
|
|
717
|
+
if (navigation && typeof navigation.openBundle === 'function') {
|
|
718
|
+
openPluginPage = () => navigation.openBundle(PACKAGE_NAME)
|
|
719
|
+
}
|
|
720
|
+
} catch {}
|
|
536
721
|
ctx.slots.inject('settings.section', () =>
|
|
537
722
|
ctx.slots.register(
|
|
538
723
|
{
|
|
@@ -541,7 +726,7 @@ function apply(ctx) {
|
|
|
541
726
|
order: 60,
|
|
542
727
|
label: () => t().nav,
|
|
543
728
|
},
|
|
544
|
-
(ownerProps) => page(ownerProps,
|
|
729
|
+
(ownerProps) => page(ownerProps, openPluginPage),
|
|
545
730
|
),
|
|
546
731
|
)
|
|
547
732
|
ctx.slots.inject('plugins.bundle.config', () =>
|
|
@@ -551,7 +736,7 @@ function apply(ctx) {
|
|
|
551
736
|
key: PACKAGE_NAME,
|
|
552
737
|
label: () => t().nav,
|
|
553
738
|
},
|
|
554
|
-
(ownerProps) => (ownerProps && ownerProps.view === 'summary' ? null : page(ownerProps,
|
|
739
|
+
(ownerProps) => (ownerProps && ownerProps.view === 'summary' ? null : page(ownerProps, undefined)),
|
|
555
740
|
),
|
|
556
741
|
)
|
|
557
742
|
}
|
package/lib/comment-policy.js
CHANGED
|
@@ -50,6 +50,10 @@ export const TAG_LABEL = {
|
|
|
50
50
|
tparam: '@tparam',
|
|
51
51
|
retval: '@retval',
|
|
52
52
|
};
|
|
53
|
+
/** 预览里的示例摘要:用户没填 @brief 时用它,避免预览出现空标签。 */
|
|
54
|
+
export const SAMPLE_BRIEF = '<一句话说明本文件>';
|
|
55
|
+
/** 预览里的示例文件名:只是占位符,不对应任何工程的真实文件。 */
|
|
56
|
+
export const SAMPLE_FILE = '<文件名>';
|
|
53
57
|
/** 出厂配置:文件头只留 @file 与 @brief,契约标签全开,隐私标签全关。 */
|
|
54
58
|
export const DEFAULT_CONFIG = {
|
|
55
59
|
brief: { enabled: true, value: '' },
|
|
@@ -96,7 +100,7 @@ export function normalizeConfig(raw) {
|
|
|
96
100
|
*/
|
|
97
101
|
export function renderFileHeader(raw, fileName) {
|
|
98
102
|
const config = normalizeConfig(raw);
|
|
99
|
-
const name = text(fileName) ||
|
|
103
|
+
const name = text(fileName) || SAMPLE_FILE;
|
|
100
104
|
const lines = [`/// @file ${name}`];
|
|
101
105
|
if (config.brief.enabled) {
|
|
102
106
|
lines.push(config.brief.value ? `/// @brief ${config.brief.value}` : '/// @brief');
|
|
@@ -112,15 +116,19 @@ export function renderFileHeader(raw, fileName) {
|
|
|
112
116
|
*/
|
|
113
117
|
export function previewParts(raw, sampleName) {
|
|
114
118
|
const config = normalizeConfig(raw);
|
|
115
|
-
const header = renderFileHeader(config, sampleName ||
|
|
119
|
+
const header = renderFileHeader(config, sampleName || SAMPLE_FILE);
|
|
120
|
+
// 预览是示例:@brief 开着但用户没填句子时,用示例文件自己的摘要顶替。
|
|
121
|
+
const briefIndex = header.indexOf('/// @brief');
|
|
122
|
+
if (briefIndex !== -1)
|
|
123
|
+
header[briefIndex] = `/// @brief ${SAMPLE_BRIEF}`;
|
|
116
124
|
const contract = [];
|
|
117
125
|
if (config.param)
|
|
118
|
-
contract.push('/// @param
|
|
126
|
+
contract.push('/// @param row 目标行号。');
|
|
119
127
|
if (config.returns)
|
|
120
128
|
contract.push('/// @return 放置成功时为 true。');
|
|
121
129
|
if (config.throws)
|
|
122
|
-
contract.push('/// @throws std::out_of_range
|
|
123
|
-
const declaration = '[[nodiscard]] bool
|
|
130
|
+
contract.push('/// @throws std::out_of_range 行号越界时抛出。');
|
|
131
|
+
const declaration = '[[nodiscard]] bool Place(int row, int column);';
|
|
124
132
|
return { header, contract, declaration };
|
|
125
133
|
}
|
|
126
134
|
/**
|
|
@@ -128,7 +136,7 @@ export function previewParts(raw, sampleName) {
|
|
|
128
136
|
*/
|
|
129
137
|
export function previewText(raw, sampleName) {
|
|
130
138
|
const { header, contract, declaration } = previewParts(raw, sampleName);
|
|
131
|
-
const lines = [...header, '///', '///
|
|
139
|
+
const lines = [...header, '///', '/// 放置成功时返回 true;行号越界或目标格子已占时返回 false。'];
|
|
132
140
|
if (contract.length > 0)
|
|
133
141
|
lines.push('///', ...contract);
|
|
134
142
|
lines.push(declaration);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@chengzhao-dev/dsh-cpp-workflow",
|
|
3
|
-
"version": "0.6.
|
|
3
|
+
"version": "0.6.2",
|
|
4
4
|
"description": "写 C++ 源码、配 CMake、用 Clang 构建;文档注释统一为 /// Doxygen,文件头标签可在设置里改。 · Write C++ sources, configure CMake, and build with Clang; /// Doxygen comments, with file-header tags you can change in Settings.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./lib/index.js",
|
|
@@ -9,7 +9,7 @@ metadata:
|
|
|
9
9
|
|
|
10
10
|
## 适用场景
|
|
11
11
|
|
|
12
|
-
|
|
12
|
+
本技能处理环境声明、构建入口与工具配置:`environment.json`、`running-build.sh`、`.clang-format`、`.clang-tidy`、`.clangd`、`.vscode/`、构建命令、编译数据库、CTest 和运行。`CMakeLists.txt` 与 `cmake/` 下的 target、属性、标准和模块由 `configuring-cmake` 负责;本技能只读它们来配置和检查,不在这一步改 target 图。函数体、类型、命名、异常和源码注释只开 `developing-cpp`。
|
|
13
13
|
|
|
14
14
|
一次改动同时碰到源码和构建文件时,先打开 `developing-cpp` 定行为、边界与实现;需要新增或移动源文件、改库与程序的边界、补测试 target 时,转到 `configuring-cmake` 把文件写进 CMake;最后回到本技能做定工具配置与通过检查。只改函数体、命名、异常或注释且入口命令可运行时,定实现后只开本技能的「通过检查」。
|
|
15
15
|
|
|
@@ -26,7 +26,7 @@ metadata:
|
|
|
26
26
|
| 增量修正 | 用户点了一条工具配置、一条诊断或一个入口脚本错误 | 先判断属于定构建还是定工具配置,只改那一处,不动无关 target 和配置 |
|
|
27
27
|
| 整段重构 | 新工程、新阶段、换环境组合,或第一次接构建 | 同样三步;先确认 target 与标准已由 configuring-cmake 写好,再改工具文件,最后跑完整检查 |
|
|
28
28
|
|
|
29
|
-
|
|
29
|
+
两种走法共用这些约束:不发明项目没有声明的环境轴、命令和 target;只改格式或配置时不改可观察行为;不覆盖已有源码、`CMakeLists.txt` 和 `.clang-*`;不安装工具链;代理侧不写 `wsl` 或 `bash -lc`。后一步发现前一步没做完,退回那一步。拿不准先用增量修正;发现环境轴、编译器、构建目录或 target 契约需要改,退回整段,并转 `configuring-cmake`。
|
|
30
30
|
|
|
31
31
|
## 步骤
|
|
32
32
|
|
|
@@ -9,7 +9,7 @@ metadata:
|
|
|
9
9
|
|
|
10
10
|
## 适用场景
|
|
11
11
|
|
|
12
|
-
|
|
12
|
+
本技能处理 CMake 文件与 target 契约:`CMakeLists.txt`、`cmake/` 模块、`target_*` 命令、目标属性、include 与链接边界、输出目录、共享库运行时路径、`FetchContent`、preset 与安装导出。函数体、类型、命名、异常和源码注释只开 `developing-cpp`;配置完成后要跑构建、格式化、静态检查或入口脚本时开 `building-cpp`。
|
|
13
13
|
|
|
14
14
|
一次改动同时碰到源码和 CMake 时先开 `developing-cpp` 定行为与边界,再用本技能把源文件、库和程序写进 target,最后到 `building-cpp` 跑检查。只改一个 target 的 include、链接或标准时走增量修正;新工程、新组件、换语言标准或抽公共函数时走整段重构。
|
|
15
15
|
|
|
@@ -22,7 +22,7 @@ metadata:
|
|
|
22
22
|
| 增量修正 | 一个 target 的 include、链接、标准、输出目录或一条属性不对 | 先读现有 `CMakeLists.txt`,只改这一处,不动无关 target 和全局设置 |
|
|
23
23
|
| 整段重构 | 新工程、新组件、抽公共函数、换语言标准或整理目录 | 同样三步;先定 target 图与标准,再定模块与函数,最后跑完整检查 |
|
|
24
24
|
|
|
25
|
-
|
|
25
|
+
两种走法共用这些约束:不发明项目没有声明的 target、标准、编译器或目录;不覆盖已有 `CMakeLists.txt`;不用全局 `include_directories()`、`add_definitions()`、`CMAKE_CXX_FLAGS` 给所有 target 加料;不写 `file(GLOB ...)`;不把 `main()` 编进库;库与程序不下载依赖,只有测试需要时调用 `LinkSettings_FetchGoogleTest`。后一步发现前一步没做完,退回那一步。
|
|
26
26
|
|
|
27
27
|
## 步骤
|
|
28
28
|
|
|
@@ -9,7 +9,7 @@ metadata:
|
|
|
9
9
|
|
|
10
10
|
## 适用场景
|
|
11
11
|
|
|
12
|
-
|
|
12
|
+
本技能处理换一台机器也不会改的源码内容:可观察行为、库与程序的边界、声明、命名、异常和源码注释。文件槽位与守卫以 `references/file-template.md` 为准;排版、命名、头文件与包含顺序以 `references/google-cpp-style.md` 的 Google 基线为准。异常条文取自 C++ Core Guidelines 的 Error handling,只约束开发期的源码,不进入插件运行时。`CMakeLists.txt` 与 `cmake/` 开 `configuring-cmake`;`running-build.sh`、`.clang-*`、`.vscode/` 和 `environment.json` 开 `building-cpp`。只改函数体、命名、异常或注释且入口命令可运行时,定实现后只开 `building-cpp` 的「通过检查」;新增或移动源文件、改库与程序的边界、补测试 target 时,声明和实现可以先写,但完成前必须开 `configuring-cmake` 把文件写进 CMake,再开 `building-cpp` 定工具配置并通过检查。`.qmd`、仓库 README、Python 和非 C++ 文档不打开本技能。
|
|
13
13
|
|
|
14
14
|
存在项目技能 `cpp-workflow-project` 时先读它的 `SKILL.md`:工程名、库与程序的边界、阶段表、术语增补、入口命令。没有项目技能时门禁不会放出本技能,不要猜项目约定。
|
|
15
15
|
|
|
@@ -22,7 +22,7 @@ metadata:
|
|
|
22
22
|
| 增量修正 | 用户点了一个函数、一条诊断或一个文件 | 沿本技能步骤走,只改判为不合格的那一处,其余保持原样 |
|
|
23
23
|
| 整段重构 | 新工程、新阶段、用户没点名范围,或改动跨过库与程序的边界 | 同样四步;第 1、2 步先定删留,不动函数体;第 3、4 步才改代码 |
|
|
24
24
|
|
|
25
|
-
|
|
25
|
+
两种走法共用这些约束:不发明阶段表和用户请求里没有的行为、类型和命令;只改注释或格式时不改可观察行为;不覆盖已有源码;不安装工具链;代理侧不写 `wsl` 或 `bash -lc`。后一步发现前一步没做完,退回那一步,不在当前步补结构。拿不准先用增量修正;做到一半发现边界被打破,退回整段重构,从被打破的那一步重走。
|
|
26
26
|
|
|
27
27
|
## 步骤
|
|
28
28
|
|
|
@@ -129,20 +129,19 @@ GameResult result_ = GameResult::kInProgress;
|
|
|
129
129
|
```
|
|
130
130
|
|
|
131
131
|
- C++ 源文件用以上格式;CMake 用 `#`,YAML/JSON 用 `#`。
|
|
132
|
-
- 超过约 200
|
|
132
|
+
- 超过约 200 行的文件用空行分成几节,每节的说明写在一行 `///` 上,不画装饰线。
|
|
133
133
|
- 文件头 3–7 行;`@file` 与 `@brief` 各占一行,后面只写本文件的范围与组成,不写实现步骤、作者和变更史。
|
|
134
134
|
|
|
135
|
-
##
|
|
135
|
+
## 排版:符号、宽度与标签
|
|
136
136
|
|
|
137
|
-
- 行宽跟项目 `.clang-format` 的 `ColumnLimit` 走(Google 基线是 80
|
|
138
|
-
- 横幅宽度等于 `ColumnLimit`:`//===` 起头、`===//` 收尾,中间补 `-`;章节横幅同一格式。
|
|
137
|
+
- 行宽跟项目 `.clang-format` 的 `ColumnLimit` 走(Google 基线是 80 列,中文按两列计)。
|
|
139
138
|
- 行尾注释与代码之间空两格;`///<` 只接在枚举项和 `struct` 短字段声明行尾。
|
|
140
139
|
- `@param`、`@return`、`@throws` 标签保持英文;`@param` 后先写参数名,再写一句。
|
|
141
140
|
- `TODO:` 后先写定位(阶段名或 issue 号),再写一个短横线,再写动作;不写 `TODO(某人)`,不写没有动作的 TODO。
|
|
142
141
|
- `.clang-format`、`.clang-tidy`、`.clangd`、CMake 的配置项旁边写「为什么这样设」,不写「这一项是什么」;配置注释贴在被解释的键上方。
|
|
143
142
|
- 禁止:大段表格、装饰图案、表情;外部资料路径、跨仓库引用、团队流程;注释掉的代码;注释元叙述(「其余内容见某处」「两处不重复」)。
|
|
144
|
-
-
|
|
145
|
-
- C++ 源文件的说明性注释一律 `///`,实现区不与 `//` 混用;`//`
|
|
143
|
+
- `///` 与 `@param` 不进入 `.qmd`、YAML、CSS。
|
|
144
|
+
- C++ 源文件的说明性注释一律 `///`,实现区不与 `//` 混用;`//` 只留给 `// NOLINTNEXTLINE(...)` 这类工具指令。
|
|
146
145
|
|
|
147
146
|
## 润色:删复述,统一句式
|
|
148
147
|
|
|
@@ -98,5 +98,5 @@ Google 原文不使用 C++ 异常;本工作流只在开发阶段的源码指
|
|
|
98
98
|
## 注释
|
|
99
99
|
|
|
100
100
|
- 注释写意图、契约、取舍,不复述代码。
|
|
101
|
-
-
|
|
101
|
+
- 文件头、声明注释、实现注释与配置注释的格式见 `references/comments.md`。
|
|
102
102
|
- TODO 统一写 `// TODO: <阶段或 issue> - <要做什么>`;不要写 `@todo`、`FIXME` 或只有「参考」没有动作的占位。
|
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
// 只读注释审计:按 references/comments.md 的硬规则检查 C/C++ 源文件。
|
|
3
3
|
//
|
|
4
|
-
//
|
|
4
|
+
// 检查项:Doxygen 文件头、注释行宽度、标签、注释与声明的贴合、
|
|
5
5
|
// 类型注释存在性、成员功能清单、设计论证(方案对比)、否定式边界、领域尺寸、谓词首句、@param 完整性、枚举成员注释一致性、数据成员主注释位置。
|
|
6
6
|
// 不重写任何文件。
|
|
7
7
|
//
|
|
8
8
|
// 用法:
|
|
9
|
-
// node checking-comments.mjs [--root DIR] [--width N] [--
|
|
9
|
+
// node checking-comments.mjs [--root DIR] [--width N] [--no-header] [--json] [--quiet]
|
|
10
10
|
//
|
|
11
11
|
// 退出码:0 无问题,1 发现问题,2 用法或读取错误。
|
|
12
12
|
import { readdirSync, readFileSync, statSync } from 'node:fs'
|
|
@@ -28,7 +28,6 @@ const SKIP_DIR = new Set([
|
|
|
28
28
|
const STATIC_FORBIDDEN_TAGS = ['@returns', '@details', '@since', '@todo']
|
|
29
29
|
const PRIVACY_TAGS = ['@author', '@date', '@copyright', '@version']
|
|
30
30
|
const FORBIDDEN_TAGS = [...STATIC_FORBIDDEN_TAGS, ...PRIVACY_TAGS]
|
|
31
|
-
const BANNER = /^\/\/=+-+=+\/\/$/
|
|
32
31
|
const DOXY_FILE = /^\/\/\/\s*@file\s+\S/
|
|
33
32
|
const DOXY_BRIEF = /^\/\/\/\s*@brief\s+\S/
|
|
34
33
|
const MEMBER_LIST = /提供[^。]{0,40}、[^。]{0,40}(的功能|的能力|的接口)|功能[::]/
|
|
@@ -42,12 +41,12 @@ const DECL_KEYWORDS = /^(class|struct|union|enum|namespace|using|typedef|templat
|
|
|
42
41
|
|
|
43
42
|
function usage(message) {
|
|
44
43
|
if (message) process.stderr.write(`错误:${message}\n`)
|
|
45
|
-
process.stderr.write('用法:node checking-comments.mjs [--root DIR] [--width N] [--
|
|
44
|
+
process.stderr.write('用法:node checking-comments.mjs [--root DIR] [--width N] [--no-header] [--no-brief] [--allow-tag TAG] [--json] [--quiet]\n')
|
|
46
45
|
return EXIT.usage
|
|
47
46
|
}
|
|
48
47
|
|
|
49
48
|
function parseArgs(argv) {
|
|
50
|
-
const options = { root: undefined, width: 80,
|
|
49
|
+
const options = { root: undefined, width: 80, widthExplicit: false, requireHeader: true, requireBrief: true, allowTags: new Set(), json: false, quiet: false }
|
|
51
50
|
for (let index = 0; index < argv.length; index += 1) {
|
|
52
51
|
const arg = argv[index]
|
|
53
52
|
if (arg === '--root') {
|
|
@@ -57,10 +56,6 @@ function parseArgs(argv) {
|
|
|
57
56
|
options.width = Number(argv[index + 1])
|
|
58
57
|
options.widthExplicit = true
|
|
59
58
|
index += 1
|
|
60
|
-
} else if (arg === '--banner-width') {
|
|
61
|
-
options.bannerWidth = Number(argv[index + 1])
|
|
62
|
-
options.bannerWidthExplicit = true
|
|
63
|
-
index += 1
|
|
64
59
|
} else if (arg === '--no-header') {
|
|
65
60
|
options.requireHeader = false
|
|
66
61
|
} else if (arg === '--no-brief') {
|
|
@@ -80,10 +75,8 @@ function parseArgs(argv) {
|
|
|
80
75
|
throw new Error(`未知参数 ${arg}`)
|
|
81
76
|
}
|
|
82
77
|
}
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
throw new Error(`--${key} 需要不小于 40 的整数`)
|
|
86
|
-
}
|
|
78
|
+
if (!Number.isInteger(options.width) || options.width < 40) {
|
|
79
|
+
throw new Error('--width 需要不小于 40 的整数')
|
|
87
80
|
}
|
|
88
81
|
return options
|
|
89
82
|
}
|
|
@@ -208,7 +201,6 @@ function classMemberLines(lines) {
|
|
|
208
201
|
/** 检查一段文本,返回问题列表;每个问题带 1 起始的行号。 */
|
|
209
202
|
export function checkText(text, options = {}) {
|
|
210
203
|
const width = options.width ?? 80
|
|
211
|
-
const bannerWidth = options.bannerWidth ?? 80
|
|
212
204
|
const requireHeader = options.requireHeader ?? true
|
|
213
205
|
const lines = text.split(/\r?\n/)
|
|
214
206
|
const issues = []
|
|
@@ -223,13 +215,12 @@ export function checkText(text, options = {}) {
|
|
|
223
215
|
doxyHeaderEnd = firstContent
|
|
224
216
|
while (doxyHeaderEnd + 1 < lines.length && lines[doxyHeaderEnd + 1].trim().startsWith('///')) doxyHeaderEnd += 1
|
|
225
217
|
}
|
|
226
|
-
if (requireHeader && firstContent !== -1 && !
|
|
218
|
+
if (requireHeader && firstContent !== -1 && !hasDoxyHeader) {
|
|
227
219
|
add(firstContent + 1, 'header-missing', '文件开头缺少文件头:写 /// @file <文件名>,下一行写 /// @brief')
|
|
228
220
|
}
|
|
229
|
-
if (firstContent !== -1 &&
|
|
221
|
+
if (firstContent !== -1 && hasDoxyHeader) {
|
|
230
222
|
const own = options.fileName ? options.fileName.split(/[\\/]/).pop() : ''
|
|
231
|
-
|
|
232
|
-
if (hasDoxyHeader && own) {
|
|
223
|
+
if (own) {
|
|
233
224
|
const declared = /@file\s+(\S+)/.exec(lines[firstContent].trim())
|
|
234
225
|
if (declared && declared[1] !== own) {
|
|
235
226
|
add(firstContent + 1, 'header-file-name', `文件头写的是 ${declared[1]},本文件是 ${own}:@file 必须等于文件名`)
|
|
@@ -240,7 +231,7 @@ export function checkText(text, options = {}) {
|
|
|
240
231
|
}
|
|
241
232
|
for (let index = firstContent + 1; index < lines.length; index += 1) {
|
|
242
233
|
const trimmed = lines[index].trim()
|
|
243
|
-
if (
|
|
234
|
+
if (!trimmed.startsWith('///')) break
|
|
244
235
|
const names = trimmed.match(/\b[A-Za-z0-9_-]+\.(?:h|hpp|cpp|cc)\b/g) ?? []
|
|
245
236
|
const fileMatches = trimmed.match(/@file\s+(\S+)/)
|
|
246
237
|
const namesToCheck = fileMatches ? names.filter((name) => name !== fileMatches[1]) : names
|
|
@@ -254,11 +245,6 @@ export function checkText(text, options = {}) {
|
|
|
254
245
|
for (let index = 0; index < lines.length; index += 1) {
|
|
255
246
|
const line = lines[index]
|
|
256
247
|
const trimmed = line.trim()
|
|
257
|
-
if (BANNER.test(trimmed)) {
|
|
258
|
-
const actual = displayWidth(trimmed)
|
|
259
|
-
if (actual !== bannerWidth) add(index + 1, 'banner-width', `横幅宽度 ${actual} 列,应为 ${bannerWidth} 列`)
|
|
260
|
-
continue
|
|
261
|
-
}
|
|
262
248
|
if (trimmed.startsWith('///') || (trimmed.startsWith('//') && !trimmed.startsWith('//!'))) {
|
|
263
249
|
if (displayWidth(trimmed) > width) add(index + 1, 'comment-width', `注释行 ${displayWidth(trimmed)} 列,超过 ${width} 列`)
|
|
264
250
|
for (const tag of FORBIDDEN_TAGS) {
|
|
@@ -453,7 +439,6 @@ function main(argv) {
|
|
|
453
439
|
const columnLimit = readColumnLimit(root)
|
|
454
440
|
if (columnLimit !== undefined) {
|
|
455
441
|
if (!options.widthExplicit) options.width = columnLimit
|
|
456
|
-
if (!options.bannerWidthExplicit) options.bannerWidth = columnLimit
|
|
457
442
|
}
|
|
458
443
|
let files
|
|
459
444
|
try {
|
|
@@ -24,7 +24,7 @@
|
|
|
24
24
|
|
|
25
25
|
**异常声明**:构造函数、`Get` 类查询、解析函数最容易抛;在声明注释里写「当……时抛出 `std::out_of_range`」这样的具体类型,不要只写「可能抛异常」,也不要给每个参数错误都新建类型。类型选择见 `references/exceptions.md`。
|
|
26
26
|
|
|
27
|
-
**注释**:声明注释用 `///` 写中文散文,只写调用方需要知道的;实现细节留给 `.cpp`。`@param`、`@return`、`@throws` 要么全写要么全不写,标识符和标签保持英文。文件头默认写 `@file` 与 `@brief` 两行,`@file` 必须与文件名相同;作者、日期、版权、版本默认不写。标签开关在插件设置页,技能正文末尾附带当前配置的快照。写完运行 `node "<skill-dir>/scripts/checking-comments.mjs" --root <项目根>`;关闭 `@brief` 时加 `--no-brief`,配置里打开了隐私标签并填了值时加 `--allow-tag
|
|
27
|
+
**注释**:声明注释用 `///` 写中文散文,只写调用方需要知道的;实现细节留给 `.cpp`。`@param`、`@return`、`@throws` 要么全写要么全不写,标识符和标签保持英文。文件头默认写 `@file` 与 `@brief` 两行,`@file` 必须与文件名相同;作者、日期、版权、版本默认不写。标签开关在插件设置页,技能正文末尾附带当前配置的快照。写完运行 `node "<skill-dir>/scripts/checking-comments.mjs" --root <项目根>`;关闭 `@brief` 时加 `--no-brief`,配置里打开了隐私标签并填了值时加 `--allow-tag <标签>`。文件头、标签、参数完整性、成员清单与设计论证由脚本兜底。文件头与 TODO 的格式见 `references/comments.md`。
|
|
28
28
|
|
|
29
29
|
## 正反例指针
|
|
30
30
|
|
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
- 函数保持短小;一个函数只做一件事。超过约 40 行时先找可以命名的中间步骤,不要用注释把长函数切成几段。
|
|
10
10
|
- 按「准备 → 操作 → 收尾」排列语句,用空行分组;组与组之间的空行表达阶段,不用大段叙述。
|
|
11
11
|
- `.cpp` 的第一个 include 是自己的配对头,然后直接包含本文件用到的标准库头;不要依赖公共头替本文件转发声明。包含顺序见 `references/google-cpp-style.md`;文件槽位见 `references/file-template.md`。
|
|
12
|
-
- `.cpp` / `.cc` 里,重要函数按空行分阶段,每个阶段上方写一句
|
|
12
|
+
- `.cpp` / `.cc` 里,重要函数按空行分阶段,每个阶段上方写一句 `///`:写边界、失败后果或为什么停在这里,不把下一行的条件翻译一遍。`IsWin` 这种单行函数不配注释;只写删掉会让下一次修改得到错误结果、而代码本身看不出的内容,变更史留给提交说明。
|
|
13
13
|
- 错误处理:失败是调用方正常结果时返回结果类型、`bool` 或 `std::optional`;函数无法完成自己的任务时抛异常,不拿异常做正常分支。Debug 与 Release 用同一套返回值与抛出条件,只有诊断详略不同。两种模式都要守的 invariant 用 `Check`(项目保留时),只在开发期做的自检用 `DebugCheck`;没有这两个宏就分别用 `assert` 与 `std::terminate`。捕获时按 `const` 引用,只处理能处理的类型,其余继续抛。消息按 `Type::Function: 条件 (值); 限度` 组织,见 `references/exceptions.md` 的「消息」节。
|
|
14
14
|
- 声明 `noexcept` 前核对函数体:出现 `at()` 或其他可能抛出的调用就去掉。越界用 `at()` 抛 `std::out_of_range`,这个函数就不能标 `noexcept`。
|
|
15
15
|
- 组合输出用 `std::format`,再写入 `std::cout` / `std::cerr`;不要在库实现里直接打印。C++23 且库支持时程序边界可用 `std::print`,C++20 及更早没有。
|