🐛 fix(vscode): 代码阅读把 Python 赋值后面的字符串当变量说明,其余单独的字符串显示成注释
This commit is contained in:
1 parent
a51f90e34d
commit
1f86201373
14 files changed
+358
-59
No files matched your search
@@ -9,11 +9,41 @@ type Scope = 'module' | 'class' | 'other'
|
||||
const CONSTANT_NAME = /^[A-Z][A-Z0-9_]*$/
|
||||
const PARAMETER_SEPARATORS = new Set(['keyword_separator', 'positional_separator'])
|
||||
const PATTERNS = new Set(['pattern_list', 'tuple_pattern', 'list_pattern'])
|
||||
/** 字符串开头(string_start):普通字符串的前缀只有 r / u 或没有;f、t、b 开头的不是。 */
|
||||
const STRING_PREFIX = /^[ru]?["']/i
|
||||
// 只有 # 行注释;函数、类的说明是主体开头的 docstring,不是写在前面的注释
|
||||
const COMMENTS: CommentRules = { isLine: () => true, isDoc: () => false }
|
||||
|
||||
function statements(items: Node[], scope: Scope, afterRow: number, docstring: boolean): CodeBlock[] {
|
||||
return listBlocks(items, afterRow, (item, first) => statementBlocks(item, scope, docstring && first), COMMENTS)
|
||||
function statements(items: Node[], scope: Scope, afterRow: number): CodeBlock[] {
|
||||
const docs = attributeDocs(items, scope)
|
||||
const attached = new Set([...docs.values()].map(statement => statement.startIndex))
|
||||
return listBlocks(items, afterRow, (item) => {
|
||||
if (attached.has(item.startIndex))
|
||||
return []
|
||||
const docStatement = docs.get(item.startIndex)
|
||||
return statementBlocks(item, scope, docStatement && docstringIn(docStatement))
|
||||
}, COMMENTS)
|
||||
}
|
||||
|
||||
/**
|
||||
* 属性 docstring(PEP 257):模块、类主体里紧跟在一条赋值后面的字符串是这个变量的说明。返回赋值语句的起始下标 → 那条字符串语句。
|
||||
* 同 JSDoc 挂到下一条语句:中间可以隔空行和赋值的行尾注释,隔着别的注释、语句就不算。
|
||||
*/
|
||||
function attributeDocs(items: Node[], scope: Scope): Map<number, Node> {
|
||||
const docs = new Map<number, Node>()
|
||||
if (scope === 'other')
|
||||
return docs
|
||||
items.forEach((item, i) => {
|
||||
if (!assignmentIn(item))
|
||||
return
|
||||
let next = i + 1
|
||||
while (items[next]?.type === 'comment' && items[next].startPosition.row === item.endPosition.row)
|
||||
next++
|
||||
const statement = items[next]
|
||||
if (statement && docstringIn(statement))
|
||||
docs.set(item.startIndex, statement)
|
||||
})
|
||||
return docs
|
||||
}
|
||||
|
||||
/** 冒号后面的主体:body / consequence 字段,except / finally 子句里是不带字段名的 block。 */
|
||||
@@ -25,17 +55,18 @@ function bodyOf(node: Node): Node | null {
|
||||
return blocks.at(-1) ?? null
|
||||
}
|
||||
|
||||
function bodyShape(outer: Node, body: Node | null, scope: Scope, docstring = false): BlockShape {
|
||||
function bodyShape(outer: Node, body: Node | null, scope: Scope): BlockShape {
|
||||
if (!body)
|
||||
return leafShape(outer)
|
||||
return compoundShape(outer, body.startIndex, named(body), (list, afterRow) => statements(list, scope, afterRow, docstring))
|
||||
return compoundShape(outer, body.startIndex, named(body), (list, afterRow) => statements(list, scope, afterRow))
|
||||
}
|
||||
|
||||
function bodyBlock(kind: TextKind, outer: Node, body: Node | null, scope: Scope): CodeBlock {
|
||||
return { kind, ...bodyShape(outer, body, scope) }
|
||||
}
|
||||
|
||||
function statementBlocks(node: Node, scope: Scope, docstring: boolean): CodeBlock[] {
|
||||
/** doc 是紧跟在这条赋值后面的属性 docstring(见 attributeDocs)。 */
|
||||
function statementBlocks(node: Node, scope: Scope, doc: Node | undefined): CodeBlock[] {
|
||||
switch (node.type) {
|
||||
case 'import_statement':
|
||||
case 'import_from_statement':
|
||||
@@ -64,7 +95,7 @@ function statementBlocks(node: Node, scope: Scope, docstring: boolean): CodeBloc
|
||||
case 'return_statement':
|
||||
return [leafBlock('return', node)]
|
||||
case 'expression_statement':
|
||||
return [expressionBlock(node, scope, docstring)]
|
||||
return [expressionBlock(node, scope, doc)]
|
||||
default:
|
||||
return [leafBlock('statement', node)]
|
||||
}
|
||||
@@ -87,18 +118,30 @@ function docstringOf(body: Node | null): Node | undefined {
|
||||
return first && docstringIn(first)
|
||||
}
|
||||
|
||||
/** 语句是单独一个字符串表达式(docstring 的写法)时返回那个字符串节点。 */
|
||||
/**
|
||||
* 语句是单独一个字符串字面量(docstring 的写法,隐式拼接的几段也算)时返回那个字符串节点。
|
||||
* f-string、t-string、字节串不算:Python 不把它们当 docstring。
|
||||
*/
|
||||
function docstringIn(statement: Node): Node | undefined {
|
||||
if (statement.type !== 'expression_statement')
|
||||
return undefined
|
||||
const [expression, ...rest] = named(statement)
|
||||
return rest.length === 0 && (expression?.type === 'string' || expression?.type === 'concatenated_string') ? expression : undefined
|
||||
if (!expression || rest.length > 0)
|
||||
return undefined
|
||||
const parts = expression.type === 'concatenated_string' ? named(expression) : [expression]
|
||||
return parts.every(isTextString) ? expression : undefined
|
||||
}
|
||||
|
||||
/** 普通字符串字面量:前缀只有 r / u(不分大小写)或没有。 */
|
||||
function isTextString(node: Node): boolean {
|
||||
const start = node.type === 'string' ? node.firstChild : null
|
||||
return start?.type === 'string_start' && STRING_PREFIX.test(start.text)
|
||||
}
|
||||
|
||||
/** 函数、类的 docstring 是它自己的说明:算进头部(连同它前面的注释),子块从它后面开始。 */
|
||||
function documentedShape(outer: Node, body: Node, doc: Node, scope: Scope): BlockShape {
|
||||
const rest = named(body).filter(item => item.startIndex >= doc.endIndex)
|
||||
return compoundShape(outer, doc.endIndex, rest, (list, afterRow) => statements(list, scope, afterRow, false))
|
||||
return compoundShape(outer, doc.endIndex, rest, (list, afterRow) => statements(list, scope, afterRow))
|
||||
}
|
||||
|
||||
/** 单下划线开头的名字是私有(`_x`、`__x`),前后双下划线的特殊方法(`__init__`)是公共。 */
|
||||
@@ -219,28 +262,40 @@ function isConstantTarget(target: Node | null): boolean {
|
||||
return false
|
||||
}
|
||||
|
||||
/** 赋值 → 字段 / 常量 / 变量;主体开头的字符串是 docstring;其余表达式是语句。 */
|
||||
function expressionBlock(node: Node, scope: Scope, docstring: boolean): CodeBlock {
|
||||
if (docstring && docstringIn(node))
|
||||
/**
|
||||
* 赋值 → 字段 / 常量 / 变量,doc 是它的属性 docstring:进字段的 doc、常量和变量的 facts.doc,头部往下延到字符串结尾;
|
||||
* 单独一个字符串(模块开头的 docstring、写在语句中间当注释用的字符串)是注释;其余表达式是语句。
|
||||
*/
|
||||
function expressionBlock(node: Node, scope: Scope, doc: Node | undefined): CodeBlock {
|
||||
if (docstringIn(node))
|
||||
return leafBlock('comment', node)
|
||||
const expression = named(node)[0]
|
||||
const left = expression?.type === 'assignment' ? expression.childForFieldName('left') : null
|
||||
const expression = assignmentIn(node)
|
||||
const left = expression?.childForFieldName('left')
|
||||
if (!expression || !left)
|
||||
return leafBlock('statement', node)
|
||||
const leaf = leafShape(node)
|
||||
const shape = doc ? { ...leaf, header: { ...leaf.header, endIndex: doc.endIndex, endRow: doc.endPosition.row } } : leaf
|
||||
const type = expression.childForFieldName('type')
|
||||
if (scope === 'class' && type)
|
||||
return leafBlock('field', node)
|
||||
return { kind: 'field', ...shape, doc }
|
||||
const value = expression.childForFieldName('right')
|
||||
return {
|
||||
kind: scope === 'module' && isConstantTarget(left) ? 'const' : 'variable',
|
||||
...leafShape(node),
|
||||
...shape,
|
||||
facts: {
|
||||
access: scopeAccess(left, scope),
|
||||
doc,
|
||||
declarators: [{ name: left, type: type ?? undefined, value: value ?? undefined, entries: dictionaryEntries(value) }],
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
/** 语句是一条赋值(带不带注解、有没有值)时返回那个 assignment 节点。 */
|
||||
function assignmentIn(statement: Node): Node | undefined {
|
||||
const expression = statement.type === 'expression_statement' ? named(statement)[0] : undefined
|
||||
return expression?.type === 'assignment' ? expression : undefined
|
||||
}
|
||||
|
||||
/** 字典字面量的各项:`键: 值`(值又是字典时连它的各项)、`**other`;值不是字典字面量时没有。 */
|
||||
function dictionaryEntries(value: Node | null): EntryFacts[] | undefined {
|
||||
if (value?.type !== 'dictionary')
|
||||
@@ -428,7 +483,7 @@ export function createPythonAdapter(wasmDir: string): LanguageAdapter {
|
||||
id: 'python',
|
||||
languageIds: ['python'],
|
||||
grammarPath: () => path.join(wasmDir, 'tree-sitter-python.wasm'),
|
||||
toBlocks: root => statements(named(root), 'module', -1, true),
|
||||
toBlocks: root => statements(named(root), 'module', -1),
|
||||
tokenRole: pythonTokenRole,
|
||||
iteration: pythonIteration,
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user