🐛 fix(vscode): 写注释时改已有说明,不再在声明上面另插一行
This commit is contained in:
1 parent
63dfb658e9
commit
7f39345d03
11 files changed
+196
-36
No files matched your search
@@ -435,7 +435,7 @@ describe('卡片上的「注释」一行', () => {
|
||||
const [, load] = await views(ts, 'typescript', typescriptZhTranslator, source)
|
||||
expect('properties' in load && load.properties.map(row => row.label).slice(0, 4)).toEqual(['名称', '说明', '注释', '访问级别'])
|
||||
expect(commentText(load)).toBe('讲 load\n\n第二段')
|
||||
expect(load).toMatchObject({ startLine: 2, commentTarget: { kind: 'replace', startLine: 2, endLine: 4 } })
|
||||
expect(load).toMatchObject({ startLine: 2, commentTarget: { kind: 'replace', startLine: 5, endLine: 5 } })
|
||||
const [check, returned] = load.children
|
||||
expect(check).toMatchObject({ kind: 'if', startLine: 7, rows: [{ label: '注释', value: { markdown: '先检查' } }] })
|
||||
expect(returned).toMatchObject({ kind: 'return', commentTarget: { kind: 'insert', line: 11 } })
|
||||
@@ -449,7 +449,7 @@ describe('卡片上的「注释」一行', () => {
|
||||
embedded: languageId => languageId === 'shellscript' ? { adapter: shell, translator: bashZhTranslator } : undefined,
|
||||
})
|
||||
const run = flatten(blocks).find(view => view.kind === 'run')
|
||||
expect(run?.commentTarget).toEqual({ kind: 'insert', line: 1 })
|
||||
expect(run?.commentTarget).toEqual({ kind: 'insert', line: 1, shell: '# _' })
|
||||
expect(run?.children.length).toBeGreaterThan(0)
|
||||
expect(run?.children.every(step => step.commentTarget === undefined)).toBe(true)
|
||||
})
|
||||
|
||||
@@ -5,7 +5,7 @@ import type { ImportedName } from './symbolLinks'
|
||||
import { blockAttributes } from './attributes'
|
||||
import { KIND_ICONS } from './contract'
|
||||
import { commonIndent, splitLines } from './editText'
|
||||
import { isCommentBlock } from './languages/shared'
|
||||
import { explanationSpan, isCommentBlock } from './languages/shared'
|
||||
import { activeRules, claimBlock, importsIn } from './libraryRules'
|
||||
import { BlockAnchors } from './qa/anchor'
|
||||
import { importedNames } from './symbolLinks'
|
||||
@@ -38,7 +38,7 @@ interface BlockViewBase {
|
||||
* 同一个 id 只留第一个。项目入口那一块的第一个换成入口(见 entries/entryMarks.ts)。webview 按这个画图标,不自己用种类查。
|
||||
*/
|
||||
concepts: BlockConcept[]
|
||||
/** 「重写注释」写到哪(见 CommentTarget);不能写注释的块(嵌着的代码里的、语言没说行注释怎么写又还没有注释的)没有。 */
|
||||
/** 「重写注释」写到哪(见 CommentTarget);不能写注释的块(嵌着的代码里的、语言没给出新写的办法又还没有注释的)没有。 */
|
||||
commentTarget?: CommentTarget
|
||||
/** 库规则的卡片才有:筛选栏里这个种类的名字,卡片边框的颜色(RuleCard.badge)。图标在 concepts 里。预览同类块一样默认收起它的子块。 */
|
||||
badge?: KindBadge
|
||||
@@ -54,12 +54,14 @@ export interface BlockSlot {
|
||||
}
|
||||
|
||||
/**
|
||||
* 「重写注释」写到哪,行号 0 起:replace 是换掉 startLine..endLine 这几行(块前面那几段普通注释;文件开头的说明卡片是它自己),
|
||||
* insert 是块还没有注释,新写一段插在 line 这一行上面(块的第一行),缩进同这一行。
|
||||
* 「重写注释」写到哪,行号 0 起。
|
||||
* replace 换掉这几行:块的说明(docstring、JSDoc、`///`),没有说明时是块前面的普通注释,文件开头的说明卡片是它自己。
|
||||
* insert 是都没有,新写一段插在 line 这一行上面。shell 是带定界符的空壳(正文占位 `_`),缩进同这一行。
|
||||
* breakAt 是主体和声明写在同一行:从这一行的这一列拆开,说明插进主体里。
|
||||
*/
|
||||
export type CommentTarget
|
||||
= | { kind: 'replace', startLine: number, endLine: number }
|
||||
| { kind: 'insert', line: number }
|
||||
| { kind: 'insert', line: number, shell: string, breakAt?: number }
|
||||
|
||||
/**
|
||||
* 发给 webview 的一块(可序列化):函数、类、枚举、异常、导入、常量、变量块和库规则的卡片预览时是属性表,文件开头的说明是 Markdown,其余是一行译文。
|
||||
@@ -394,17 +396,26 @@ export function toBlockViews(
|
||||
return { properties: row ? withCommentRow(rows, row) : rows }
|
||||
}
|
||||
/** 「重写注释」写到哪(见 CommentTarget):块前面的普通注释换掉,没有就新写在块上面。 */
|
||||
/** 「重写注释」写到哪:有说明就换说明,否则换块前面的普通注释,都没有就按这门语言的说明新写。 */
|
||||
function commentTarget(block: CodeBlock): Pick<BlockView, 'commentTarget'> {
|
||||
if (!writable)
|
||||
return {}
|
||||
if (isCommentBlock(block))
|
||||
return { commentTarget: { kind: 'replace', startLine: block.header.startRow, endLine: block.header.endRow } }
|
||||
const leading = (block.comments ?? []).filter(comment => comment.header.startIndex < block.header.startIndex)
|
||||
const doc = explanationSpan(block)
|
||||
const covered = doc !== undefined && leading.some(comment => comment.header.startIndex <= doc.startIndex && comment.header.endIndex >= doc.endIndex)
|
||||
if (doc && !covered)
|
||||
return { commentTarget: { kind: 'replace', startLine: doc.startRow, endLine: doc.endRow } }
|
||||
const [first] = leading
|
||||
const last = leading.at(-1)
|
||||
if (first && last)
|
||||
return { commentTarget: { kind: 'replace', startLine: first.header.startRow, endLine: last.header.endRow } }
|
||||
return layerDocument.language.lineComment ? { commentTarget: { kind: 'insert', line: block.header.startRow } } : {}
|
||||
const fresh = layerDocument.language.freshDoc?.(block)
|
||||
if (fresh)
|
||||
return { commentTarget: { kind: 'insert', line: fresh.line, shell: fresh.shell, ...(fresh.breakAt !== undefined && { breakAt: fresh.breakAt }) } }
|
||||
const marker = layerDocument.language.lineComment
|
||||
return marker ? { commentTarget: { kind: 'insert', line: block.header.startRow, shell: `${marker} _` } } : {}
|
||||
}
|
||||
/** 一行译文的卡片:译文、实参表、说明,有注释时下面再排「注释」一行。 */
|
||||
function textCard(block: TextBlock): ReturnType<typeof textView> & { rows?: PropertyRow[] } {
|
||||
|
||||
@@ -5,9 +5,11 @@ import { toBlockViews } from './blockView'
|
||||
import { COMMENT_SKILL, commentEdit, commentRewritePrompt, commentSource, createCommentParser, findBlock, refillComment, rewriteBlockComment } from './commentRewrite'
|
||||
import { commonIndent, replaceLines, splitLines } from './editText'
|
||||
import { createPythonAdapter } from './languages/python'
|
||||
import { createRustAdapter } from './languages/rust'
|
||||
import { createTypeScriptAdapter } from './languages/typescript'
|
||||
import { WASM_DIR, withBlocks } from './testSupport'
|
||||
import { pythonZhTranslator } from './translators/pythonZh'
|
||||
import { rustZhTranslator } from './translators/rustZh'
|
||||
import { typescriptZhTranslator } from './translators/typescriptZh'
|
||||
|
||||
/** 和预览写回同一条路:正文套回壳,再按原来的缩进替换这几行。 */
|
||||
@@ -90,6 +92,11 @@ describe('重写注释后套回原来的壳', () => {
|
||||
].join('\n'))
|
||||
})
|
||||
|
||||
it('三斜杠和模块内文档注释:记号留着,不收成 //', () => {
|
||||
expect(refillComment('/// 旧说明', '新说明')).toBe('/// 新说明')
|
||||
expect(refillComment('//! 旧说明', '新说明')).toBe('//! 新说明')
|
||||
})
|
||||
|
||||
it('双斜杠行注释同样只换正文', () => {
|
||||
const file = [
|
||||
'function keep() {',
|
||||
@@ -194,10 +201,10 @@ async function pythonBlock(source: string, line: number): Promise<BlockView> {
|
||||
return found
|
||||
}
|
||||
|
||||
function job(block: BlockView, path = 'src/a.py', lineComment = '#'): CommentRewriteJob {
|
||||
function job(block: BlockView, path = 'src/a.py'): CommentRewriteJob {
|
||||
if (!block.commentTarget)
|
||||
throw new Error('这块不能写注释')
|
||||
return { target: block.commentTarget, startLine: block.startLine, endLine: block.endLine, path, lineComment }
|
||||
return { target: block.commentTarget, startLine: block.startLine, endLine: block.endLine, path }
|
||||
}
|
||||
|
||||
const SOURCE = [
|
||||
@@ -227,13 +234,54 @@ describe('重写一块代码的注释', () => {
|
||||
expect(file.text()).toBe(SOURCE.replace('# 旧的说明\n\n# 第二段', '# 仓库:按名字找文件。'))
|
||||
})
|
||||
|
||||
it('还没有注释的块:用行注释记号新写一段,插在块上面,缩进同块的第一行;说明(docstring)不动', async () => {
|
||||
it('函数已经有 docstring、上面没有 #:换掉 docstring 的正文,不在 def 上面再插一行', async () => {
|
||||
const source = 'class Repo:\n def find(self):\n """找。"""\n return 1\n'
|
||||
const find = await pythonBlock(source, 2)
|
||||
expect(find.commentTarget).toEqual({ kind: 'insert', line: 1 })
|
||||
expect(find.commentTarget).toEqual({ kind: 'replace', startLine: 2, endLine: 2 })
|
||||
const file = memoryFile(source)
|
||||
expect(await rewriteBlockComment(job(find), file.deps(async () => '按名字找\n\n找不到时返回 1'))).toEqual({ status: 'done' })
|
||||
expect(file.text()).toBe('class Repo:\n # 按名字找\n #\n # 找不到时返回 1\n def find(self):\n """找。"""\n return 1\n')
|
||||
const prompts: string[] = []
|
||||
expect(await rewriteBlockComment(job(find), file.deps(async (prompt) => {
|
||||
prompts.push(prompt)
|
||||
return '按名字找\n\n找不到时返回 1'
|
||||
}))).toEqual({ status: 'done' })
|
||||
expect(prompts).toEqual(['/ecc:writing-code-comments src/a.py:2-4'])
|
||||
expect(file.text()).toBe('class Repo:\n def find(self):\n """按名字找\n\n 找不到时返回 1\n """\n return 1\n')
|
||||
})
|
||||
|
||||
it('函数既没有 docstring 也没有前面的 #:在主体里插一段 docstring,不在 def 上面写 #', async () => {
|
||||
const source = 'def find(self):\n return 1\n'
|
||||
const find = await pythonBlock(source, 1)
|
||||
expect(find.commentTarget).toEqual({ kind: 'insert', line: 1, shell: '"""_"""' })
|
||||
const file = memoryFile(source)
|
||||
expect(await rewriteBlockComment(job(find), file.deps(async () => '按名字找。'))).toEqual({ status: 'done' })
|
||||
expect(file.text()).toBe('def find(self):\n """按名字找。"""\n return 1\n')
|
||||
})
|
||||
|
||||
it('声明和主体写在同一行:docstring 插进主体,不写在 def 上面', async () => {
|
||||
const source = 'def find(): return 1\n'
|
||||
const find = await pythonBlock(source, 1)
|
||||
expect(find.commentTarget).toMatchObject({ kind: 'insert', line: 0, shell: '"""_"""' })
|
||||
const file = memoryFile(source)
|
||||
expect(await rewriteBlockComment(job(find), file.deps(async () => '按名字找。'))).toEqual({ status: 'done' })
|
||||
expect(file.text()).toBe('def find():\n """按名字找。"""\n return 1\n')
|
||||
})
|
||||
|
||||
it('函数只有前面的 #、没有 docstring:仍换掉那段 #', async () => {
|
||||
const source = '# 旧的说明\ndef find():\n return 1\n'
|
||||
const find = await pythonBlock(source, 1)
|
||||
expect(find.commentTarget).toEqual({ kind: 'replace', startLine: 0, endLine: 0 })
|
||||
const file = memoryFile(source)
|
||||
expect(await rewriteBlockComment(job(find), file.deps(async () => '按名字找。'))).toEqual({ status: 'done' })
|
||||
expect(file.text()).toBe('# 按名字找。\ndef find():\n return 1\n')
|
||||
})
|
||||
|
||||
it('docstring 和前面的 # 都有:只换 docstring,# 留着', async () => {
|
||||
const source = '# 旧的说明\ndef find():\n """找。"""\n return 1\n'
|
||||
const find = await pythonBlock(source, 1)
|
||||
expect(find.commentTarget).toEqual({ kind: 'replace', startLine: 2, endLine: 2 })
|
||||
const file = memoryFile(source)
|
||||
expect(await rewriteBlockComment(job(find), file.deps(async () => '新的说明。'))).toEqual({ status: 'done' })
|
||||
expect(file.text()).toBe('# 旧的说明\ndef find():\n """新的说明。"""\n return 1\n')
|
||||
})
|
||||
|
||||
it('文件开头的说明卡片换的是它自己:范围就是这段注释', async () => {
|
||||
@@ -284,13 +332,31 @@ describe('重写一块代码的注释', () => {
|
||||
expect(stopped.writes).toEqual([])
|
||||
})
|
||||
|
||||
it('用 // 写 TypeScript 的块:JSDoc 是说明,不当成要换掉的注释', async () => {
|
||||
it('typescript 已经有 JSDoc:换掉这段说明,不在声明上面再写 //', async () => {
|
||||
const ts = createTypeScriptAdapter(WASM_DIR)
|
||||
const source = '/** 读配置。 */\nexport function load() {}\n'
|
||||
const [view] = await withBlocks(ts, 'typescript', source, blocks => toBlockViews(blocks, { source, language: ts, markers: [] }, typescriptZhTranslator))
|
||||
expect(view.commentTarget).toEqual({ kind: 'insert', line: 0 })
|
||||
const edit = commentEdit(view.commentTarget as CommentTarget, index => splitLines(source)[index], '启动时读一次', ts.lineComment)
|
||||
expect(edit).toEqual({ kind: 'insert', line: 0, text: '// 启动时读一次' })
|
||||
expect(view.commentTarget).toEqual({ kind: 'replace', startLine: 0, endLine: 0 })
|
||||
const edit = commentEdit(view.commentTarget as CommentTarget, index => splitLines(source)[index], '启动时读一次')
|
||||
expect(edit).toEqual({ kind: 'replace', startLine: 0, endLine: 0, text: '/** 启动时读一次 */' })
|
||||
})
|
||||
|
||||
it('typescript 还没有说明:在声明上面插 /** */,不写 //', async () => {
|
||||
const ts = createTypeScriptAdapter(WASM_DIR)
|
||||
const source = 'export function load() {}\n'
|
||||
const [view] = await withBlocks(ts, 'typescript', source, blocks => toBlockViews(blocks, { source, language: ts, markers: [] }, typescriptZhTranslator))
|
||||
expect(view.commentTarget).toEqual({ kind: 'insert', line: 0, shell: '/** _ */' })
|
||||
const edit = commentEdit(view.commentTarget as CommentTarget, index => splitLines(source)[index], '启动时读一次')
|
||||
expect(edit).toEqual({ kind: 'insert', line: 0, text: '/** 启动时读一次 */' })
|
||||
})
|
||||
|
||||
it('rust 还没有说明:在项上面插 ///,不写 //', async () => {
|
||||
const rust = createRustAdapter(WASM_DIR)
|
||||
const source = 'fn load() {}\n'
|
||||
const [view] = await withBlocks(rust, 'rust', source, blocks => toBlockViews(blocks, { source, language: rust, markers: [] }, rustZhTranslator))
|
||||
expect(view.commentTarget).toEqual({ kind: 'insert', line: 0, shell: '/// _' })
|
||||
const edit = commentEdit(view.commentTarget as CommentTarget, index => splitLines(source)[index], '启动时读一次')
|
||||
expect(edit).toEqual({ kind: 'insert', line: 0, text: '/// 启动时读一次' })
|
||||
})
|
||||
})
|
||||
|
||||
|
||||
@@ -102,11 +102,13 @@ interface LineShell {
|
||||
spaced: boolean
|
||||
}
|
||||
|
||||
/** 一行 `#` 或 `//` 注释:记号、记号后面有没有空格。 */
|
||||
/** 一行 `#`、`//`、`///`、`//!` 注释:记号、记号后面有没有空格。`///` 和 `//!` 整段都是记号,不能收成 `//`。 */
|
||||
function lineParts(line: string): LineShell | undefined {
|
||||
let end = 0
|
||||
if (line.startsWith('//')) {
|
||||
end = 2
|
||||
if (line[end] === '/' || line[end] === '!')
|
||||
end++
|
||||
}
|
||||
else if (line.startsWith('#')) {
|
||||
while (line[end] === '#')
|
||||
@@ -337,17 +339,34 @@ export type CommentEdit
|
||||
* 新正文落到文件里的样子:换掉原来那段注释时套回它的定界符(几段注释中间隔着空行时照第一段的写法写成一段);
|
||||
* 块还没有注释时用语言的行注释记号新写一段,缩进同块的第一行。认不出定界符、没有行注释记号、正文是空的时没有。
|
||||
*/
|
||||
export function commentEdit(target: CommentTarget, lineAt: (line: number) => string, body: string, lineComment: string | undefined): CommentEdit | undefined {
|
||||
/** 把模型交回的正文套回目标:换掉原来那几行,或按 shell 新写一段。正文是空的、定界符认不出,就不写。 */
|
||||
export function commentEdit(target: CommentTarget, lineAt: (line: number) => string, body: string): CommentEdit | undefined {
|
||||
if (target.kind === 'replace') {
|
||||
const comment = commentSource(Array.from({ length: target.endLine - target.startLine + 1 }, (_, i) => lineAt(target.startLine + i)))
|
||||
const text = refillComment(comment, body) ?? refillComment(comment.split(/\n[ \t]*\n/)[0], body)
|
||||
return text === undefined ? undefined : { kind: 'replace', startLine: target.startLine, endLine: target.endLine, text }
|
||||
}
|
||||
if (!lineComment)
|
||||
const text = refillComment(target.shell, body)
|
||||
if (text === undefined)
|
||||
return undefined
|
||||
if (target.breakAt !== undefined)
|
||||
return splitSameLine(lineAt(target.line), target.line, target.breakAt, text)
|
||||
const indent = /^[ \t]*/.exec(lineAt(target.line))?.[0] ?? ''
|
||||
const text = refillComment(`${lineComment} _`, body)
|
||||
return text === undefined ? undefined : { kind: 'insert', line: target.line, text: text.split('\n').map(line => indent + line).join('\n') }
|
||||
return { kind: 'insert', line: target.line, text: text.split('\n').map(line => indent + line).join('\n') }
|
||||
}
|
||||
|
||||
/** 声明和主体写在同一行:从 breakAt 那一列拆开,说明插进主体,缩进比声明多一层。交给写回时再补上这一行原来的缩进。 */
|
||||
function splitSameLine(line: string, atLine: number, breakAt: number, doc: string): CommentEdit | undefined {
|
||||
if (breakAt < 0 || breakAt > line.length)
|
||||
return undefined
|
||||
const base = /^[ \t]*/.exec(line)?.[0] ?? ''
|
||||
const head = line.slice(base.length, breakAt).trimEnd()
|
||||
const tail = line.slice(breakAt).trim()
|
||||
if (!head || !tail)
|
||||
return undefined
|
||||
const unit = base.includes('\t') && !base.includes(' ') ? '\t' : ' '
|
||||
const body = doc.split('\n').map(row => row ? unit + row : '')
|
||||
return { kind: 'replace', startLine: atLine, endLine: atLine, text: [head, ...body, unit + tail].join('\n') }
|
||||
}
|
||||
|
||||
/** 写回文件的结果:written 写进去了,unchanged 和文件里一样不用写,missing 行号已经不在,rejected 编辑器没接受。 */
|
||||
@@ -365,7 +384,6 @@ export interface CommentRewriteJob {
|
||||
startLine: number
|
||||
endLine: number
|
||||
path: string
|
||||
lineComment?: string
|
||||
}
|
||||
|
||||
/** 重写时要用的:读文件现在的行,问模型(拿到结构化结果里的正文,没有正文时 undefined;失败时抛错),出错时给人看的话,写回文件。 */
|
||||
@@ -403,7 +421,7 @@ export async function rewriteBlockComment(job: CommentRewriteJob, deps: CommentR
|
||||
return failed('模型没有按要求交回注释正文,没有改文件。')
|
||||
if (rangeText() !== snapshot)
|
||||
return failed('这块代码在重写期间变了,没有写回。')
|
||||
const edit = commentEdit(job.target, deps.lineAt, body, job.lineComment)
|
||||
const edit = commentEdit(job.target, deps.lineAt, body)
|
||||
if (!edit)
|
||||
return failed('这段注释的定界符认不出来,没有改文件。')
|
||||
const wrote = await deps.write(edit)
|
||||
|
||||
@@ -720,8 +720,17 @@ export interface LanguageAdapter {
|
||||
toBlocks: (root: Node, source: string, languageId: string) => CodeBlock[]
|
||||
/** 给语法树叶子节点定词性:源码着色用它,译文里照抄的词也按它着色、按它认出能查定义的名字。 */
|
||||
tokenRole: (leaf: Node) => TokenRole
|
||||
/** 行注释的记号(`#`、`//`):「重写注释」给还没有注释的块新写一段时用它;不填就不能新写。 */
|
||||
/**
|
||||
* 行注释的记号(`#`、`//`):块还没有说明、也没有前面的普通注释,而这门语言的说明就是行注释时(shell、Dockerfile),
|
||||
* 「写注释」用它在块上面新写一段。不填、又没有 freshDoc,就不能新写。
|
||||
*/
|
||||
lineComment?: string
|
||||
/**
|
||||
* 还没有说明、也没有块前面的普通注释时,「写注释」新写的说明插在哪。
|
||||
* shell 是带定界符的空壳,正文用 `_` 占位。breakAt 是主体和声明写在同一行:从这一行的这一列拆开,说明插进主体里。
|
||||
* 不填就用 lineComment 写在块的第一行上面。
|
||||
*/
|
||||
freshDoc?: (block: CodeBlock) => { line: number, shell: string, breakAt?: number } | undefined
|
||||
/** node 是写在表达式里的遍历(推导式、生成器表达式)时给出它的结构,别的节点没有;没有这种写法的语言不填。 */
|
||||
iteration?: (node: Node) => IterationFacts | undefined
|
||||
/** node 是一次调用(含新建对象)时给出它的结构,别的节点没有;没给的语言不展开调用,照常一行译文。 */
|
||||
|
||||
@@ -982,7 +982,6 @@ class Preview {
|
||||
startLine: block.startLine,
|
||||
endLine: block.endLine,
|
||||
path: relative(cwd, document.uri.fsPath).split(sep).join('/'),
|
||||
lineComment: this.registry.languageFor(this.languageId)?.lineComment,
|
||||
}, {
|
||||
lineAt: line => document.lineAt(line).text,
|
||||
lineCount: () => document.lineCount,
|
||||
|
||||
@@ -648,6 +648,18 @@ function pythonTokenRole(leaf: Node): TokenRole {
|
||||
}
|
||||
|
||||
/** Python 语言插件。@param wasmDir 语法包 .wasm 所在目录 */
|
||||
/** 函数、类还没有说明时,在主体里插一段 docstring。主体跟声明写在同一行时从第一条语句那一列拆开。 */
|
||||
function freshDoc(block: CodeBlock): { line: number, shell: string, breakAt?: number } | undefined {
|
||||
if (block.kind !== 'function' && block.kind !== 'class')
|
||||
return undefined
|
||||
const shell = '"""_"""'
|
||||
const below = block.children.find(child => child.header.startRow > block.header.endRow)
|
||||
if (below)
|
||||
return { line: below.header.startRow, shell }
|
||||
const same = block.children.find(child => child.node.startPosition.row === block.header.endRow)
|
||||
return same ? { line: block.header.endRow, shell, breakAt: same.node.startPosition.column } : undefined
|
||||
}
|
||||
|
||||
export function createPythonAdapter(wasmDir: string): LanguageAdapter {
|
||||
return {
|
||||
id: 'python',
|
||||
@@ -656,6 +668,7 @@ export function createPythonAdapter(wasmDir: string): LanguageAdapter {
|
||||
toBlocks: root => withExceptions(attachComments(statements(named(root), 'module', -1), aboutModule), PYTHON_EXCEPTIONS),
|
||||
tokenRole: pythonTokenRole,
|
||||
lineComment: '#',
|
||||
freshDoc,
|
||||
iteration: pythonIteration,
|
||||
call: pythonCall,
|
||||
displayNames: pythonNameReader,
|
||||
|
||||
@@ -14,6 +14,7 @@ export function createRustAdapter(wasmDir: string): LanguageAdapter {
|
||||
toBlocks: root => attachComments(topBlocks(named(root), -1), aboutModule),
|
||||
tokenRole: rustTokenRole,
|
||||
lineComment: '//',
|
||||
freshDoc,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -35,6 +36,13 @@ const COMMENTS: CommentRules = {
|
||||
documents: new Set(['function', 'class', 'trait', 'impl', 'module', 'enum', 'const', 'variable']),
|
||||
}
|
||||
|
||||
/** 能带说明的项还没有说明时,在它紧上面插 `///`。 */
|
||||
function freshDoc(block: CodeBlock): { line: number, shell: string } | undefined {
|
||||
if (!(COMMENTS.documents as ReadonlySet<string>).has(block.kind))
|
||||
return undefined
|
||||
return { line: block.header.startRow, shell: '/// _' }
|
||||
}
|
||||
|
||||
/** 讲所在模块或 crate 的内部文档注释(`//!`、`/*!`):写在文件开头是文件的说明,写在 `mod` 身体里交给那个模块。 */
|
||||
function aboutModule(comment: CommentBlock): boolean {
|
||||
return named(comment.node).some(child => child.type === 'inner_doc_comment_marker')
|
||||
|
||||
@@ -328,6 +328,32 @@ function ownDoc(block: CodeBlock): Node | undefined {
|
||||
}
|
||||
}
|
||||
|
||||
/** 连着几行的外部文档注释(Rust 的 `///`)是同一段说明,第一条节点带着后面紧挨着的几行;别的说明就是它那个节点。 */
|
||||
export function docRun(first: Node): Node[] {
|
||||
const outer = (node: Node): boolean => node.type === 'line_comment' && named(node).some(child => child.type === 'outer_doc_comment_marker')
|
||||
if (!outer(first))
|
||||
return [first]
|
||||
const nodes = [first]
|
||||
for (let next = first.nextSibling; next && outer(next) && next.startPosition.row === nodes.at(-1)!.startPosition.row + 1; next = next.nextSibling)
|
||||
nodes.push(next)
|
||||
return nodes
|
||||
}
|
||||
|
||||
/** 块自己的说明占的那几行(见 ownDoc,Rust 连着的 `///` 算一整段)。没有说明就没有。 */
|
||||
export function explanationSpan(block: CodeBlock): SourceSpan | undefined {
|
||||
const doc = ownDoc(block)
|
||||
if (!doc)
|
||||
return undefined
|
||||
const nodes = docRun(doc)
|
||||
const first = nodes[0]
|
||||
const last = nodes.at(-1)
|
||||
if (!first || !last)
|
||||
return undefined
|
||||
const start = isCommentNode(first) ? commentNodeSpan(first) : nodeSpan(first)
|
||||
const end = isCommentNode(last) ? commentNodeSpan(last) : nodeSpan(last)
|
||||
return { startIndex: start.startIndex, endIndex: end.endIndex, startRow: start.startRow, endRow: end.endRow }
|
||||
}
|
||||
|
||||
/** 类属性的说明,照源码顺序:讲它的注释(BlockShape.comments)、它这一行行尾的注释、Python 紧跟在后面的属性 docstring。 */
|
||||
function fieldDocs(block: CodeBlock): Node[] {
|
||||
const trailing = block.node.nextNamedSibling
|
||||
|
||||
@@ -636,6 +636,13 @@ function typescriptTokenRole(leaf: Node): TokenRole {
|
||||
}
|
||||
|
||||
/** TypeScript / TSX / JavaScript / JSX 语言插件。@param wasmDir 语法包 .wasm 所在目录 */
|
||||
/** 能带说明的块还没有说明时,在声明紧上面插一段 JSDoc。 */
|
||||
function freshDoc(block: CodeBlock): { line: number, shell: string } | undefined {
|
||||
if (!(COMMENTS.documents as ReadonlySet<string>).has(block.kind))
|
||||
return undefined
|
||||
return { line: block.header.startRow, shell: '/** _ */' }
|
||||
}
|
||||
|
||||
export function createTypeScriptAdapter(wasmDir: string): LanguageAdapter {
|
||||
return {
|
||||
id: 'typescript',
|
||||
@@ -649,6 +656,7 @@ export function createTypeScriptAdapter(wasmDir: string): LanguageAdapter {
|
||||
toBlocks: (root, _source, languageId) => withExceptions(attachComments(blockReader(TYPED_LANGUAGES.has(languageId))(root), aboutFile), TYPESCRIPT_EXCEPTIONS),
|
||||
tokenRole: typescriptTokenRole,
|
||||
lineComment: '//',
|
||||
freshDoc,
|
||||
call: typescriptCall,
|
||||
displayNames: typescriptNameReader,
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user