🐛 fix(vscode): Rust 卡片照抄的类型、签名逐个名字带下标,返回类型也能悬停跳转
This commit is contained in:
1 parent
ecb6dc89c6
commit
55595e948c
17 files changed
+220
-122
No files matched your search
@@ -335,6 +335,7 @@ export function toBlockViews(
|
|||||||
imports,
|
imports,
|
||||||
...(layerTranslator && { displayLanguage: layerTranslator.displayLanguage }),
|
...(layerTranslator && { displayLanguage: layerTranslator.displayLanguage }),
|
||||||
translate: node => layerTranslator ? layerTranslator.expression(node, layerDocument) : [{ text: node.text, role: 'plain' }],
|
translate: node => layerTranslator ? layerTranslator.expression(node, layerDocument) : [{ text: node.text, role: 'plain' }],
|
||||||
|
verbatim: (node, role) => layerTranslator ? layerTranslator.verbatim(node, layerDocument, role) : [{ text: node.text, role: role ?? 'plain' }],
|
||||||
doc: node => layerTranslator?.doc(node, layerDocument),
|
doc: node => layerTranslator?.doc(node, layerDocument),
|
||||||
lookup: layerRules.lookup ?? (() => undefined),
|
lookup: layerRules.lookup ?? (() => undefined),
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -866,6 +866,11 @@ export interface Translator {
|
|||||||
describe: (block: FactBlock, document: SourceDocument) => PropertyRow[]
|
describe: (block: FactBlock, document: SourceDocument) => PropertyRow[]
|
||||||
/** 一个表达式或类型节点整个翻成一行译文,译法同属性表里的值:库规则翻默认值、类型时借它。 */
|
/** 一个表达式或类型节点整个翻成一行译文,译法同属性表里的值:库规则翻默认值、类型时借它。 */
|
||||||
expression: (node: Node, document: SourceDocument) => Inline[]
|
expression: (node: Node, document: SourceDocument) => Inline[]
|
||||||
|
/**
|
||||||
|
* 一个类型、签名节点照抄成一行原文,不翻关键字:叶子按源码着色(给了 `role` 就都按它),名字各自带源码下标,
|
||||||
|
* 跨几行写的压成一行、连着的空白收成一个空格。库规则照抄类型、签名时借它。
|
||||||
|
*/
|
||||||
|
verbatim: (node: Node, document: SourceDocument, role?: TokenRole) => Inline[]
|
||||||
/**
|
/**
|
||||||
* 一段说明文字(注释、docstring、当说明用的字符串)读成说明(见 Doc):去掉注释符号、引号和共同缩进后的 Markdown 原文,
|
* 一段说明文字(注释、docstring、当说明用的字符串)读成说明(见 Doc):去掉注释符号、引号和共同缩进后的 Markdown 原文,
|
||||||
* 拆出块标签;正文同属性表的「说明」。几条行文档注释连成一段的(Rust 的 `///`),node 是第一条,翻译插件自己往下连。没有文字时没有。
|
* 拆出块标签;正文同属性表的「说明」。几条行文档注释连成一段的(Rust 的 `///`),node 是第一条,翻译插件自己往下连。没有文字时没有。
|
||||||
@@ -1086,6 +1091,8 @@ export interface RuleContext {
|
|||||||
displayLanguage?: string
|
displayLanguage?: string
|
||||||
/** 用当前翻译插件把一个表达式或类型节点翻成一行译文(Translator.expression):名字带源码下标,能跳到定义、高亮各处出现;没有翻译插件时照抄源码。 */
|
/** 用当前翻译插件把一个表达式或类型节点翻成一行译文(Translator.expression):名字带源码下标,能跳到定义、高亮各处出现;没有翻译插件时照抄源码。 */
|
||||||
translate: (node: Node) => Inline[]
|
translate: (node: Node) => Inline[]
|
||||||
|
/** 用当前翻译插件把一个类型、签名节点照抄成一行原文([Translator.verbatim]):名字各自带源码下标,能跳到定义、高亮各处出现;没有翻译插件时照抄源码。 */
|
||||||
|
verbatim: (node: Node, role?: TokenRole) => Inline[]
|
||||||
/** 用当前翻译插件读一段说明文字(Translator.doc):正文和块标签;没有翻译插件、没有文字时没有。 */
|
/** 用当前翻译插件读一段说明文字(Translator.doc):正文和块标签;没有翻译插件、没有文字时没有。 */
|
||||||
doc: (node: Node) => Doc | undefined
|
doc: (node: Node) => Doc | undefined
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -1,11 +1,12 @@
|
|||||||
import type { BlockView } from '../blockView'
|
import type { BlockView } from '../blockView'
|
||||||
|
import type { PreviewText } from '../contract'
|
||||||
import type { Outline } from '../testSupport'
|
import type { Outline } from '../testSupport'
|
||||||
import { describe, expect, it } from 'vitest'
|
import { describe, expect, it } from 'vitest'
|
||||||
import { blockCodes, toBlockViews } from '../blockView'
|
import { blockCodes, toBlockViews } from '../blockView'
|
||||||
import { blockMarks } from '../diff/viewDiff'
|
import { blockMarks } from '../diff/viewDiff'
|
||||||
import { rustImplRule } from '../rules/rustImpl'
|
import { rustImplRule } from '../rules/rustImpl'
|
||||||
import { isCrateRoot } from '../rules/rustTree'
|
import { isCrateRoot } from '../rules/rustTree'
|
||||||
import { commentText, nodeFacts, outline, propertyLines, readFixture, viewLines, WASM_DIR, withBlocks } from '../testSupport'
|
import { commentText, nodeFacts, outline, previewText, propertyLines, propertyRow, readFixture, typeNames, viewLines, WASM_DIR, withBlocks, wordsOf } from '../testSupport'
|
||||||
import { rustZhTranslator } from '../translators/rustZh'
|
import { rustZhTranslator } from '../translators/rustZh'
|
||||||
import { createRustAdapter } from './rust'
|
import { createRustAdapter } from './rust'
|
||||||
|
|
||||||
@@ -136,6 +137,68 @@ describe('rust 语言卡片', () => {
|
|||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
it('照抄的类型逐个名字带自己的下标、按词性着色:参数类型、返回;跨行写的压成一行;没写返回类型的 () 是普通文字', async () => {
|
||||||
|
const source = [
|
||||||
|
'fn f(state: State<\'_, AppState>) -> Result<(), String> {}',
|
||||||
|
'fn g(',
|
||||||
|
' map: HashMap<',
|
||||||
|
' String,',
|
||||||
|
' Vec<u8>,',
|
||||||
|
' >,',
|
||||||
|
') -> Option<',
|
||||||
|
' String,',
|
||||||
|
'> {}',
|
||||||
|
'fn h() {}',
|
||||||
|
'',
|
||||||
|
].join('\n')
|
||||||
|
const [f, g, h] = await views(source)
|
||||||
|
const paramType = (block: BlockView | undefined): PreviewText => propertyRow(block, '输入参数').items?.[0]?.cells[1] ?? []
|
||||||
|
const returned = (block: BlockView | undefined): PreviewText => propertyRow(block, '返回').value
|
||||||
|
expect(previewText(paramType(f))).toBe('State<\'_, AppState>')
|
||||||
|
expect(wordsOf(paramType(f), 'type')).toEqual([['State', source.indexOf('State')], ['AppState', source.indexOf('AppState')]])
|
||||||
|
expect(previewText(returned(f))).toBe('Result<(), String>')
|
||||||
|
expect(wordsOf(returned(f), 'type')).toEqual([['Result', source.indexOf('Result')], ['String', source.indexOf('String')]])
|
||||||
|
expect(previewText(paramType(g))).toBe('HashMap< String, Vec<u8>, >')
|
||||||
|
expect(previewText(returned(g))).toBe('Option< String, >')
|
||||||
|
expect([...typeNames(paramType(g), source), ...typeNames(returned(g), source)]).toEqual(['HashMap', 'String', 'Vec', 'u8', 'Option', 'String'])
|
||||||
|
expect(returned(h)).toEqual([{ text: '()', role: 'plain' }])
|
||||||
|
})
|
||||||
|
|
||||||
|
it('方法表的参数和返回、字段类型、变体携带的数据同样逐个名字带下标,导入来源逐节带下标', async () => {
|
||||||
|
const source = [
|
||||||
|
'use std::collections::HashMap;',
|
||||||
|
'struct S {',
|
||||||
|
' map: HashMap<String, Vec<u8>>,',
|
||||||
|
'}',
|
||||||
|
'enum E {',
|
||||||
|
' Pair(Option<String>, Box<S>),',
|
||||||
|
' Named { id: Uuid },',
|
||||||
|
'}',
|
||||||
|
'impl S {',
|
||||||
|
' fn m(',
|
||||||
|
' &self,',
|
||||||
|
' state: State<\'_, AppState>,',
|
||||||
|
' ) -> Result<(), String> {}',
|
||||||
|
'}',
|
||||||
|
'',
|
||||||
|
].join('\n')
|
||||||
|
const list = await views(source)
|
||||||
|
const from = propertyRow(card(list, 'import'), '来源').value
|
||||||
|
expect(previewText(from)).toBe('std::collections')
|
||||||
|
expect(wordsOf(from, 'type')).toEqual([['std', source.indexOf('std')], ['::', undefined], ['collections', source.indexOf('collections')]])
|
||||||
|
expect(typeNames(propertyRow(card(list, 'class'), '字段').items?.[0]?.cells[1], source)).toEqual(['HashMap', 'String', 'Vec', 'u8'])
|
||||||
|
const [pair, named] = propertyRow(card(list, 'enum'), '变体').items ?? []
|
||||||
|
expect(previewText(pair?.cells[1] ?? [])).toBe('Option<String>, Box<S>')
|
||||||
|
expect(typeNames(pair?.cells[1], source)).toEqual(['Option', 'String', 'Box', 'S'])
|
||||||
|
expect(previewText(named?.cells[1] ?? [])).toBe('{ id: Uuid }')
|
||||||
|
expect(typeNames(named?.cells[1], source)).toEqual(['Uuid'])
|
||||||
|
const [method] = propertyRow(card(list, 'rust.impl'), '方法').items ?? []
|
||||||
|
expect(previewText(method?.cells[1] ?? [])).toBe('&self, state: State<\'_, AppState>')
|
||||||
|
expect(typeNames(method?.cells[1], source)).toEqual(['State', 'AppState'])
|
||||||
|
expect(previewText(method?.cells[2] ?? [])).toBe('Result<(), String>')
|
||||||
|
expect(typeNames(method?.cells[2], source)).toEqual(['Result', 'String'])
|
||||||
|
})
|
||||||
|
|
||||||
it('带身体的模块拆成子块,常量和外部函数是卡片', async () => {
|
it('带身体的模块拆成子块,常量和外部函数是卡片', async () => {
|
||||||
const source = [
|
const source = [
|
||||||
'/// 平台相关。',
|
'/// 平台相关。',
|
||||||
|
|||||||
@@ -17,7 +17,7 @@ function adapter(id: string, languageIds: string[]): LanguageAdapter {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function translator(id: string, languageIds: string[]): Translator {
|
function translator(id: string, languageIds: string[]): Translator {
|
||||||
return { id, label: id, languageIds, displayLanguage: 'zh', translate: () => ({ text: [] }), describe: () => [], expression: () => [], doc: () => undefined }
|
return { id, label: id, languageIds, displayLanguage: 'zh', translate: () => ({ text: [] }), describe: () => [], expression: () => [], verbatim: () => [], doc: () => undefined }
|
||||||
}
|
}
|
||||||
|
|
||||||
function rule(id: string, languageIds: string[]): LibraryRule {
|
function rule(id: string, languageIds: string[]): LibraryRule {
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
import type { FunctionBlock, Inline, KindBadge, LibraryRule, Node, PropertyItem, PropertyRow, RuleCard, RuleContext, ThemeColor } from '../contract'
|
import type { FunctionBlock, Inline, KindBadge, LibraryRule, Node, PropertyItem, PropertyRow, RuleCard, RuleContext, ThemeColor } from '../contract'
|
||||||
import { returnNote, throwsRows, writtenParamRows } from './docRows'
|
import { returnNote, throwsRows, writtenParamRows } from './docRows'
|
||||||
import { attributeName, docSummary, named, oneLine, returnText, stringContent } from './rustTree'
|
import { returnValue } from './rustRows'
|
||||||
|
import { attributeName, docSummary, named, stringContent } from './rustTree'
|
||||||
|
|
||||||
/** 和 FastAPI 接口同一个蓝。 */
|
/** 和 FastAPI 接口同一个蓝。 */
|
||||||
const COLOR: ThemeColor = { dark: '#61afef', light: '#1a6fb5' }
|
const COLOR: ThemeColor = { dark: '#61afef', light: '#1a6fb5' }
|
||||||
@@ -52,14 +53,14 @@ function attributeRoute(block: FunctionBlock): Route | undefined {
|
|||||||
function card(block: FunctionBlock, route: Route, context: RuleContext): RuleCard {
|
function card(block: FunctionBlock, route: Route, context: RuleContext): RuleCard {
|
||||||
const doc = block.facts.doc && context.doc(block.facts.doc)
|
const doc = block.facts.doc && context.doc(block.facts.doc)
|
||||||
const summary = docSummary(doc)
|
const summary = docSummary(doc)
|
||||||
const params = pathParams(route.path, block.node)
|
const params = pathParams(route.path, block.node, context)
|
||||||
const rows: PropertyRow[] = [
|
const rows: PropertyRow[] = [
|
||||||
nameRow(block.facts.name),
|
nameRow(block.facts.name),
|
||||||
...(summary ? [{ label: '说明', value: { markdown: summary } }] : []),
|
...(summary ? [{ label: '说明', value: { markdown: summary } }] : []),
|
||||||
...(block.facts.async ? [{ label: '异步', value: [keyword('是')] }] : []),
|
...(block.facts.async ? [{ label: '异步', value: [keyword('是')] }] : []),
|
||||||
requestRow(route, params),
|
requestRow(route, params),
|
||||||
...writtenParamRows(doc?.params ?? [], '参数'),
|
...writtenParamRows(doc?.params ?? [], '参数'),
|
||||||
{ label: '返回', value: [plain(returnText(block.node))], ...returnNote(doc) },
|
{ label: '返回', value: returnValue(block.facts.returnType, context), ...returnNote(doc) },
|
||||||
...throwsRows(doc),
|
...throwsRows(doc),
|
||||||
]
|
]
|
||||||
return {
|
return {
|
||||||
@@ -86,7 +87,7 @@ function handled(node: Node, handler: string | undefined): boolean {
|
|||||||
|
|
||||||
function routerCard(node: Node, route: Route & { handlerNode?: Node }, context: RuleContext): RuleCard {
|
function routerCard(node: Node, route: Route & { handlerNode?: Node }, context: RuleContext): RuleCard {
|
||||||
const fn = route.handler ? functionNamed(node, route.handler) : undefined
|
const fn = route.handler ? functionNamed(node, route.handler) : undefined
|
||||||
const params = fn ? pathParams(route.path, fn) : []
|
const params = fn ? pathParams(route.path, fn, context) : []
|
||||||
const comment = fn && leadingDoc(fn)
|
const comment = fn && leadingDoc(fn)
|
||||||
const doc = docSummary(comment && context.doc(comment))
|
const doc = docSummary(comment && context.doc(comment))
|
||||||
return {
|
return {
|
||||||
@@ -96,17 +97,17 @@ function routerCard(node: Node, route: Route & { handlerNode?: Node }, context:
|
|||||||
{ label: '名称', value: route.handler ? [plain(route.handler)] : [hint('路由')] },
|
{ label: '名称', value: route.handler ? [plain(route.handler)] : [hint('路由')] },
|
||||||
...(doc ? [{ label: '说明', value: { markdown: doc } }] : []),
|
...(doc ? [{ label: '说明', value: { markdown: doc } }] : []),
|
||||||
requestRow(route, params),
|
requestRow(route, params),
|
||||||
{ label: '返回', value: [plain(fn ? returnText(fn) : '—')] },
|
{ label: '返回', value: fn ? returnValue(fn.childForFieldName('return_type'), context) : [plain('—')] },
|
||||||
],
|
],
|
||||||
absorbs: [],
|
absorbs: [],
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function requestRow(route: Route, params: string[]): PropertyRow {
|
function requestRow(route: Route, params: Inline[][]): PropertyRow {
|
||||||
const items: PropertyItem[] = [
|
const items: PropertyItem[] = [
|
||||||
{ cells: [[plain('方法')], [keyword(route.method)]] },
|
{ cells: [[plain('方法')], [keyword(route.method)]] },
|
||||||
{ cells: [[plain('路径')], [{ text: route.path, role: 'string' }]] },
|
{ cells: [[plain('路径')], [{ text: route.path, role: 'string' }]] },
|
||||||
...(params.length > 0 ? [{ cells: [[plain('路径参数')], params.map(text => plain(text)).flatMap((part, index) => index === 0 ? [part] : [plain(', '), part])] }] : []),
|
...(params.length > 0 ? [{ cells: [[plain('路径参数')], params.flatMap((part, index) => index === 0 ? part : [plain(', '), ...part])] }] : []),
|
||||||
]
|
]
|
||||||
return {
|
return {
|
||||||
label: '请求',
|
label: '请求',
|
||||||
@@ -141,15 +142,12 @@ function routesOn(node: Node, found: (Route & { handlerNode?: Node })[] = []): (
|
|||||||
return found
|
return found
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 路径里的 {名字} 对上参数。Path(id): Path<Uuid> 写成 id: Uuid。 */
|
/** 路径里的 `{名字}` 对上函数的参数:`Path(id): Path<Uuid>` 写成 `id: Uuid`,类型照抄成一行、名字各自带源码下标。 */
|
||||||
function pathParams(path: string, fn: Node): string[] {
|
function pathParams(path: string, fn: Node, context: RuleContext): Inline[][] {
|
||||||
const names = [...path.matchAll(/\{(\w+)\}/g)].map(match => match[1] ?? '')
|
const names = [...path.matchAll(/\{(\w+)\}/g)].map(match => match[1] ?? '')
|
||||||
return names.flatMap((name) => {
|
return names.map((name) => {
|
||||||
const param = parameterNamed(fn, name)
|
const type = innerType(parameterNamed(fn, name)?.childForFieldName('type'))
|
||||||
if (!param)
|
return type ? [plain(`${name}: `), ...context.verbatim(type)] : [plain(name)]
|
||||||
return [`${name}`]
|
|
||||||
const type = innerType(param.childForFieldName('type'))
|
|
||||||
return [type ? `${name}: ${type}` : name]
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -163,15 +161,11 @@ function parameterNamed(fn: Node, name: string): Node | undefined {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
function innerType(type: Node | null): string | undefined {
|
/** 路径参数的类型:`Path<Uuid>` 取尖括号里的第一个,别的写法就是它自己。 */
|
||||||
if (!type)
|
function innerType(type: Node | null | undefined): Node | undefined {
|
||||||
return undefined
|
if (type?.type === 'generic_type' && type.childForFieldName('type')?.text === 'Path')
|
||||||
if (type.type === 'generic_type' && type.childForFieldName('type')?.text === 'Path') {
|
return named(type.childForFieldName('type_arguments'))[0] ?? type
|
||||||
const arg = named(type.childForFieldName('type_arguments'))[0]
|
return type ?? undefined
|
||||||
if (arg)
|
|
||||||
return oneLine(arg.text)
|
|
||||||
}
|
|
||||||
return oneLine(type.text)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function itemAttributesHasRoute(fn: Node): boolean {
|
function itemAttributesHasRoute(fn: Node): boolean {
|
||||||
|
|||||||
@@ -537,7 +537,7 @@ describe('整份文件的扫描', () => {
|
|||||||
await withBlocks(py, 'python', ROUTES, (blocks) => {
|
await withBlocks(py, 'python', ROUTES, (blocks) => {
|
||||||
const imports = importsIn(blocks)
|
const imports = importsIn(blocks)
|
||||||
let reads = 0
|
let reads = 0
|
||||||
const base = { document: { source: ROUTES, language: py, markers: [] }, translate: () => [], doc: () => undefined, lookup: () => undefined }
|
const base = { document: { source: ROUTES, language: py, markers: [] }, translate: () => [], verbatim: () => [], doc: () => undefined, lookup: () => undefined }
|
||||||
const shared: RuleContext = { ...base, get imports() {
|
const shared: RuleContext = { ...base, get imports() {
|
||||||
reads++
|
reads++
|
||||||
return imports
|
return imports
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import type { ImplBlock, KindBadge, LibraryRule, PropertyRow, RuleCard, TraitBlock } from '../contract'
|
import type { ImplBlock, KindBadge, LibraryRule, PropertyRow, RuleCard, TraitBlock } from '../contract'
|
||||||
import { implRows, plainSay, traitRows } from './rustRows'
|
import { implRows, traitRows } from './rustRows'
|
||||||
|
|
||||||
/** 和函数卡同一个黄,实现块看起来仍是函数那一挂。 */
|
/** 和函数卡同一个黄,实现块看起来仍是函数那一挂。 */
|
||||||
const IMPL: KindBadge = { label: '实现', color: { dark: '#dcdcaa', light: '#795e26' }, icon: 'square-function' }
|
const IMPL: KindBadge = { label: '实现', color: { dark: '#dcdcaa', light: '#795e26' }, icon: 'square-function' }
|
||||||
@@ -16,11 +16,10 @@ export const rustImplRule: LibraryRule = {
|
|||||||
languageIds: ['rust'],
|
languageIds: ['rust'],
|
||||||
libraries: [],
|
libraries: [],
|
||||||
claim: (block, context) => {
|
claim: (block, context) => {
|
||||||
const say = plainSay(context.doc)
|
|
||||||
if (block.kind === 'impl')
|
if (block.kind === 'impl')
|
||||||
return methodsCard(block, 'rust.impl', IMPL, implRows(block, say))
|
return methodsCard(block, 'rust.impl', IMPL, implRows(block, context))
|
||||||
if (block.kind === 'trait')
|
if (block.kind === 'trait')
|
||||||
return methodsCard(block, 'rust.trait', TRAIT, traitRows(block, say))
|
return methodsCard(block, 'rust.trait', TRAIT, traitRows(block, context))
|
||||||
return undefined
|
return undefined
|
||||||
},
|
},
|
||||||
attributeNames: () => ({
|
attributeNames: () => ({
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ import { describe, expect, it } from 'vitest'
|
|||||||
import { toBlockViews } from '../blockView'
|
import { toBlockViews } from '../blockView'
|
||||||
import { blockMarks } from '../diff/viewDiff'
|
import { blockMarks } from '../diff/viewDiff'
|
||||||
import { createRustAdapter } from '../languages/rust'
|
import { createRustAdapter } from '../languages/rust'
|
||||||
import { readFixture, viewLines, WASM_DIR, withBlocks } from '../testSupport'
|
import { previewText, propertyRow, readFixture, typeNames, viewLines, WASM_DIR, withBlocks, wordsOf } from '../testSupport'
|
||||||
import { rustZhTranslator } from '../translators/rustZh'
|
import { rustZhTranslator } from '../translators/rustZh'
|
||||||
import { axumRule } from './axum'
|
import { axumRule } from './axum'
|
||||||
import { rustImplRule } from './rustImpl'
|
import { rustImplRule } from './rustImpl'
|
||||||
@@ -97,6 +97,40 @@ describe('serde 与 thiserror', () => {
|
|||||||
])
|
])
|
||||||
})
|
})
|
||||||
|
|
||||||
|
it('库卡片里照抄的类型逐个名字带自己的下标:tauri 的参数类型和返回、axum 的路径参数和返回、serde 的字段类型、sqlx 的返回类型', async () => {
|
||||||
|
const source = [
|
||||||
|
'use tauri::{AppHandle, State};',
|
||||||
|
'use axum::routing::get;',
|
||||||
|
'use serde::Serialize;',
|
||||||
|
'use sqlx::query_as;',
|
||||||
|
'#[tauri::command]',
|
||||||
|
'async fn rename(app: AppHandle, state: State<\'_, AppState>) -> Result<(), String> {}',
|
||||||
|
'#[get("/agents/{id}")]',
|
||||||
|
'async fn get_agent(Path(id): Path<Uuid>) -> Result<Json<AgentRow>, StatusCode> {}',
|
||||||
|
'#[derive(Serialize)]',
|
||||||
|
'struct Row {',
|
||||||
|
' tags: Vec<Tag>,',
|
||||||
|
'}',
|
||||||
|
'let row = query_as!(AgentRow, "SELECT 1");',
|
||||||
|
'',
|
||||||
|
].join('\n')
|
||||||
|
const list = await views(source)
|
||||||
|
const card = (kind: string): BlockView | undefined => list.find(block => block.kind === kind)
|
||||||
|
const [app, state] = propertyRow(card('tauri.command'), '参数').items ?? []
|
||||||
|
expect(typeNames(app?.cells[1], source)).toEqual(['AppHandle'])
|
||||||
|
expect(previewText(state?.cells[1] ?? [])).toBe('State<\'_, AppState>')
|
||||||
|
expect(typeNames(state?.cells[1], source)).toEqual(['State', 'AppState'])
|
||||||
|
const tauriReturned = propertyRow(card('tauri.command'), '返回').value
|
||||||
|
expect(previewText(tauriReturned)).toBe('Result<(), String>')
|
||||||
|
expect(wordsOf(tauriReturned, 'type')).toEqual([['Result', source.indexOf('Result')], ['String', source.indexOf('String')]])
|
||||||
|
const pathParams = propertyRow(card('axum.route'), '请求').items?.[2]?.cells[1]
|
||||||
|
expect(previewText(pathParams ?? [])).toBe('id: Uuid')
|
||||||
|
expect(typeNames(pathParams, source)).toEqual(['Uuid'])
|
||||||
|
expect(typeNames(propertyRow(card('axum.route'), '返回').value, source)).toEqual(['Result', 'Json', 'AgentRow', 'StatusCode'])
|
||||||
|
expect(typeNames(propertyRow(card('serde.struct'), '字段').items?.[0]?.cells[1], source)).toEqual(['Vec', 'Tag'])
|
||||||
|
expect(typeNames(propertyRow(card('sqlx.query'), '返回类型').value, source)).toEqual(['AgentRow'])
|
||||||
|
})
|
||||||
|
|
||||||
it('没导入 serde 时,带 derive 的结构体仍是结构体', async () => {
|
it('没导入 serde 时,带 derive 的结构体仍是结构体', async () => {
|
||||||
const source = '#[derive(Serialize)]\npub struct AgentRow {\n name: String,\n}\n'
|
const source = '#[derive(Serialize)]\npub struct AgentRow {\n name: String,\n}\n'
|
||||||
const [card] = await views(source)
|
const [card] = await views(source)
|
||||||
|
|||||||
@@ -1,15 +1,14 @@
|
|||||||
import type { AttributeBlock, AttributeFacts, AttributeItem, CfgPredicate, ClassBlock, CodeBlock, Doc, EnumBlock, FunctionBlock, ImplBlock, ImportBlock, Inline, ModuleBlock, Node, PropertyItem, PropertyRow, SourceSpan, TokenRole, TraitBlock } from '../contract'
|
import type { AttributeBlock, AttributeFacts, AttributeItem, CfgPredicate, ClassBlock, CodeBlock, Doc, EnumBlock, FunctionBlock, ImplBlock, ImportBlock, Inline, ModuleBlock, Node, PropertyItem, PropertyRow, RuleContext, SourceSpan, TokenRole, TraitBlock } from '../contract'
|
||||||
import type { RustVisibility } from './rustTree'
|
import type { RustVisibility } from './rustTree'
|
||||||
import { paramDocCell, paramDocs, returnNote, throwsRows, unmatchedParamRows } from './docRows'
|
import { paramDocCell, paramDocs, returnNote, throwsRows, unmatchedParamRows } from './docRows'
|
||||||
import { docSummary, isFnItem, isMethod, oneLine, returnText, signatureParams, structFields, variantPayload } from './rustTree'
|
import { docSummary, isFnItem, isMethod, signatureParams, structFields, variantPayload } from './rustTree'
|
||||||
|
import { hint, joined, plain, range, name as sourceName } from './segments'
|
||||||
|
|
||||||
/** 把类型、名字画成原文,读说明。翻译插件传入时,名字带源码下标。 */
|
/**
|
||||||
export interface RustSay {
|
* 属性表照抄类型、读说明的办法,同 [RuleContext] 的 `verbatim`、`doc`:库规则直接交 [RuleContext],
|
||||||
name: (node: Node, role: TokenRole) => Inline[]
|
* 中文直译用自己的模板拼一份。
|
||||||
source: (node: Node, role?: TokenRole) => Inline[]
|
*/
|
||||||
/** 读一项的文档注释(连着几行的 `///` 是一段),见 Translator.doc;没有时没有。 */
|
export type RustSay = Pick<RuleContext, 'verbatim' | 'doc'>
|
||||||
doc: (node: Node) => Doc | undefined
|
|
||||||
}
|
|
||||||
|
|
||||||
const VISIBILITY: Record<RustVisibility, string> = {
|
const VISIBILITY: Record<RustVisibility, string> = {
|
||||||
public: '公开',
|
public: '公开',
|
||||||
@@ -31,7 +30,7 @@ export function visibilityWord(block: CodeBlock): string {
|
|||||||
export function importRows(block: ImportBlock, say: RustSay): PropertyRow[] {
|
export function importRows(block: ImportBlock, say: RustSay): PropertyRow[] {
|
||||||
const [source] = block.facts.sources
|
const [source] = block.facts.sources
|
||||||
return [
|
return [
|
||||||
{ label: '来源', value: source ? say.source(source, 'type') : [hint('—')] },
|
{ label: '来源', value: source ? say.verbatim(source, 'type') : [hint('—')] },
|
||||||
{
|
{
|
||||||
label: '导入项',
|
label: '导入项',
|
||||||
value: [hint(`${block.facts.items.length} 个`)],
|
value: [hint(`${block.facts.items.length} 个`)],
|
||||||
@@ -42,7 +41,7 @@ export function importRows(block: ImportBlock, say: RustSay): PropertyRow[] {
|
|||||||
if (item.kind !== 'named')
|
if (item.kind !== 'named')
|
||||||
return { cells: [[hint('—')], [hint('—')]] }
|
return { cells: [[hint('—')], [hint('—')]] }
|
||||||
return {
|
return {
|
||||||
cells: [say.name(item.name, 'type'), item.alias ? say.name(item.alias, 'type') : [hint('—')]],
|
cells: [[sourceName(item.name, 'type')], item.alias ? [sourceName(item.alias, 'type')] : [hint('—')]],
|
||||||
defines: range(item.name),
|
defines: range(item.name),
|
||||||
}
|
}
|
||||||
}),
|
}),
|
||||||
@@ -62,8 +61,8 @@ export function structRows(block: ClassBlock, say: RustSay): PropertyRow[] {
|
|||||||
visibilityRow(block),
|
visibilityRow(block),
|
||||||
...tableRow('字段', ['字段', '类型', '可见性', ...(described ? ['说明'] : [])], fields.map(field => ({
|
...tableRow('字段', ['字段', '类型', '可见性', ...(described ? ['说明'] : [])], fields.map(field => ({
|
||||||
cells: [
|
cells: [
|
||||||
say.name(field.name, 'variable'),
|
[sourceName(field.name, 'variable')],
|
||||||
say.source(field.type, 'type'),
|
say.verbatim(field.type),
|
||||||
[keyword(VISIBILITY[field.visibility])],
|
[keyword(VISIBILITY[field.visibility])],
|
||||||
...(described ? [paramDocCell(docs, field.name.text)] : []),
|
...(described ? [paramDocCell(docs, field.name.text)] : []),
|
||||||
],
|
],
|
||||||
@@ -176,7 +175,7 @@ export function enumRows(block: EnumBlock, say: RustSay): PropertyRow[] {
|
|||||||
nameRow(block.facts.name, say, 'type'),
|
nameRow(block.facts.name, say, 'type'),
|
||||||
...docRow(block.facts.doc && say.doc(block.facts.doc)),
|
...docRow(block.facts.doc && say.doc(block.facts.doc)),
|
||||||
...tableRow('变体', ['变体', '携带的数据'], block.facts.members.map(member => ({
|
...tableRow('变体', ['变体', '携带的数据'], block.facts.members.map(member => ({
|
||||||
cells: [say.name(member.name, 'type'), [hint(variantPayload(member.value) ?? '—')]],
|
cells: [[sourceName(member.name, 'type')], payloadValue(member.value, say)],
|
||||||
defines: range(member.name),
|
defines: range(member.name),
|
||||||
}))),
|
}))),
|
||||||
]
|
]
|
||||||
@@ -200,13 +199,13 @@ export function functionRows(block: FunctionBlock, say: RustSay): PropertyRow[]
|
|||||||
...(visible ? [visibilityRow(block)] : []),
|
...(visible ? [visibilityRow(block)] : []),
|
||||||
...tableRow(isMethod(block.node) ? '参数' : '输入参数', ['名称', '类型', ...(described ? ['说明'] : [])], parameters.map(param => ({
|
...tableRow(isMethod(block.node) ? '参数' : '输入参数', ['名称', '类型', ...(described ? ['说明'] : [])], parameters.map(param => ({
|
||||||
cells: [
|
cells: [
|
||||||
say.name(param.name, 'variable'),
|
[sourceName(param.name, 'variable')],
|
||||||
param.type ? say.source(param.type, 'type') : [hint('—')],
|
param.type ? say.verbatim(param.type) : [hint('—')],
|
||||||
...(described ? [paramDocCell(docs, param.name.text)] : []),
|
...(described ? [paramDocCell(docs, param.name.text)] : []),
|
||||||
],
|
],
|
||||||
defines: range(param.name),
|
defines: range(param.name),
|
||||||
}))),
|
}))),
|
||||||
...(returnType || isFnItem(block.node) ? [{ label: '返回', value: [plain(returnType ? oneLine(returnType.text) : '()')], ...returnNote(doc) }] : []),
|
...(returnType || isFnItem(block.node) ? [{ label: '返回', value: returnValue(returnType, say), ...returnNote(doc) }] : []),
|
||||||
...throwsRows(doc),
|
...throwsRows(doc),
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
@@ -221,7 +220,7 @@ export function traitRows(block: TraitBlock, say: RustSay): PropertyRow[] {
|
|||||||
|
|
||||||
export function implRows(block: ImplBlock, say: RustSay): PropertyRow[] {
|
export function implRows(block: ImplBlock, say: RustSay): PropertyRow[] {
|
||||||
const title: Inline[] = block.facts.name
|
const title: Inline[] = block.facts.name
|
||||||
? [...say.name(block.facts.name, 'type'), plain(' 的方法')]
|
? [sourceName(block.facts.name, 'type'), plain(' 的方法')]
|
||||||
: [hint('实现')]
|
: [hint('实现')]
|
||||||
return [
|
return [
|
||||||
{ label: '名称', value: title, ...(block.facts.name && { defines: range(block.facts.name) }) },
|
{ label: '名称', value: title, ...(block.facts.name && { defines: range(block.facts.name) }) },
|
||||||
@@ -237,9 +236,9 @@ function methodRows(block: TraitBlock | ImplBlock, say: RustSay): PropertyRow[]
|
|||||||
const name = method.facts.name
|
const name = method.facts.name
|
||||||
return {
|
return {
|
||||||
cells: [
|
cells: [
|
||||||
name ? say.name(name, 'function') : [hint('匿名')],
|
name ? [sourceName(name, 'function')] : [hint('匿名')],
|
||||||
[plain(signatureParams(method.node))],
|
joined(signatureParams(method.node).map(param => say.verbatim(param)), plain(', ')),
|
||||||
[plain(returnText(method.node))],
|
returnValue(method.facts.returnType, say),
|
||||||
],
|
],
|
||||||
...(name && { defines: range(name) }),
|
...(name && { defines: range(name) }),
|
||||||
}
|
}
|
||||||
@@ -249,7 +248,7 @@ function methodRows(block: TraitBlock | ImplBlock, say: RustSay): PropertyRow[]
|
|||||||
function nameRow(name: Node | undefined, say: RustSay, role: TokenRole): PropertyRow {
|
function nameRow(name: Node | undefined, say: RustSay, role: TokenRole): PropertyRow {
|
||||||
return {
|
return {
|
||||||
label: '名称',
|
label: '名称',
|
||||||
value: name ? say.name(name, role) : [hint('匿名')],
|
value: name ? [sourceName(name, role)] : [hint('匿名')],
|
||||||
...(name && { defines: range(name) }),
|
...(name && { defines: range(name) }),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -270,14 +269,6 @@ function tableRow(label: string, columns: string[], items: PropertyItem[]): Prop
|
|||||||
return [{ label, value: [hint(`${items.length} 个`)], columns, items }]
|
return [{ label, value: [hint(`${items.length} 个`)], columns, items }]
|
||||||
}
|
}
|
||||||
|
|
||||||
function hint(text: string): Inline {
|
|
||||||
return { text, role: 'hint' }
|
|
||||||
}
|
|
||||||
|
|
||||||
function plain(text: string): Inline {
|
|
||||||
return { text, role: 'plain' }
|
|
||||||
}
|
|
||||||
|
|
||||||
function keyword(text: string): Inline {
|
function keyword(text: string): Inline {
|
||||||
return { text, role: 'keyword' }
|
return { text, role: 'keyword' }
|
||||||
}
|
}
|
||||||
@@ -290,14 +281,13 @@ function note(text: string): Inline {
|
|||||||
return { text, role: 'comment' }
|
return { text, role: 'comment' }
|
||||||
}
|
}
|
||||||
|
|
||||||
function range(node: Node): { startIndex: number, endIndex: number } {
|
/** 函数的返回类型 `returned` 照抄成一行,名字各自带源码下标;没写返回类型时是普通文字 `()`。 */
|
||||||
return { startIndex: node.startIndex, endIndex: node.endIndex }
|
export function returnValue(returned: Node | null | undefined, say: RustSay): Inline[] {
|
||||||
}
|
return returned ? say.verbatim(returned) : [plain('()')]
|
||||||
/** 名字、类型照抄源码,名字带上下标,点了能跳到定义。 */
|
|
||||||
export function plainSay(doc: RustSay['doc']): RustSay {
|
|
||||||
return {
|
|
||||||
name: (node, role) => [{ text: node.text, role, offset: node.startIndex }],
|
|
||||||
source: (node, role = 'type') => [{ text: oneLine(node.text), role, offset: node.startIndex }],
|
|
||||||
doc,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** 变体携带的数据照抄成一行:元组变体的各个类型用逗号隔开,结构变体是字段表原文;没带数据写「—」。`body` 是变体的 body。 */
|
||||||
|
export function payloadValue(body: Node | undefined, say: RustSay): Inline[] {
|
||||||
|
const parts = variantPayload(body)
|
||||||
|
return parts.length > 0 ? joined(parts.map(part => say.verbatim(part)), plain(', ')) : [hint('—')]
|
||||||
}
|
}
|
||||||
@@ -69,29 +69,16 @@ export function visibilityFacts(node: Node): { access?: Access, attributes?: Blo
|
|||||||
return visibility === 'public' ? { access: 'public' } : { attributes: { access: visibility } }
|
return visibility === 'public' ? { access: 'public' } : { attributes: { access: visibility } }
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 参数表括号里的原文,空白收成一行。 */
|
/** 参数表括号里的各个参数(含 `self`),属性、注释不算。 */
|
||||||
export function signatureParams(fn: Node): string {
|
export function signatureParams(fn: Node): Node[] {
|
||||||
const params = fn.childForFieldName('parameters')
|
return named(fn.childForFieldName('parameters')).filter(child => child.type !== 'attribute_item' && !child.isExtra)
|
||||||
if (!params)
|
|
||||||
return ''
|
|
||||||
return oneLine(params.text.replace(/^\(/, '').replace(/\)$/, ''))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 返回类型原文;没写是 ()。 */
|
/** 变体携带的数据:元组变体是各个类型,结构变体是整个字段表 `{ … }`,没带数据是空的。`body` 是变体的 body。 */
|
||||||
export function returnText(fn: Node): string {
|
export function variantPayload(body: Node | undefined): Node[] {
|
||||||
const returned = fn.childForFieldName('return_type')
|
if (body?.type === 'ordered_field_declaration_list')
|
||||||
return returned ? oneLine(returned.text) : '()'
|
return named(body).filter(child => child.type !== 'attribute_item' && !child.isExtra)
|
||||||
}
|
return body ? [body] : []
|
||||||
|
|
||||||
/** 变体携带的数据:没带是空的,元组变体是类型,结构变体是字段原文。body 是变体的 body。 */
|
|
||||||
export function variantPayload(body: Node | undefined): string | undefined {
|
|
||||||
if (!body)
|
|
||||||
return undefined
|
|
||||||
if (body.type === 'ordered_field_declaration_list') {
|
|
||||||
const types = named(body).filter(child => child.type !== 'attribute_item').map(child => oneLine(child.text))
|
|
||||||
return types.join(', ') || undefined
|
|
||||||
}
|
|
||||||
return oneLine(body.text)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 写在这条前面、紧挨着的属性(#[…]),从外往里。 */
|
/** 写在这条前面、紧挨着的属性(#[…]),从外往里。 */
|
||||||
|
|||||||
@@ -1,8 +1,9 @@
|
|||||||
import type { ClassBlock, Inline, KindBadge, LibraryRule, Node, PropertyRow, RuleCard, RuleContext, SegmentGroup, ThemeColor, TokenRole } from '../contract'
|
import type { ClassBlock, Inline, KindBadge, LibraryRule, Node, PropertyRow, RuleCard, RuleContext, SegmentGroup, ThemeColor } from '../contract'
|
||||||
import type { rustVisibility } from './rustTree'
|
import type { rustVisibility } from './rustTree'
|
||||||
import { paramDocCell, paramDocs, unmatchedParamRows } from './docRows'
|
import { paramDocCell, paramDocs, unmatchedParamRows } from './docRows'
|
||||||
import { visibilityWord } from './rustRows'
|
import { visibilityWord } from './rustRows'
|
||||||
import { attributeName, deriveNames, docSummary, itemAttributes, leadingAttributes, named, oneLine, stringContent, structFields } from './rustTree'
|
import { attributeName, deriveNames, docSummary, itemAttributes, leadingAttributes, named, stringContent, structFields } from './rustTree'
|
||||||
|
import { name as sourceName } from './segments'
|
||||||
|
|
||||||
/** 和结构体同一个青,序列化只是名称后的标签,不另换边框色。 */
|
/** 和结构体同一个青,序列化只是名称后的标签,不另换边框色。 */
|
||||||
const COLOR: ThemeColor = { dark: '#4ec9b0', light: '#267f99' }
|
const COLOR: ThemeColor = { dark: '#4ec9b0', light: '#267f99' }
|
||||||
@@ -36,7 +37,7 @@ function card(block: ClassBlock, context: RuleContext): RuleCard {
|
|||||||
const rows: PropertyRow[] = [
|
const rows: PropertyRow[] = [
|
||||||
{
|
{
|
||||||
label: '名称',
|
label: '名称',
|
||||||
value: block.facts.name ? [...copied(block.facts.name, 'type'), plain(' '), tag('可序列化')] : [tag('可序列化')],
|
value: block.facts.name ? [sourceName(block.facts.name, 'type'), plain(' '), tag('可序列化')] : [tag('可序列化')],
|
||||||
...(block.facts.name && { defines: range(block.facts.name) }),
|
...(block.facts.name && { defines: range(block.facts.name) }),
|
||||||
},
|
},
|
||||||
...(summary ? [{ label: '说明', value: { markdown: summary } }] : []),
|
...(summary ? [{ label: '说明', value: { markdown: summary } }] : []),
|
||||||
@@ -50,8 +51,8 @@ function card(block: ClassBlock, context: RuleContext): RuleCard {
|
|||||||
columns: ['字段', '类型', '可见性', '序列化', ...(described ? ['说明'] : [])],
|
columns: ['字段', '类型', '可见性', '序列化', ...(described ? ['说明'] : [])],
|
||||||
items: fields.map(field => ({
|
items: fields.map(field => ({
|
||||||
cells: [
|
cells: [
|
||||||
copied(field.name, 'variable'),
|
[sourceName(field.name, 'variable')],
|
||||||
copied(field.type, 'type'),
|
context.verbatim(field.type),
|
||||||
[keyword(visibilityWordOf(field.visibility))],
|
[keyword(visibilityWordOf(field.visibility))],
|
||||||
[serdeCell(field.name)],
|
[serdeCell(field.name)],
|
||||||
...(described ? [paramDocCell(docs, field.name.text)] : []),
|
...(described ? [paramDocCell(docs, field.name.text)] : []),
|
||||||
@@ -125,10 +126,6 @@ function firstString(tree: Node | undefined): Node | undefined {
|
|||||||
return tree?.children.find(child => child?.type === 'string_literal') ?? undefined
|
return tree?.children.find(child => child?.type === 'string_literal') ?? undefined
|
||||||
}
|
}
|
||||||
|
|
||||||
function copied(node: Node, role: TokenRole): Inline[] {
|
|
||||||
return [{ text: oneLine(node.text), role, offset: node.startIndex }]
|
|
||||||
}
|
|
||||||
|
|
||||||
function tag(label: string): SegmentGroup {
|
function tag(label: string): SegmentGroup {
|
||||||
return { style: 'tag', label, segments: [], color: COLOR }
|
return { style: 'tag', label, segments: [], color: COLOR }
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import type { Inline, KindBadge, LibraryRule, Node, PropertyRow, RuleCard, ThemeColor } from '../contract'
|
import type { Inline, KindBadge, LibraryRule, Node, PropertyRow, RuleCard, RuleContext, ThemeColor } from '../contract'
|
||||||
import { macroInvocation, named, oneLine, stringContent } from './rustTree'
|
import { macroInvocation, named, oneLine, stringContent } from './rustTree'
|
||||||
|
|
||||||
/** 查询卡用字符串那种橙。 */
|
/** 查询卡用字符串那种橙。 */
|
||||||
@@ -15,14 +15,14 @@ export const sqlxRule: LibraryRule = {
|
|||||||
label: 'sqlx 查询',
|
label: 'sqlx 查询',
|
||||||
languageIds: ['rust'],
|
languageIds: ['rust'],
|
||||||
libraries: ['sqlx'],
|
libraries: ['sqlx'],
|
||||||
claim: (block) => {
|
claim: (block, context) => {
|
||||||
const macro = block.kind === 'statement' ? findQuery(block.node) : undefined
|
const macro = block.kind === 'statement' ? findQuery(block.node) : undefined
|
||||||
return macro ? card(macro) : undefined
|
return macro ? card(macro, context) : undefined
|
||||||
},
|
},
|
||||||
attributeNames: () => ({ attributes: {}, values: { kind: { 'sqlx.query': '查询' } } }),
|
attributeNames: () => ({ attributes: {}, values: { kind: { 'sqlx.query': '查询' } } }),
|
||||||
}
|
}
|
||||||
|
|
||||||
function card(macro: Node): RuleCard {
|
function card(macro: Node, context: RuleContext): RuleCard {
|
||||||
const call = macroInvocation(macro)
|
const call = macroInvocation(macro)
|
||||||
const args = call?.args ?? []
|
const args = call?.args ?? []
|
||||||
const typed = call?.name.split('::').pop() !== 'query'
|
const typed = call?.name.split('::').pop() !== 'query'
|
||||||
@@ -35,7 +35,7 @@ function card(macro: Node): RuleCard {
|
|||||||
...(params.length > 0
|
...(params.length > 0
|
||||||
? [{ label: '参数', value: [{ text: params.map((param, index) => `$${index + 1} = ${oneLine(param.text)}`).join('、'), role: 'plain' as const }] }]
|
? [{ label: '参数', value: [{ text: params.map((param, index) => `$${index + 1} = ${oneLine(param.text)}`).join('、'), role: 'plain' as const }] }]
|
||||||
: []),
|
: []),
|
||||||
{ label: '返回类型', value: returned ? [{ text: oneLine(returned.text), role: 'type', offset: returned.startIndex }] : [hint('—')] },
|
{ label: '返回类型', value: returned ? context.verbatim(returned, 'type') : [hint('—')] },
|
||||||
]
|
]
|
||||||
return { kind: 'sqlx.query', badge: BADGE, rows, absorbs: [] }
|
return { kind: 'sqlx.query', badge: BADGE, rows, absorbs: [] }
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
import type { FunctionBlock, Inline, KindBadge, LibraryRule, Node, PropertyRow, RuleCard, RuleContext, SegmentGroup, ThemeColor } from '../contract'
|
import type { FunctionBlock, Inline, KindBadge, LibraryRule, Node, PropertyRow, RuleCard, RuleContext, SegmentGroup, ThemeColor } from '../contract'
|
||||||
import { paramDocCell, paramDocs, returnNote, throwsRows, unmatchedParamRows } from './docRows'
|
import { paramDocCell, paramDocs, returnNote, throwsRows, unmatchedParamRows } from './docRows'
|
||||||
import { attributeName, docSummary, oneLine, returnText } from './rustTree'
|
import { returnValue } from './rustRows'
|
||||||
|
import { attributeName, docSummary } from './rustTree'
|
||||||
|
|
||||||
/** 桌面命令的边框用这个橙。 */
|
/** 桌面命令的边框用这个橙。 */
|
||||||
const COLOR: ThemeColor = { dark: '#e5c07b', light: '#9a6a00' }
|
const COLOR: ThemeColor = { dark: '#e5c07b', light: '#9a6a00' }
|
||||||
@@ -49,7 +50,7 @@ function card(block: FunctionBlock, context: RuleContext): RuleCard {
|
|||||||
items: parameters.map(param => ({
|
items: parameters.map(param => ({
|
||||||
cells: [
|
cells: [
|
||||||
[variable(param.name)],
|
[variable(param.name)],
|
||||||
[param.type ? typed(param.type) : hint('—')],
|
param.type ? context.verbatim(param.type) : [hint('—')],
|
||||||
[hint(isWindowState(param.type) ? '窗口状态' : '调用方传入')],
|
[hint(isWindowState(param.type) ? '窗口状态' : '调用方传入')],
|
||||||
...(described ? [paramDocCell(docs, param.name.text)] : []),
|
...(described ? [paramDocCell(docs, param.name.text)] : []),
|
||||||
],
|
],
|
||||||
@@ -57,7 +58,7 @@ function card(block: FunctionBlock, context: RuleContext): RuleCard {
|
|||||||
})),
|
})),
|
||||||
}]
|
}]
|
||||||
: []),
|
: []),
|
||||||
{ label: '返回', value: [plain(returnText(block.node))], ...returnNote(doc) },
|
{ label: '返回', value: returnValue(block.facts.returnType, context), ...returnNote(doc) },
|
||||||
...throwsRows(doc),
|
...throwsRows(doc),
|
||||||
]
|
]
|
||||||
return {
|
return {
|
||||||
@@ -81,10 +82,6 @@ function variable(node: Node): Inline {
|
|||||||
return { text: node.text, role: 'variable', offset: node.startIndex }
|
return { text: node.text, role: 'variable', offset: node.startIndex }
|
||||||
}
|
}
|
||||||
|
|
||||||
function typed(node: Node): Inline {
|
|
||||||
return { text: oneLine(node.text), role: 'type', offset: node.startIndex }
|
|
||||||
}
|
|
||||||
|
|
||||||
function tag(label: string): SegmentGroup {
|
function tag(label: string): SegmentGroup {
|
||||||
return { style: 'tag', label, segments: [], color: COLOR }
|
return { style: 'tag', label, segments: [], color: COLOR }
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import type { EnumBlock, Inline, KindBadge, LibraryRule, Node, PreviewText, PropertyRow, RuleCard, RuleContext, SegmentGroup, ThemeColor } from '../contract'
|
import type { EnumBlock, Inline, KindBadge, LibraryRule, Node, PreviewText, PropertyRow, RuleCard, RuleContext, SegmentGroup, ThemeColor } from '../contract'
|
||||||
import { attributeName, commentPlain, deriveNames, docSummary, firstSentence, named, stringContent, variantPayload } from './rustTree'
|
import { payloadValue } from './rustRows'
|
||||||
|
import { attributeName, commentPlain, deriveNames, docSummary, firstSentence, named, stringContent } from './rustTree'
|
||||||
|
|
||||||
/** 错误卡用异常那种红。 */
|
/** 错误卡用异常那种红。 */
|
||||||
const COLOR: ThemeColor = { dark: '#f14c4c', light: '#cd3131' }
|
const COLOR: ThemeColor = { dark: '#f14c4c', light: '#cd3131' }
|
||||||
@@ -39,7 +40,7 @@ function card(block: EnumBlock, context: RuleContext): RuleCard {
|
|||||||
[hint(whenText(variant) ?? '—')],
|
[hint(whenText(variant) ?? '—')],
|
||||||
display
|
display
|
||||||
? [plain(display), ...(passesSource(variant) ? [plain(' '), tag('透传来源')] : [])]
|
? [plain(display), ...(passesSource(variant) ? [plain(' '), tag('透传来源')] : [])]
|
||||||
: [hint(variantPayload(member.value) ?? '—')],
|
: payloadValue(member.value, context),
|
||||||
]
|
]
|
||||||
return { cells, defines: range(member.name) }
|
return { cells, defines: range(member.name) }
|
||||||
}),
|
}),
|
||||||
|
|||||||
@@ -122,6 +122,28 @@ export function inlineOf(text: PreviewText): Inline[] {
|
|||||||
return 'markdown' in text ? [] : text
|
return 'markdown' in text ? [] : text
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** 卡片上叫 `label` 的那一行;没有这一行就抛错。 */
|
||||||
|
export function propertyRow(block: BlockView | undefined, label: string): PropertyRow {
|
||||||
|
const row = block && 'properties' in block ? block.properties.find(item => item.label === label) : undefined
|
||||||
|
if (!row)
|
||||||
|
throw new Error(`没有「${label}」这一行`)
|
||||||
|
return row
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 预览文字里按 `role` 着色的词:各自的文字和源码下标(没带下标的是 `undefined`)。 */
|
||||||
|
export function wordsOf(text: PreviewText | undefined, role: TokenRole): [string, number | undefined][] {
|
||||||
|
return flatSegments(text ? inlineOf(text) : []).filter(segment => segment.role === role).map(segment => [segment.text, segment.offset])
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 预览文字里按类型着色的词的文字;每个词都要带下标,下标在 `source` 里正好是它自己,否则抛错。 */
|
||||||
|
export function typeNames(text: PreviewText | undefined, source: string): string[] {
|
||||||
|
return wordsOf(text, 'type').map(([word, offset]) => {
|
||||||
|
if (offset === undefined || source.slice(offset, offset + word.length) !== word)
|
||||||
|
throw new Error(`「${word}」的下标 ${offset} 对不上源码`)
|
||||||
|
return word
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
/** 子行写成文字:各列用 ` | ` 隔开,下一层子行、排在最后一格里的表(PropertyItem.table)的各行比这一层多缩进两格。 */
|
/** 子行写成文字:各列用 ` | ` 隔开,下一层子行、排在最后一格里的表(PropertyItem.table)的各行比这一层多缩进两格。 */
|
||||||
function itemLines(items: PropertyItem[] | undefined, indent: string): string[] {
|
function itemLines(items: PropertyItem[] | undefined, indent: string): string[] {
|
||||||
return (items ?? []).flatMap(item => [
|
return (items ?? []).flatMap(item => [
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import type { FactBlock, Inline, Node, PropertyRow, SourceDocument, SourceSpan, TextBlock, TextTranslation, Translator } from '../contract'
|
import type { FactBlock, Inline, Node, PropertyRow, SourceDocument, SourceSpan, TextBlock, TextTranslation, Translator } from '../contract'
|
||||||
import { attributeRows, enumRows, functionRows, implRows, importRows, moduleRows, plainSay, structRows, traitRows } from '../rules/rustRows'
|
import type { RustSay } from '../rules/rustRows'
|
||||||
|
import { attributeRows, enumRows, functionRows, implRows, importRows, moduleRows, structRows, traitRows } from '../rules/rustRows'
|
||||||
import { isCrateRoot } from '../rules/rustTree'
|
import { isCrateRoot } from '../rules/rustTree'
|
||||||
import { createZhTemplateTranslator, KIND_ZH, ZH_ATTRIBUTE_NAMES } from './zhTemplate'
|
import { createZhTemplateTranslator, KIND_ZH, ZH_ATTRIBUTE_NAMES } from './zhTemplate'
|
||||||
|
|
||||||
@@ -93,7 +94,7 @@ function translate(block: TextBlock, document: SourceDocument): TextTranslation
|
|||||||
}
|
}
|
||||||
|
|
||||||
function describe(block: FactBlock, document: SourceDocument): PropertyRow[] {
|
function describe(block: FactBlock, document: SourceDocument): PropertyRow[] {
|
||||||
const say = plainSay(node => template.doc(node, document))
|
const say: RustSay = { verbatim: (node, role) => template.verbatim(node, document, role), doc: node => template.doc(node, document) }
|
||||||
switch (block.kind) {
|
switch (block.kind) {
|
||||||
case 'import':
|
case 'import':
|
||||||
return importRows(block, say)
|
return importRows(block, say)
|
||||||
|
|||||||
@@ -717,8 +717,12 @@ export function createZhTemplateTranslator(table: ZhTable): Translator {
|
|||||||
return out.tokens
|
return out.tokens
|
||||||
}
|
}
|
||||||
|
|
||||||
/** node 的源码原样照抄:叶子按源码着色(给了 role 就都按它),叶子之间的空白、标点照抄。 */
|
/**
|
||||||
function verbatim(node: Node, { source, language }: SourceDocument, role?: TokenRole): Segment[] {
|
* `node` 的源码原样照抄:叶子按源码着色(给了 `role` 就都按它),名字带源码下标,叶子之间的空白、标点照抄;
|
||||||
|
* `flat` 为真时压成一行,连着的空白(含换行)收成一个空格。
|
||||||
|
*/
|
||||||
|
function verbatim(node: Node, { source, language }: SourceDocument, role?: TokenRole, flat = false): Segment[] {
|
||||||
|
const copy = (text: string): string => flat ? text.replace(/\s+/g, ' ') : text
|
||||||
const segments: Segment[] = []
|
const segments: Segment[] = []
|
||||||
let cursor = node.startIndex
|
let cursor = node.startIndex
|
||||||
const visit = (current: Node): void => {
|
const visit = (current: Node): void => {
|
||||||
@@ -731,12 +735,12 @@ export function createZhTemplateTranslator(table: ZhTable): Translator {
|
|||||||
}
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
pushSegment(segments, source.slice(cursor, current.startIndex), 'plain')
|
pushSegment(segments, copy(source.slice(cursor, current.startIndex)), 'plain')
|
||||||
pushSegment(segments, current.text, role ?? language.tokenRole(current), offsetOf(current, language))
|
pushSegment(segments, copy(current.text), role ?? language.tokenRole(current), offsetOf(current, language))
|
||||||
cursor = current.endIndex
|
cursor = current.endIndex
|
||||||
}
|
}
|
||||||
visit(node)
|
visit(node)
|
||||||
pushSegment(segments, source.slice(cursor, node.endIndex), 'plain')
|
pushSegment(segments, copy(source.slice(cursor, node.endIndex)), 'plain')
|
||||||
return segments
|
return segments
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -843,6 +847,7 @@ export function createZhTemplateTranslator(table: ZhTable): Translator {
|
|||||||
translate,
|
translate,
|
||||||
describe,
|
describe,
|
||||||
expression,
|
expression,
|
||||||
|
verbatim: (node, document, role) => verbatim(node, document, role, true),
|
||||||
doc,
|
doc,
|
||||||
attributeNames: ZH_ATTRIBUTE_NAMES,
|
attributeNames: ZH_ATTRIBUTE_NAMES,
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in new issue
Block a user