@chengzhao-dev/dsh-cpp-workflow 0.6.1 → 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 +227 -229
- package/lib/comment-policy.js +9 -7
- 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
|
@@ -58,7 +58,9 @@ const TAG_LABEL = {
|
|
|
58
58
|
retval: '@retval',
|
|
59
59
|
};
|
|
60
60
|
/** 预览里的示例摘要:用户没填 @brief 时用它,避免预览出现空标签。 */
|
|
61
|
-
const SAMPLE_BRIEF = '
|
|
61
|
+
const SAMPLE_BRIEF = '<一句话说明本文件>';
|
|
62
|
+
/** 预览里的示例文件名:只是占位符,不对应任何工程的真实文件。 */
|
|
63
|
+
const SAMPLE_FILE = '<文件名>';
|
|
62
64
|
/** 出厂配置:文件头只留 @file 与 @brief,契约标签全开,隐私标签全关。 */
|
|
63
65
|
const DEFAULT_CONFIG = {
|
|
64
66
|
brief: { enabled: true, value: '' },
|
|
@@ -105,7 +107,7 @@ function normalizeConfig(raw) {
|
|
|
105
107
|
*/
|
|
106
108
|
function renderFileHeader(raw, fileName) {
|
|
107
109
|
const config = normalizeConfig(raw);
|
|
108
|
-
const name = text(fileName) ||
|
|
110
|
+
const name = text(fileName) || SAMPLE_FILE;
|
|
109
111
|
const lines = [`/// @file ${name}`];
|
|
110
112
|
if (config.brief.enabled) {
|
|
111
113
|
lines.push(config.brief.value ? `/// @brief ${config.brief.value}` : '/// @brief');
|
|
@@ -121,19 +123,19 @@ function renderFileHeader(raw, fileName) {
|
|
|
121
123
|
*/
|
|
122
124
|
function previewParts(raw, sampleName) {
|
|
123
125
|
const config = normalizeConfig(raw);
|
|
124
|
-
const header = renderFileHeader(config, sampleName ||
|
|
126
|
+
const header = renderFileHeader(config, sampleName || SAMPLE_FILE);
|
|
125
127
|
// 预览是示例:@brief 开着但用户没填句子时,用示例文件自己的摘要顶替。
|
|
126
128
|
const briefIndex = header.indexOf('/// @brief');
|
|
127
129
|
if (briefIndex !== -1)
|
|
128
130
|
header[briefIndex] = `/// @brief ${SAMPLE_BRIEF}`;
|
|
129
131
|
const contract = [];
|
|
130
132
|
if (config.param)
|
|
131
|
-
contract.push('/// @param
|
|
133
|
+
contract.push('/// @param row 目标行号。');
|
|
132
134
|
if (config.returns)
|
|
133
135
|
contract.push('/// @return 放置成功时为 true。');
|
|
134
136
|
if (config.throws)
|
|
135
|
-
contract.push('/// @throws std::out_of_range
|
|
136
|
-
const declaration = '[[nodiscard]] bool
|
|
137
|
+
contract.push('/// @throws std::out_of_range 行号越界时抛出。');
|
|
138
|
+
const declaration = '[[nodiscard]] bool Place(int row, int column);';
|
|
137
139
|
return { header, contract, declaration };
|
|
138
140
|
}
|
|
139
141
|
/**
|
|
@@ -141,7 +143,7 @@ function previewParts(raw, sampleName) {
|
|
|
141
143
|
*/
|
|
142
144
|
function previewText(raw, sampleName) {
|
|
143
145
|
const { header, contract, declaration } = previewParts(raw, sampleName);
|
|
144
|
-
const lines = [...header, '///', '///
|
|
146
|
+
const lines = [...header, '///', '/// 放置成功时返回 true;行号越界或目标格子已占时返回 false。'];
|
|
145
147
|
if (contract.length > 0)
|
|
146
148
|
lines.push('///', ...contract);
|
|
147
149
|
lines.push(declaration);
|
|
@@ -193,27 +195,28 @@ function snapshotText(raw) {
|
|
|
193
195
|
// 客户端半侧:设置导航里的「C++ 工程流程」页与插件页的配置卡片共用同一个组件。
|
|
194
196
|
// 标签策略在本文件上方内联(由 scripts/building-client.mjs 从 src/comment-policy.js
|
|
195
197
|
// 拼进来),所以预览、默认值与服务端快照永远同源;这里只做表单与排版。
|
|
198
|
+
//
|
|
199
|
+
// 版式参照 VS Code 设置页:左侧标签与说明、右侧控件,行间细分隔线;
|
|
200
|
+
// 修改过默认值的行在标签前加一个圆点,底部固定一条保存栏。
|
|
196
201
|
const React = require('react')
|
|
197
202
|
const h = React.createElement
|
|
198
203
|
|
|
199
204
|
const PACKAGE_NAME = '@chengzhao-dev/dsh-cpp-workflow'
|
|
200
|
-
const SAMPLE_FILE = 'board.h'
|
|
201
205
|
|
|
202
206
|
const STRINGS = {
|
|
203
207
|
zh: {
|
|
204
208
|
nav: 'C++ 工程流程',
|
|
205
209
|
title: '文档注释',
|
|
206
|
-
intro: '
|
|
210
|
+
intro: '设置文件头与函数声明注释里默认写出的 Doxygen 标签;改动影响之后写出的注释,已有源码保持原样。',
|
|
207
211
|
header: '文件头',
|
|
208
|
-
headerHint: '每个手写源文件开头的一段
|
|
212
|
+
headerHint: '每个手写源文件开头的一段 /// 注释。',
|
|
209
213
|
contract: '函数契约',
|
|
210
|
-
contractHint: '
|
|
214
|
+
contractHint: '函数声明注释里要求的参数与失败信息。',
|
|
211
215
|
extra: '更多标签',
|
|
212
|
-
extraHint: '
|
|
216
|
+
extraHint: '需要时在单个函数上补写。',
|
|
213
217
|
file: '@file',
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
briefHint: '一句话说明本文件定义或实现什么;留空则由每个文件自己写。',
|
|
218
|
+
fileHint: '取自当前文件名。',
|
|
219
|
+
briefHint: '一句话说明这个文件定义或实现什么;留空则由文件自述。',
|
|
217
220
|
authorHint: '作者署名;留空不写。',
|
|
218
221
|
dateHint: '创建或修改日期;留空不写。',
|
|
219
222
|
copyrightHint: '版权声明;留空不写。',
|
|
@@ -221,7 +224,7 @@ const STRINGS = {
|
|
|
221
224
|
emptyValue: '留空不写',
|
|
222
225
|
briefEmpty: '留空则由文件自述',
|
|
223
226
|
preview: '预览',
|
|
224
|
-
previewHint: '
|
|
227
|
+
previewHint: '按当前开关生成;文件名以占位符表示。',
|
|
225
228
|
save: '保存',
|
|
226
229
|
reset: '重置',
|
|
227
230
|
saved: '已保存,下一次读取技能时生效。',
|
|
@@ -229,24 +232,23 @@ const STRINGS = {
|
|
|
229
232
|
unsaved: '有未保存的改动。',
|
|
230
233
|
loading: '正在读取配置…',
|
|
231
234
|
unreadable: '当前账户不能修改这份配置。',
|
|
232
|
-
readOnly: '
|
|
235
|
+
readOnly: '此处显示出厂值;修改在「插件」页的配置卡片里进行。',
|
|
233
236
|
openSettings: '打开插件配置',
|
|
234
|
-
|
|
237
|
+
modified: '与出厂值不同',
|
|
235
238
|
},
|
|
236
239
|
en: {
|
|
237
240
|
nav: 'C++ Workflow',
|
|
238
241
|
title: 'Documentation comments',
|
|
239
|
-
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.',
|
|
240
243
|
header: 'File header',
|
|
241
|
-
headerHint: 'The /// block at the top of every hand-written source
|
|
244
|
+
headerHint: 'The /// block at the top of every hand-written source.',
|
|
242
245
|
contract: 'Function contract',
|
|
243
|
-
contractHint: 'Parameter and failure tags in declaration comments
|
|
246
|
+
contractHint: 'Parameter and failure tags required in declaration comments.',
|
|
244
247
|
extra: 'More tags',
|
|
245
|
-
extraHint: '
|
|
248
|
+
extraHint: 'Add them per function when needed.',
|
|
246
249
|
file: '@file',
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
briefHint: 'One sentence about what this file defines; leave empty and each file writes its own.',
|
|
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.',
|
|
250
252
|
authorHint: 'Author credit; left empty, omitted.',
|
|
251
253
|
dateHint: 'Creation or change date; left empty, omitted.',
|
|
252
254
|
copyrightHint: 'Copyright notice; left empty, omitted.',
|
|
@@ -254,7 +256,7 @@ const STRINGS = {
|
|
|
254
256
|
emptyValue: 'Empty means omit',
|
|
255
257
|
briefEmpty: 'Empty: the file writes its own',
|
|
256
258
|
preview: 'Preview',
|
|
257
|
-
previewHint: '
|
|
259
|
+
previewHint: 'Generated from the switches; the file name is a placeholder.',
|
|
258
260
|
save: 'Save',
|
|
259
261
|
reset: 'Reset',
|
|
260
262
|
saved: 'Saved; the next skill read uses it.',
|
|
@@ -262,9 +264,9 @@ const STRINGS = {
|
|
|
262
264
|
unsaved: 'Unsaved changes.',
|
|
263
265
|
loading: 'Reading configuration…',
|
|
264
266
|
unreadable: 'This account cannot edit the configuration.',
|
|
265
|
-
readOnly: '
|
|
267
|
+
readOnly: 'Factory values are shown here; edit them on the Plugins page.',
|
|
266
268
|
openSettings: 'Open plugin settings',
|
|
267
|
-
|
|
269
|
+
modified: 'Differs from the factory value',
|
|
268
270
|
},
|
|
269
271
|
}
|
|
270
272
|
|
|
@@ -280,7 +282,6 @@ function t() {
|
|
|
280
282
|
const palette = {
|
|
281
283
|
label: 'var(--dsw-alias-label-primary, inherit)',
|
|
282
284
|
muted: 'var(--dsw-alias-label-secondary, #6b7280)',
|
|
283
|
-
faint: 'var(--dsw-alias-label-tertiary, #8b93a1)',
|
|
284
285
|
line: 'var(--dsw-alias-border-l2, rgba(127, 127, 127, 0.24))',
|
|
285
286
|
surface: 'var(--dsw-alias-bg-layer-2, rgba(127, 127, 127, 0.06))',
|
|
286
287
|
field: 'var(--dsw-alias-bg-layer-1, rgba(127, 127, 127, 0.04))',
|
|
@@ -288,115 +289,143 @@ const palette = {
|
|
|
288
289
|
mono: 'ui-monospace, SFMono-Regular, Menlo, Consolas, monospace',
|
|
289
290
|
}
|
|
290
291
|
|
|
291
|
-
const
|
|
292
|
-
const
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
})
|
|
306
|
-
|
|
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
|
+
)
|
|
309
|
+
}
|
|
310
|
+
|
|
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
|
+
)
|
|
348
|
+
}
|
|
307
349
|
|
|
308
|
-
function
|
|
350
|
+
function Checkbox(props) {
|
|
309
351
|
return h(
|
|
310
352
|
'button',
|
|
311
353
|
{
|
|
312
354
|
type: 'button',
|
|
313
|
-
role: '
|
|
355
|
+
role: 'checkbox',
|
|
314
356
|
'aria-checked': Boolean(props.checked),
|
|
315
357
|
'aria-label': props.label,
|
|
316
358
|
disabled: Boolean(props.disabled),
|
|
317
359
|
onClick: () => props.onChange(!props.checked),
|
|
318
360
|
style: {
|
|
319
361
|
flex: 'none',
|
|
320
|
-
width:
|
|
321
|
-
height:
|
|
362
|
+
width: 16,
|
|
363
|
+
height: 16,
|
|
322
364
|
padding: 0,
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
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',
|
|
326
371
|
cursor: props.disabled ? 'default' : 'pointer',
|
|
327
|
-
opacity: props.disabled ? 0.
|
|
328
|
-
|
|
329
|
-
transition: 'background-color 0.15s ease',
|
|
372
|
+
opacity: props.disabled ? 0.55 : 1,
|
|
373
|
+
transition: 'background-color 0.12s ease, border-color 0.12s ease',
|
|
330
374
|
},
|
|
331
375
|
},
|
|
332
|
-
h('span', {
|
|
333
|
-
'aria-hidden': 'true',
|
|
334
|
-
style: {
|
|
335
|
-
position: 'absolute',
|
|
336
|
-
top: 2,
|
|
337
|
-
left: props.checked ? 16 : 2,
|
|
338
|
-
width: 16,
|
|
339
|
-
height: 16,
|
|
340
|
-
borderRadius: '50%',
|
|
341
|
-
background: '#fff',
|
|
342
|
-
boxShadow: '0 1px 2px rgba(0, 0, 0, 0.25)',
|
|
343
|
-
transition: 'left 0.15s ease',
|
|
344
|
-
},
|
|
345
|
-
}),
|
|
346
|
-
)
|
|
347
|
-
}
|
|
348
|
-
|
|
349
|
-
function TagRow(props) {
|
|
350
|
-
const s = props.strings
|
|
351
|
-
return [
|
|
352
|
-
h('div', { key: `${props.tag}-label`, style: labelCellStyle() }, props.label),
|
|
353
376
|
h(
|
|
354
|
-
'
|
|
355
|
-
{
|
|
356
|
-
h(
|
|
357
|
-
'
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
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
|
+
}),
|
|
362
388
|
),
|
|
363
|
-
|
|
389
|
+
)
|
|
364
390
|
}
|
|
365
391
|
|
|
366
392
|
function ValueRow(props) {
|
|
367
393
|
const s = props.strings
|
|
368
|
-
|
|
369
|
-
|
|
394
|
+
const entry = props.entry
|
|
395
|
+
return h(Row, {
|
|
370
396
|
label: TAG_LABEL[props.tag],
|
|
371
|
-
|
|
372
|
-
|
|
397
|
+
mono: true,
|
|
398
|
+
description: props.hint,
|
|
399
|
+
modified: props.modified,
|
|
400
|
+
modifiedLabel: s.modified,
|
|
373
401
|
control: [
|
|
374
|
-
h(
|
|
375
|
-
key: '
|
|
376
|
-
label:
|
|
377
|
-
checked:
|
|
402
|
+
h(Checkbox, {
|
|
403
|
+
key: 'check',
|
|
404
|
+
label: TAG_LABEL[props.tag],
|
|
405
|
+
checked: entry.enabled,
|
|
378
406
|
disabled: props.disabled,
|
|
379
|
-
onChange: (enabled) =>
|
|
407
|
+
onChange: (enabled) => props.onChange({ ...entry, enabled }),
|
|
380
408
|
}),
|
|
381
409
|
h('input', {
|
|
382
410
|
key: 'input',
|
|
383
411
|
type: 'text',
|
|
384
|
-
value:
|
|
412
|
+
value: entry.value,
|
|
385
413
|
placeholder: props.tag === 'brief' ? s.briefEmpty : s.emptyValue,
|
|
386
|
-
disabled: props.disabled || !
|
|
414
|
+
disabled: props.disabled || !entry.enabled,
|
|
387
415
|
'aria-label': TAG_LABEL[props.tag],
|
|
388
|
-
onChange: (event) => props.onChange({ ...
|
|
416
|
+
onChange: (event) => props.onChange({ ...entry, value: event.target.value }),
|
|
389
417
|
style: {
|
|
390
|
-
|
|
418
|
+
width: '100%',
|
|
391
419
|
minWidth: 0,
|
|
392
|
-
maxWidth: 320,
|
|
393
420
|
fontSize: 13,
|
|
394
|
-
|
|
395
|
-
|
|
421
|
+
lineHeight: '18px',
|
|
422
|
+
padding: '3px 7px',
|
|
423
|
+
borderRadius: 4,
|
|
396
424
|
border: `1px solid ${palette.line}`,
|
|
397
|
-
background:
|
|
398
|
-
color:
|
|
425
|
+
background: entry.enabled && !props.disabled ? palette.field : 'transparent',
|
|
426
|
+
color: entry.enabled ? 'inherit' : palette.muted,
|
|
399
427
|
fontFamily: palette.mono,
|
|
428
|
+
boxSizing: 'border-box',
|
|
400
429
|
},
|
|
401
430
|
}),
|
|
402
431
|
],
|
|
@@ -404,16 +433,17 @@ function ValueRow(props) {
|
|
|
404
433
|
}
|
|
405
434
|
|
|
406
435
|
function FlagRow(props) {
|
|
407
|
-
return h(
|
|
408
|
-
tag: props.tag,
|
|
436
|
+
return h(Row, {
|
|
409
437
|
label: TAG_LABEL[props.tag],
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
438
|
+
mono: true,
|
|
439
|
+
description: props.hint,
|
|
440
|
+
modified: props.modified,
|
|
441
|
+
modifiedLabel: props.strings.modified,
|
|
442
|
+
control: h(Checkbox, {
|
|
413
443
|
label: TAG_LABEL[props.tag],
|
|
414
444
|
checked: Boolean(props.checked),
|
|
415
445
|
disabled: props.disabled,
|
|
416
|
-
onChange: (value) =>
|
|
446
|
+
onChange: (value) => props.onChange(value),
|
|
417
447
|
}),
|
|
418
448
|
})
|
|
419
449
|
}
|
|
@@ -422,16 +452,16 @@ function Preview(props) {
|
|
|
422
452
|
const s = props.strings
|
|
423
453
|
return h(
|
|
424
454
|
'section',
|
|
425
|
-
{ 'aria-label': s.preview, style: { marginTop:
|
|
426
|
-
h('h4', { style:
|
|
427
|
-
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),
|
|
428
458
|
h(
|
|
429
459
|
'pre',
|
|
430
460
|
{
|
|
431
461
|
style: {
|
|
432
462
|
margin: '8px 0 0',
|
|
433
463
|
padding: '10px 12px',
|
|
434
|
-
borderRadius:
|
|
464
|
+
borderRadius: 6,
|
|
435
465
|
background: palette.surface,
|
|
436
466
|
border: `1px solid ${palette.line}`,
|
|
437
467
|
overflowX: 'auto',
|
|
@@ -442,7 +472,7 @@ function Preview(props) {
|
|
|
442
472
|
color: palette.label,
|
|
443
473
|
},
|
|
444
474
|
},
|
|
445
|
-
previewText(props.config
|
|
475
|
+
previewText(props.config),
|
|
446
476
|
),
|
|
447
477
|
)
|
|
448
478
|
}
|
|
@@ -457,6 +487,10 @@ function diffOperations(draft, base) {
|
|
|
457
487
|
return operations
|
|
458
488
|
}
|
|
459
489
|
|
|
490
|
+
function isModified(key, draft) {
|
|
491
|
+
return JSON.stringify(draft[key]) !== JSON.stringify(DEFAULT_CONFIG[key])
|
|
492
|
+
}
|
|
493
|
+
|
|
460
494
|
function ConfigPage(props) {
|
|
461
495
|
const s = t()
|
|
462
496
|
const form = props.form
|
|
@@ -511,133 +545,97 @@ function ConfigPage(props) {
|
|
|
511
545
|
}
|
|
512
546
|
|
|
513
547
|
const editable = Boolean(form)
|
|
514
|
-
const
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
]
|
|
522
|
-
const contractHints = { param: TAG_TEXT.param, returns: TAG_TEXT.returns, throws: TAG_TEXT.throws }
|
|
523
|
-
const extraHints = { note: TAG_TEXT.note, see: TAG_TEXT.see, pre: TAG_TEXT.pre, post: TAG_TEXT.post, tparam: TAG_TEXT.tparam, retval: TAG_TEXT.retval }
|
|
548
|
+
const headerHints = {
|
|
549
|
+
brief: s.briefHint,
|
|
550
|
+
author: s.authorHint,
|
|
551
|
+
date: s.dateHint,
|
|
552
|
+
copyright: s.copyrightHint,
|
|
553
|
+
version: s.versionHint,
|
|
554
|
+
}
|
|
524
555
|
|
|
525
556
|
return h(
|
|
526
557
|
'div',
|
|
527
|
-
{ style: { display: 'flex', flexDirection: 'column', padding: '
|
|
528
|
-
h('h3', { style: { margin: '0 0
|
|
529
|
-
h('p', { style:
|
|
558
|
+
{ style: { display: 'flex', flexDirection: 'column', padding: '2px 0 8px', maxWidth: 760, color: palette.label } },
|
|
559
|
+
h('h3', { style: { margin: '0 0 2px', fontSize: 15, fontWeight: 600 } }, s.title),
|
|
560
|
+
h('p', { style: descriptionStyle() }, s.intro),
|
|
530
561
|
!editable
|
|
531
562
|
? h(
|
|
532
563
|
'div',
|
|
533
|
-
{
|
|
534
|
-
|
|
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),
|
|
535
579
|
props.openPluginPage
|
|
536
580
|
? h('button', { type: 'button', onClick: props.openPluginPage, style: secondaryButton(true) }, s.openSettings)
|
|
537
581
|
: null,
|
|
538
582
|
)
|
|
539
583
|
: null,
|
|
540
584
|
editable && !ready && status !== 'missing'
|
|
541
|
-
? h('p', { role: 'status', style:
|
|
585
|
+
? h('p', { role: 'status', style: descriptionStyle() }, status === 'loading' ? s.loading : s.unreadable)
|
|
542
586
|
: null,
|
|
543
587
|
h(
|
|
544
|
-
|
|
545
|
-
{
|
|
546
|
-
h(
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
'span',
|
|
564
|
-
{
|
|
565
|
-
style: {
|
|
566
|
-
fontSize: 12,
|
|
567
|
-
lineHeight: '20px',
|
|
568
|
-
padding: '0 8px',
|
|
569
|
-
borderRadius: 999,
|
|
570
|
-
border: `1px solid ${palette.line}`,
|
|
571
|
-
color: palette.muted,
|
|
572
|
-
},
|
|
573
|
-
},
|
|
574
|
-
s.fixed,
|
|
575
|
-
),
|
|
576
|
-
h('code', { style: { fontSize: 12.5, fontFamily: palette.mono, color: palette.muted } }, SAMPLE_FILE),
|
|
577
|
-
),
|
|
578
|
-
h('p', { style: hintStyle() }, s.fileHint),
|
|
579
|
-
),
|
|
580
|
-
...header.flatMap(({ tag, hint }) =>
|
|
581
|
-
h(ValueRow, {
|
|
582
|
-
key: tag,
|
|
583
|
-
tag,
|
|
584
|
-
hint,
|
|
585
|
-
strings: s,
|
|
586
|
-
disabled: !editable,
|
|
587
|
-
entry: draft[tag],
|
|
588
|
-
onChange: (entry) => update({ [tag]: entry }),
|
|
589
|
-
}),
|
|
590
|
-
),
|
|
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, {
|
|
598
|
+
key: tag,
|
|
599
|
+
tag,
|
|
600
|
+
hint: headerHints[tag],
|
|
601
|
+
strings: s,
|
|
602
|
+
disabled: !editable,
|
|
603
|
+
modified: editable && isModified(tag, draft),
|
|
604
|
+
entry: draft[tag],
|
|
605
|
+
onChange: (entry) => update({ [tag]: entry }),
|
|
606
|
+
}),
|
|
591
607
|
),
|
|
592
608
|
),
|
|
593
609
|
h(
|
|
594
|
-
|
|
595
|
-
{
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
disabled: !editable,
|
|
608
|
-
checked: draft[tag],
|
|
609
|
-
onChange: (value) => update({ [tag]: value }),
|
|
610
|
-
}),
|
|
611
|
-
),
|
|
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
|
+
}),
|
|
612
623
|
),
|
|
613
624
|
),
|
|
614
625
|
h(
|
|
615
|
-
|
|
616
|
-
{
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
{ role: 'group', 'aria-label': s.extra, style: { ...gridStyle(), marginTop: 8 } },
|
|
629
|
-
...EXTRA_TAGS.flatMap((tag) =>
|
|
630
|
-
h(FlagRow, {
|
|
631
|
-
key: tag,
|
|
632
|
-
tag,
|
|
633
|
-
hint: extraHints[tag],
|
|
634
|
-
strings: s,
|
|
635
|
-
disabled: !editable,
|
|
636
|
-
checked: draft[tag],
|
|
637
|
-
onChange: (value) => update({ [tag]: value }),
|
|
638
|
-
}),
|
|
639
|
-
),
|
|
640
|
-
),
|
|
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
|
+
}),
|
|
641
639
|
),
|
|
642
640
|
),
|
|
643
641
|
h(Preview, { config: draft, strings: s }),
|
|
@@ -649,7 +647,7 @@ function ConfigPage(props) {
|
|
|
649
647
|
display: 'flex',
|
|
650
648
|
alignItems: 'center',
|
|
651
649
|
gap: 10,
|
|
652
|
-
marginTop:
|
|
650
|
+
marginTop: 20,
|
|
653
651
|
paddingTop: 12,
|
|
654
652
|
borderTop: `1px solid ${palette.line}`,
|
|
655
653
|
flexWrap: 'wrap',
|
|
@@ -660,8 +658,8 @@ function ConfigPage(props) {
|
|
|
660
658
|
{
|
|
661
659
|
type: 'button',
|
|
662
660
|
onClick: save,
|
|
663
|
-
disabled: busy || !
|
|
664
|
-
style: primaryButton(dirty &&
|
|
661
|
+
disabled: busy || !ready || !dirty,
|
|
662
|
+
style: primaryButton(dirty && ready && !busy),
|
|
665
663
|
},
|
|
666
664
|
s.save,
|
|
667
665
|
),
|
|
@@ -675,7 +673,7 @@ function ConfigPage(props) {
|
|
|
675
673
|
},
|
|
676
674
|
s.reset,
|
|
677
675
|
),
|
|
678
|
-
h('p', { role: 'status', style: { ...
|
|
676
|
+
h('p', { role: 'status', style: { ...descriptionStyle(), margin: 0, flex: '1 1 200px' } }, message || (dirty ? s.unsaved : '')),
|
|
679
677
|
)
|
|
680
678
|
: null,
|
|
681
679
|
)
|
|
@@ -685,8 +683,8 @@ function primaryButton(enabled) {
|
|
|
685
683
|
return {
|
|
686
684
|
fontSize: 13,
|
|
687
685
|
lineHeight: '20px',
|
|
688
|
-
padding: '
|
|
689
|
-
borderRadius:
|
|
686
|
+
padding: '4px 12px',
|
|
687
|
+
borderRadius: 4,
|
|
690
688
|
border: '1px solid transparent',
|
|
691
689
|
background: enabled ? palette.brand : 'var(--dsw-alias-bg-layer-3, rgba(127, 127, 127, 0.2))',
|
|
692
690
|
color: enabled ? '#fff' : palette.muted,
|
|
@@ -698,8 +696,8 @@ function secondaryButton(enabled) {
|
|
|
698
696
|
return {
|
|
699
697
|
fontSize: 13,
|
|
700
698
|
lineHeight: '20px',
|
|
701
|
-
padding: '
|
|
702
|
-
borderRadius:
|
|
699
|
+
padding: '4px 12px',
|
|
700
|
+
borderRadius: 4,
|
|
703
701
|
border: `1px solid ${palette.line}`,
|
|
704
702
|
background: 'transparent',
|
|
705
703
|
color: enabled ? palette.label : palette.muted,
|
package/lib/comment-policy.js
CHANGED
|
@@ -51,7 +51,9 @@ export const TAG_LABEL = {
|
|
|
51
51
|
retval: '@retval',
|
|
52
52
|
};
|
|
53
53
|
/** 预览里的示例摘要:用户没填 @brief 时用它,避免预览出现空标签。 */
|
|
54
|
-
export const SAMPLE_BRIEF = '
|
|
54
|
+
export const SAMPLE_BRIEF = '<一句话说明本文件>';
|
|
55
|
+
/** 预览里的示例文件名:只是占位符,不对应任何工程的真实文件。 */
|
|
56
|
+
export const SAMPLE_FILE = '<文件名>';
|
|
55
57
|
/** 出厂配置:文件头只留 @file 与 @brief,契约标签全开,隐私标签全关。 */
|
|
56
58
|
export const DEFAULT_CONFIG = {
|
|
57
59
|
brief: { enabled: true, value: '' },
|
|
@@ -98,7 +100,7 @@ export function normalizeConfig(raw) {
|
|
|
98
100
|
*/
|
|
99
101
|
export function renderFileHeader(raw, fileName) {
|
|
100
102
|
const config = normalizeConfig(raw);
|
|
101
|
-
const name = text(fileName) ||
|
|
103
|
+
const name = text(fileName) || SAMPLE_FILE;
|
|
102
104
|
const lines = [`/// @file ${name}`];
|
|
103
105
|
if (config.brief.enabled) {
|
|
104
106
|
lines.push(config.brief.value ? `/// @brief ${config.brief.value}` : '/// @brief');
|
|
@@ -114,19 +116,19 @@ export function renderFileHeader(raw, fileName) {
|
|
|
114
116
|
*/
|
|
115
117
|
export function previewParts(raw, sampleName) {
|
|
116
118
|
const config = normalizeConfig(raw);
|
|
117
|
-
const header = renderFileHeader(config, sampleName ||
|
|
119
|
+
const header = renderFileHeader(config, sampleName || SAMPLE_FILE);
|
|
118
120
|
// 预览是示例:@brief 开着但用户没填句子时,用示例文件自己的摘要顶替。
|
|
119
121
|
const briefIndex = header.indexOf('/// @brief');
|
|
120
122
|
if (briefIndex !== -1)
|
|
121
123
|
header[briefIndex] = `/// @brief ${SAMPLE_BRIEF}`;
|
|
122
124
|
const contract = [];
|
|
123
125
|
if (config.param)
|
|
124
|
-
contract.push('/// @param
|
|
126
|
+
contract.push('/// @param row 目标行号。');
|
|
125
127
|
if (config.returns)
|
|
126
128
|
contract.push('/// @return 放置成功时为 true。');
|
|
127
129
|
if (config.throws)
|
|
128
|
-
contract.push('/// @throws std::out_of_range
|
|
129
|
-
const declaration = '[[nodiscard]] bool
|
|
130
|
+
contract.push('/// @throws std::out_of_range 行号越界时抛出。');
|
|
131
|
+
const declaration = '[[nodiscard]] bool Place(int row, int column);';
|
|
130
132
|
return { header, contract, declaration };
|
|
131
133
|
}
|
|
132
134
|
/**
|
|
@@ -134,7 +136,7 @@ export function previewParts(raw, sampleName) {
|
|
|
134
136
|
*/
|
|
135
137
|
export function previewText(raw, sampleName) {
|
|
136
138
|
const { header, contract, declaration } = previewParts(raw, sampleName);
|
|
137
|
-
const lines = [...header, '///', '///
|
|
139
|
+
const lines = [...header, '///', '/// 放置成功时返回 true;行号越界或目标格子已占时返回 false。'];
|
|
138
140
|
if (contract.length > 0)
|
|
139
141
|
lines.push('///', ...contract);
|
|
140
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 及更早没有。
|