Files
superwork/vscode/src/codeReader/contract.ts
T

914 lines
49 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import type { Node } from '@vscode/tree-sitter-wasm'
import type { Disposable } from 'vscode'
export type { Node }
/**
* 块的种类。interpreter 到 pipeline 给脚本语言(shell):interpreter 第一行 `#!` 写的解释器,options 打开 / 关掉解释器的开关
* (`set -euo pipefail`),case 按一个值分情况(整条语句,和它的每个情况),command 执行一条命令,pipeline 几条命令用管道串起来。
* directive 往后给构建文件(Dockerfile):directive 文件开头给构建工具看的解析指令(`# syntax=…`),stage 一个构建阶段(FROM 和它后面的指令),
* run 构建时执行的命令,copy 往镜像里复制文件,setting 一条设置(工作目录、运行身份、端口……),label 镜像的标签,
* health 健康检查,start 容器启动时执行的命令。
*/
export type BlockKind
= | 'import' | 'function' | 'class' | 'enum' | 'exception' | 'type' | 'field' | 'const' | 'variable'
| 'if' | 'else' | 'loop' | 'try' | 'catch' | 'finally' | 'return' | 'statement' | 'comment'
| 'interpreter' | 'options' | 'case' | 'command' | 'pipeline'
| 'directive' | 'stage' | 'run' | 'copy' | 'setting' | 'label' | 'health' | 'start'
/** 源码里一段的下标:源码字符串下标(web-tree-sitter 的 startIndex / endIndex),end 为开区间。 */
export interface SourceRange { startIndex: number, endIndex: number }
/** 源码里的一段,带起止行号(从 0 开始)。 */
export interface SourceSpan extends SourceRange { startRow: number, endRow: number }
/** 访问级别。 */
export type Access = 'public' | 'private' | 'protected'
/** 函数的一个参数;节点都指向源码。 */
export interface ParameterFacts {
/** 参数名;解构参数是整个模式,`...rest`、`*args`、`**kwargs` 带着前缀。 */
name: Node
/** TypeScript 的可省略参数 `x?`。 */
optional: boolean
type?: Node
default?: Node
}
export interface FunctionFacts {
/** 匿名函数(`export default function () {}`)没有。 */
name?: Node
/**
* 函数自己的说明:TypeScript 是紧贴在声明前面的 JSDoc 注释(以 `/**` 开头),Python 是主体开头的 docstring(字符串节点)。
* 它算在块的头部里,不再单独成一块注释。
*/
doc?: Node
/** 嵌套在函数体、条件等语句块里的函数没有。 */
access?: Access
/** 异步函数;没有这个概念的语言不填。 */
async?: boolean
decorators: Node[]
parameters: ParameterFacts[]
returnType?: Node
/**
* 不写参数表、按位置取参数的语言(shell 的 `$1`):函数里用到的第几个参数,照位置排;
* 函数里给它起了名字(`local url="$1"`)时 name 是那个名字。按参数表取参数的语言不填,填了 parameters 就是空的。
*/
positional?: PositionalParameter[]
/** 函数打印到输出的那几句(shell 的 echo / printf,没重定向到别处):调用处拿到的就是它们打印的;没有这个概念的语言不填。 */
prints?: Node[]
/** 函数的输出被调用处拿去用了(shell 同一文件里有 `$(函数名 …)`)。 */
captured?: boolean
/**
* 函数的结果怎么来(shell 这种没有返回值、只有成不成功的语言):写了 return 的是那几句(return 语句的节点),
* 没写的是空数组,意思是最后一句成不成功。别的语言不填。
*/
returns?: Node[]
}
/** 按位置取的一个参数:第几个(1 起),函数里给它起的名字。 */
export interface PositionalParameter {
position: number
name?: Node
}
/** 第一行 `#!` 写的解释器。 */
export interface InterpreterFacts {
/** 解释器叫什么:`#!/usr/bin/env bash` 和 `#!/bin/bash` 都是 bash。 */
program: string
/** 怎么找到它:交给 env 按 PATH 找,还是写死的路径。 */
lookup: 'env' | 'path'
/** `#!` 后面写的整句(`/usr/bin/env bash`、`/bin/bash -e`)。 */
command: string
}
/** 打开 / 关掉解释器的一个开关(shell 的 `set -e`、`set -o pipefail`)。 */
export interface SwitchFacts {
/** 开关的 id:语言自己的叫法(bash 是长名字 errexit、nounset、pipefail……),翻译插件按它起名。 */
id: string
on: boolean
/** 源码里怎么写的(`-e`、`-o pipefail`)。 */
flag: string
}
export interface OptionsFacts {
/** 照源码顺序。 */
switches: SwitchFacts[]
}
/** 文件开头给构建工具看的一条解析指令(Dockerfile 的 `# syntax=docker/dockerfile:1.7`):名字和值照写。 */
export interface ParserDirective {
name: string
value: string
}
export interface DirectiveFacts {
/** 照源码顺序,一行一条。 */
directives: ParserDirective[]
}
/**
* 写在指令后面的一个选项(Dockerfile 的 `--from=deps`、`--chown=u:g`、`--interval=30s`):名字、值,值在源码里从哪开始
* (值里的阶段名、变量能跳到定义)。node 是整个选项。
*/
export interface InstructionFlag {
node: Node
name: string
value: string
valueStart: number
}
/** 一个构建阶段(Dockerfile 的 FROM),它后面到下一个 FROM 之前的指令是它的子块。 */
export interface StageFacts {
/** 阶段名(`AS deps` 的 deps);没起名的没有。 */
name?: Node
/** 第几个阶段,从 0 数:没起名的阶段按它引用(`COPY --from=0`)。 */
index: number
/** 紧贴在 FROM 上面的几行注释(中间没空行):阶段的说明,算在块的头部里。 */
doc?: Node[]
/** 基础镜像的名字(`node:22-alpine` 的 node);stage 为真时是前面某个阶段的名字。 */
image: Node
/** 前面的阶段名(`FROM builder`):从那个阶段接着往下建。 */
stage: boolean
/** 标签(`:22-alpine`,带冒号)、摘要(`@sha256:…`,带 @),写了才有。 */
tag?: Node
digest?: Node
/** FROM 后面的选项(`--platform=linux/amd64`)。 */
flags: InstructionFlag[]
}
/** 一条要执行的命令的两种写法:数组(`["node", "server.js"]`,不经过 shell,node 是整个数组),或交给 shell 执行的一段代码。 */
export type CommandForm
= | { form: 'exec', node: Node }
| { form: 'shell', code: EmbeddedCode }
/** 健康检查(Dockerfile 的 HEALTHCHECK):none 是关掉健康检查(`HEALTHCHECK NONE`);否则有选项(间隔、超时……)和检查命令。 */
export interface HealthFacts {
none: boolean
flags: InstructionFlag[]
command?: CommandForm
}
/** 容器启动时执行的命令:CMD(默认命令,`docker run` 后面写了命令就换掉它)或 ENTRYPOINT(入口,CMD 成了它的参数)。 */
export interface StartFacts {
instruction: 'cmd' | 'entrypoint'
command: CommandForm
}
/**
* 写在这门语言里的另一种语言的代码(Dockerfile 的 RUN、shell 写法的 CMD、HEALTHCHECK 的检查命令是 shell)。
* ranges 是源码里属于它的几段,去掉了外层语言的记号(续行的 `\`),拼起来是一份那种语言的代码;宿主按 languageId 找语言插件,
* 只解析这几段(下标照旧是这份源码的),块交给那种语言的翻译插件。node 是外层语法树里装着它的节点,翻译插件拿它问 SourceDocument.embedded。
*/
export interface EmbeddedCode {
node: Node
languageId: string
ranges: SourceRange[]
}
/**
* 一段嵌着的代码被当成一串步骤执行时(Dockerfile 的 RUN)怎么分块:一步一块;chained 表示前一步失败就不往下走(shell 用 && 连着的一串)。
* 见 LanguageAdapter.steps。
*/
export interface StepBlocks {
blocks: CodeBlock[]
chained: boolean
}
/**
* 宿主读嵌着的代码读出来的(SourceDocument.embedded):各步读成的一行(那种语言的翻译插件翻的,可以带一张表;注释不算一步),
* 是不是一步失败就停。whole 表示整段就是一步(没有别的块):这一步就是整段,它的子块归嵌着它的块;
* 否则各块是嵌着它的块的子块(见 TextBlock.embedded)。连词、「N 条命令」这类叫法归本语言的翻译插件。
*/
export interface EmbeddedReading {
steps: { text: Inline[], table?: PreviewTable }[]
chained: boolean
whole: boolean
}
/**
* 父类的一项:TypeScript `extends Base<T>` 里的 Base(一个表达式)和跟在它后面的泛型实参 `<T>`(语法里是两个节点);
* Python 类参数列表里的一项,`metaclass=ABCMeta` 这类关键字参数、`*bases` 也算。
*/
export interface BaseFacts {
value: Node
typeArguments?: Node
}
export interface ClassFacts {
/** 匿名类(`export default class {}`)没有;赋给变量、类字段的类表达式取变量名、字段名。 */
name?: Node
/** 类自己的说明,同 FunctionFacts.doc:TypeScript 是紧贴在前面的 JSDoc,Python 是主体开头的 docstring。 */
doc?: Node
/** 同函数:嵌套在函数体、条件等语句块里的类没有。 */
access?: Access
/** 抽象类(TypeScript 的 `abstract class`);没有这个概念的语言不填。 */
abstract?: boolean
decorators: Node[]
/** 泛型参数,每个参数一项(TypeScript `<T extends X = Y>` 里的 `T extends X = Y`)。 */
typeParameters: Node[]
/** 按源码顺序。 */
bases: BaseFacts[]
/** 实现的接口(TypeScript 的 `implements A, B<T>`),每个接口一项。 */
implements: Node[]
}
/** 枚举的一个成员;节点都指向源码。 */
export interface EnumMemberFacts {
name: Node
/** 源码里写的值;没写(TypeScript 的自增成员)、或值交给库自动取(Python 的 `auto()`)时没有。 */
value?: Node
/** 没写值、语言算得出来时的值(TypeScript 数值枚举自增:0、1、2……);算不出来时没有。 */
computed?: number
/**
* 成员的说明文字节点(注释、JSDoc、属性 docstring),照源码顺序:紧挨在它上面(从上一个成员之后起)单独成行的、
* 写在它这一行行尾的、Python 紧跟在它后面的属性 docstring。翻译插件逐个取原文连起来。
*/
docs: Node[]
}
/**
* 枚举:TypeScript 的 `enum` 由语言插件报;Python 没有枚举语法,自带 enum 库的写法(`class X(enum.Enum)`)由库规则认成这种块。
* 两边同一种块、同一张属性表。
*/
export interface EnumFacts {
name: Node
/** 枚举自己的说明,同 ClassFacts.doc。 */
doc?: Node
/** 同 ClassFacts.access。 */
access?: Access
decorators: Node[]
/** 照源码写的父类(Python 的 `str, enum.Enum`);TypeScript 没有。 */
bases: BaseFacts[]
/** 照源码顺序。 */
members: EnumMemberFacts[]
}
/** 异常类的一个类属性:Python 类主体里给一个名字赋值的语句(带不带注解、有没有值),TypeScript 的类字段(含 static);节点都指向源码。 */
export interface FieldFacts {
name: Node
type?: Node
value?: Node
/** 属性的说明文字节点,同 EnumMemberFacts.docs:紧挨在上面单独成行的注释 / JSDoc、这一行行尾的注释、Python 紧跟在后面的属性 docstring。 */
docs: Node[]
}
/**
* 异常类:父类是语言自带的异常类、同一文件里认出来的异常类,或名字按命名习惯以 Error / Exception(Python 还有 Warning)结尾。
* Python、TypeScript 都由语言插件报,同一种块、同一张属性表。块的头部是整个类(类属性在属性表里讲),留下的方法等是子块。
*/
export interface ExceptionFacts {
/** 同 ClassFacts.name。 */
name?: Node
/** 同 ClassFacts.doc。 */
doc?: Node
/** 同 ClassFacts.access。 */
access?: Access
decorators: Node[]
/** 照源码写的父类,同 ClassFacts.bases。 */
bases: BaseFacts[]
/** 类属性,照源码顺序。 */
fields: FieldFacts[]
/** 构造函数的参数(Python 的 `__init__` 去掉 self,TypeScript 的 `constructor(...)`);类里没写构造函数时没有。 */
parameters?: ParameterFacts[]
}
/** 导入的一项。 */
export type ImportItem
= | { kind: 'named', name: Node, alias?: Node } // import { a as b }、from m import a as b
| { kind: 'default', alias: Node } // import x from 'm'
| { kind: 'namespace', alias?: Node } // import * as x、from m import *
| { kind: 'module', module: Node, alias?: Node } // import a.b as c、import x = require('m')
export interface ImportFacts {
/** 来源模块,节点就是源码里的写法:TypeScript 是字符串字面量,Python 是点分模块名;Python 的 `import a, b` 有多个。 */
sources: Node[]
/** 只导入类型(TypeScript 的 `import type`);没有这个概念的语言不填。 */
typeOnly?: boolean
/** 副作用导入(`import './x'`)没有。 */
items: ImportItem[]
}
/** 字典 / 对象字面量里的一项,照源码顺序;注释不算。 */
export type EntryFacts
= | { kind: 'pair', key: Node, value: Node, entries?: EntryFacts[] } // 'a': 1、a: 1、[k]: v;TypeScript 的简写 { a } 键和值是同一个节点;entries 同 DeclaratorFacts.entries
| { kind: 'spread', value: Node } // **other、...other:value 是展开的那个表达式
| { kind: 'method', name: Node, node: Node, header: SourceSpan } // 对象字面量里的方法、getter、setter:node 是整个方法,header 同 BlockShape.header,只到主体开始之前
/** 写在表达式里的遍历得到什么:逐个产出的生成器,或一次建好的列表、集合、字典。 */
export type IterationResult = 'generator' | 'list' | 'set' | 'dict'
/** 遍历的一节,照源码顺序:从来源里逐个取出变量(async:异步地取),或只留下满足条件的。 */
export type IterationClause
= | { kind: 'for', variables: Node, source: Node, async: boolean }
| { kind: 'if', condition: Node }
/**
* 写在表达式里的遍历(Python 的列表 / 集合 / 字典推导式、生成器表达式):语言插件从语法树读出结构(LanguageAdapter.iteration),
* 翻译插件按自己的语序重排。节点都指向源码;各节、每一项取的值里还能再套遍历。
*/
export interface IterationFacts {
result: IterationResult
/** 至少一节,第一节是 for。 */
clauses: IterationClause[]
/** 每一项取的值。 */
value: Node
/** 结果是字典时每一项的键。 */
key?: Node
/**
* 遍历自己的一对括号同时是外面调用的括号时(Python `f(x for x in y)` 只写一对括号)是这对括号:
* 它们属于外面的调用,照常写在遍历外面;别的写法里遍历自己的括号、方括号、花括号不写出来。
*/
sharedParentheses?: { open: Node, close: Node }
}
/** 调用里的一个实参,照源码顺序;注释不算。 */
export type CallArgument
= | { kind: 'positional', value: Node } // 按位置传的
| { kind: 'keyword', name: Node, value: Node } // 按名字传的:Python 的 name=value
| { kind: 'spread', value: Node } // 展开成按位置传的几个:*args、...args;value 是展开的那个表达式
| { kind: 'keywordSpread', value: Node } // 展开成按名字传的几个:Python 的 **kwargs
/**
* 一次调用(含新建对象):语言插件从语法树读出结构(LanguageAdapter.call),翻译插件把跨几行写的调用排成
* 「新建 / 调用 被调用的名字 · N 个参数」一行,实参摊成一张表。节点都指向源码。
*/
export interface CallFacts {
/** 被调用的表达式:`user_service.update_user`、`UserDetailResponse`;TypeScript 的 new 不算在里面。 */
callee: Node
/** callee 里最后那个名字(点分名字的最后一节):着色、查定义用它;callee 不是名字(`f()()`、`handlers[k]()`)时没有。 */
name?: Node
/**
* 写法本身说明了是不是新建对象时给:TypeScript 的 `new X()` 是,不带 new 的调用不是。
* Python 新建对象和调用函数写法一样,不给,翻译插件问 name 的定义是哪种块(SourceDocument.calleeKind)。
*/
construct?: boolean
/** 括号连同里面的实参:它跨了几行,这次调用就是跨几行写的。 */
argumentList: Node
arguments: CallArgument[]
}
/**
* 命令的一个重定向:把输出(1)、错误输出(2)或两样都写到 / 追加到一个文件,并进另一路,或从文件读输入。
* `>&2` 是输出并进错误输出,`2>/dev/null` 是错误输出写到 /dev/null。
*/
export type Redirect
= | { kind: 'write' | 'append', stream: 'output' | 'error' | 'both', target: Node }
| { kind: 'merge', stream: 'output' | 'error', into: 'output' | 'error' }
| { kind: 'read', source: Node }
/** 命令的一个词:节点,和它写死的文字(去掉引号、转义;里面有展开 `$x`、`$(…)` 的没有),规则认选项、选项的值用它。 */
export interface CommandWord {
node: Node
literal?: string
}
/** 执行一条命令(shell):语言插件从语法树读出结构(LanguageAdapter.command),命令规则按命令名读成译文(CommandRule)。 */
export interface CommandFacts {
/** 命令名(`rm`、`curl`、同一文件里定义的函数名)。 */
name: CommandWord
/** 命令名后面的各个词,照源码顺序:选项、选项的值、要处理的东西都在里面,规则自己认。 */
arguments: CommandWord[]
redirects: Redirect[]
}
/**
* 命令规则读出来的:一行译文;value 表示读出来的是一个东西(`dirname "$0"` 是脚本所在目录),不是一个动作;
* table 是排在这一行下面的表(选项写得多的命令:「选项 | 意思」)。
*/
export interface CommandReading {
text: Inline[]
value?: boolean
table?: PreviewTable
}
/** 命令规则读命令时能用的。 */
export interface CommandContext {
/** 当前翻译插件的 displayLanguage;规则按它挑自己的词表,没有这种语言的词表就不读(照抄源码)。 */
displayLanguage: string
/** 一个词照常翻(变量、字符串、展开读成译文,名字带源码下标,能跳到定义、高亮各处出现)。 */
translate: (word: Node) => Inline[]
/** 读另一条写在实参里的命令(`find … -exec rm -rf {} +` 里的 rm):name、args 是那条命令的词;没有它的规则时没有。 */
command: (name: CommandWord, args: CommandWord[]) => CommandReading | undefined
}
/**
* 命令规则:按命令名注册,像库规则一样把一条命令(它的选项)读成译文:`rm -rf dest` → 删除 dest(连同里面的东西,不提示)。
* 认不出的选项照抄;读不了(不认识的写法)返回 undefined,宿主照抄命令名、实参照常翻。没有规则的命令同样照抄。
*/
export interface CommandRule {
id: string // 'shell.rm'
label: string // 'rm'
languageIds: string[]
/** 管哪些命令名;几条规则都管同一个名字时先注册的算。'*' 管没有自己的规则的命令(`pg_repack --version` → 打印 pg_repack 的版本)。 */
commands: string[]
read: (command: CommandFacts, context: CommandContext) => CommandReading | undefined
}
/**
* 一份源码里自己认得出的一个名字(语言插件认,不问语言扩展):定义处,和用到它的各处(含定义处)。
* VS Code 里没有这种语言的定义提供者、高亮提供者时(shell),宿主拿它跳到定义、高亮各处出现:跳到的是用到的那处之前最近的定义。
*/
export interface LocalSymbol {
definitions: SourceRange[]
occurrences: SourceRange[]
}
export interface DeclaratorFacts {
/** 变量名;解构声明是整个模式。 */
name: Node
type?: Node
value?: Node
/** 值是字典 / 对象字面量(Python 的 dictionary、TypeScript 的 object)时是它的各项,空字面量是空数组;别的值没有。 */
entries?: EntryFacts[]
}
export interface VariableFacts {
/** 只有模块顶层(Python 还有类主体)的声明有。 */
access?: Access
/**
* 变量自己的说明,同 FunctionFacts.doc:Python 是模块、类主体里紧跟在这条赋值后面的字符串(属性 docstring,字符串节点;
* 中间可以隔空行和赋值行尾的注释)。它算在块的头部里,不再单独成一块。
*/
doc?: Node
/** 一条语句声明几个变量(`let a = 1, b = 2`)就有几项;几条连着写的声明并成一块时(Dockerfile 连着的 ENV)是它们的全部。 */
declarators: DeclaratorFacts[]
/**
* 变量在哪儿有效,语言分这个才填:build 是构建参数(Dockerfile 的 ARG,只在构建时有,值是默认值、构建时可以另传),
* environment 是环境变量(Dockerfile 的 ENV,构建时和容器运行时都有)。
*/
scope?: 'build' | 'environment'
}
/**
* 筛选属性的通用词表,筛选栏按这个顺序排,语言自己的属性排在后面:
* - kind:块的种类(BlockKind);
* - access:访问级别,'public' | 'private' | 'protected',语言可以加自己的值(Kotlin 的 'internal',见 BlockShape.attributes);
* - async、abstract、typeOnly:异步、抽象、仅类型,值是 'true' | 'false'。
* 宿主从块的 kind 和 facts 推出它们(FunctionFacts.access / async、ClassFacts.access / abstract、EnumFacts.access、ExceptionFacts.access、ImportFacts.typeOnly、VariableFacts.access),
* 语言插件不用另报;facts 里没填的(语言没有这个概念、块没有这一项),块就没有这个属性,也不受这个属性的筛选影响。
*/
export const COMMON_ATTRIBUTES = ['kind', 'access', 'async', 'abstract', 'typeOnly'] as const
export type CommonAttribute = (typeof COMMON_ATTRIBUTES)[number]
/** 块的筛选属性:属性 id → 值 id,都是稳定的英文 id,不放显示文字(显示名由翻译插件给,见 AttributeNames)。 */
export type BlockAttributes = Record<string, string>
export interface BlockShape {
/** 这一块对应的语法树节点(整条语句)。 */
node: Node
/**
* 块自己显示 / 编辑的那一段:复合语句只到主体开始之前,其余是整条语句。
* 块自己的说明(facts.doc、TextBlock.doc)也算在里面:JSDoc 往上延到注释开头,docstring、属性 docstring 往下延到字符串结尾。
*/
header: SourceSpan
children: CodeBlock[]
/**
* 语言自己报的筛选属性,和宿主推出的通用属性(见 COMMON_ATTRIBUTES)合在一起:通用词表以外的属性(Rust 的 unsafe: 'true'),
* 或 facts 表达不了的通用属性值(Kotlin 的 access: 'internal',盖过从 facts 推出的)。kind 总是块的种类,这里填了不算。
*/
attributes?: BlockAttributes
}
/** 预览成属性表的块:语言插件从语法树读出结构化信息(facts),翻译插件把它排成属性表。 */
export interface FunctionBlock extends BlockShape { kind: 'function', facts: FunctionFacts }
export interface ClassBlock extends BlockShape { kind: 'class', facts: ClassFacts }
/** 枚举块的头部是整个枚举(成员都在属性表里讲),没有子块;库规则认出来的枚举(Python)是整个类,留下的方法等是子块。 */
export interface EnumBlock extends BlockShape { kind: 'enum', facts: EnumFacts }
/** 异常块的头部是整个类(类属性都在属性表里讲),留下的方法(含构造函数)等是子块。 */
export interface ExceptionBlock extends BlockShape { kind: 'exception', facts: ExceptionFacts }
export interface ImportBlock extends BlockShape { kind: 'import', facts: ImportFacts }
export interface VariableBlock extends BlockShape { kind: 'const' | 'variable', facts: VariableFacts }
/** 解释器块只有 `#!` 那一行。 */
export interface InterpreterBlock extends BlockShape { kind: 'interpreter', facts: InterpreterFacts }
/** 开关块是一整条 `set …`,每个开关一行。 */
export interface OptionsBlock extends BlockShape { kind: 'options', facts: OptionsFacts }
/** 解析指令块是文件开头连着的几行 `# 名字=值`,一条一行。 */
export interface DirectiveBlock extends BlockShape { kind: 'directive', facts: DirectiveFacts }
/** 阶段块的头部是 FROM 那一行(连同上面的说明),它后面的指令是子块。 */
export interface StageBlock extends BlockShape { kind: 'stage', facts: StageFacts }
export interface HealthBlock extends BlockShape { kind: 'health', facts: HealthFacts }
export interface StartBlock extends BlockShape { kind: 'start', facts: StartFacts }
export type FactBlock
= | FunctionBlock | ClassBlock | EnumBlock | ExceptionBlock | ImportBlock | VariableBlock | InterpreterBlock | OptionsBlock
| DirectiveBlock | StageBlock | HealthBlock | StartBlock
/** 带说明(facts.doc)的块。 */
export type DocumentedBlock = FunctionBlock | ClassBlock | EnumBlock | ExceptionBlock | VariableBlock
/** 属性表块的种类和 facts,不带块的形状(节点、头部、子块):库规则把一块认成现成的一种属性表块时给它(RuleFacts)。 */
export type BlockFacts = FactBlock extends infer B ? B extends FactBlock ? Pick<B, 'kind' | 'facts'> : never : never
export type TextKind = Exclude<BlockKind, FactBlock['kind']>
/** 预览成一行译文的块。 */
export interface TextBlock extends BlockShape {
kind: TextKind
/**
* 块自己的说明,同 VariableFacts.doc:Python 字段(类主体里带注解的赋值)后面紧跟的属性 docstring(字符串节点)。
* 算在块的头部里;预览在一行译文下面按 Markdown 显示(Translator.doc)。
*/
doc?: Node
/**
* 几条连着写的同种语句并成的一块(Dockerfile 连着的 COPY、LABEL):照源码顺序的各条,node 是第一条,头部盖住全部。
* 翻译插件排成一行字加一张表。
*/
group?: Node[]
/**
* 这块执行的是嵌着的另一种语言的代码(Dockerfile 的 RUN):宿主交给那种语言的语言插件分成几步(LanguageAdapter.steps),
* 只有一步时这一步的子块是这块的子块,几步时各步是这块的子块,都交给那种语言的翻译插件;这块自己的译文照旧归本语言的翻译插件
* (它从 SourceDocument.embedded 拿到读出来的样子)。
*/
embedded?: EmbeddedCode
}
export type CodeBlock = TextBlock | FactBlock
/** 着色用的词性,源码着色和译文着色共用。 */
export type TokenRole
= | 'keyword' // 流程关键字:import from export if else for return try catch await …
| 'storage' // 声明关键字与字面量关键字:const function async class interface new true false null …
| 'operator' // 被翻成文字的运算符:全等 并且 或者 非
| 'hint' // 属性表里的说明词:未标注 无 N 个 默认导出 …
| 'constant' // 按习惯全大写、当常量用的名字(shell 的 PY_VERSION、HOME)
| 'variable' | 'function' | 'type' | 'string' | 'number' | 'literal' | 'comment' | 'plain'
/**
* 一段着色的文字。offset:这段照抄自源码里的名字(标识符、属性名、类型名、点分模块名的一节)或字符串时,
* 它在源码里的起始下标(同 SourceSpan 的下标),预览拿它向 VS Code 查定义、跳过去;译词、提示词没有。
* reference:只有库规则给的段有,这段讲的东西由规则自己查定义(LibraryRule.resolve),不问语言扩展;这样的段必须带 offset。
* color:这段自己的颜色(暗色、亮色主题各一个),盖过词性的颜色:库规则按库自己的配色上色(FastAPI 的 HTTP 方法用 Swagger UI 的颜色)。
* refers:这段讲的是源码里别处的一个名字(那个名字的范围),语言扩展不认得这段自己的位置:写在字符串里的名字
* (FastAPI 路径 `"/{user_id}"` 里的 user_id 讲的是函数的参数 user_id)。预览把这段当那个名字:⌘ / Ctrl + 点击跳到它的定义,
* 单击高亮它的各处出现,那个名字的某处出现盖住 refers 时这段也跟着高亮。offset 照旧是这段照抄的位置。
*/
export interface Segment { text: string, role: TokenRole, offset?: number, reference?: RuleReference, color?: ThemeColor, refers?: SourceRange }
/** 框的样式,决定框头的图标和框的颜色:'iteration' 是写在表达式里的遍历,图标、颜色同「循环」块。 */
export type GroupStyle = 'iteration'
/**
* 框起来的一组词:译文里自成一体的一段(写在表达式里的遍历),预览把它画成嵌在前后文字里的一个小框,框头是图标和 label。
* 框里的词照样带源码下标,还能再套框。
*/
export interface SegmentGroup {
style: GroupStyle
/** 框头的文字(遍历的结果:生成器 / 列表 …),翻译插件给。 */
label: string
segments: Inline[]
}
/** 译文里的一段:着色的词,或框起来的一组词。 */
export type Inline = Segment | SegmentGroup
/**
* 一段 Markdown 说明文字:注释、JSDoc、docstring 去掉注释符号、引号和共同缩进后的原文,不做别的转换
* (reStructuredText 的 `:func:`、JSDoc 的 `@param` 都照抄)。预览按 Markdown 渲染成注释色的段落、列表、代码块,源码里的 HTML 当文字显示。
*/
export interface Markdown { markdown: string }
/** 预览里显示的一段文字:着色的词(一行译文、属性值),或整段 Markdown 说明(注释块、函数和类的说明)。 */
export type PreviewText = Inline[] | Markdown
/**
* 属性表里的一行或一个子行讲的是哪个定义:定义出来的那个名字的节点(取自 facts,例如参数名、导入项的名称和别名、
* 声明的变量名、类的泛型参数、字典 / 对象字面量一项的键)在源码里的范围。跳到定义时,定义处落在这个范围里就只闪这一行;不填(第三方翻译插件可以不填)就闪整块。
*/
export interface Defines { defines?: SourceRange }
/** 属性表里展开的一个子行:几列对齐的格子,例如参数的名称 / 类型 / 默认值、字典一项的键 / 值。 */
export interface PropertyItem extends Defines {
cells: Inline[][]
/** 展开在这个子行下面的下一层子行(字典 / 对象的值又是字典 / 对象时,它的各项);一层层可以一直往下套。 */
items?: PropertyItem[]
/**
* 排在这一行最后一格里、那格文字下面的表:实参表里一个实参的值里有跨几行写的调用时,是那个调用的实参表,
* 表里的行又可以带 table,一层层往下套。总是展开的。
*/
table?: PreviewTable
}
/**
* 属性表的一行:左列属性名,右列着色的值或 Markdown 说明;items 是展开在这一行下面的子行(输入参数、导入项、字典 / 对象的各项)。
* columns 是子行各列的列名(输入参数的名称 / 类型 / 默认值):给了列名的子行排成带表头和边框的表格,没给的排成列表;表格只有一层,子行不再往下套。
*/
export interface PropertyRow extends Defines {
label: string
value: PreviewText
items?: PropertyItem[]
columns?: string[]
}
/** 一张带表头和边框的表:列名,每行几列对齐的格子,同 PropertyRow 给了 columns 的子行。 */
export interface PreviewTable {
columns: string[]
items: PropertyItem[]
}
/** 一行译文的块翻出来的:一行译文(注释块是 Markdown 说明);跨几行写的调用还有摊开各个实参的表,预览排在这一行下面。 */
export interface TextTranslation {
text: PreviewText
table?: PreviewTable
}
/**
* 语言插件:语法树 → 代码块树,外加只有它懂、翻译插件翻表达式时要用的信息(叶子的词性、写在表达式里的遍历的结构)。
* 翻译插件翻一块时从 SourceDocument.language 拿到解析这份源码的语言插件,不直接依赖某个语言插件。
*/
export interface LanguageAdapter {
id: string
/** 覆盖的 VS Code languageId,例如 ['typescript', 'typescriptreact', 'javascript', 'javascriptreact']。 */
languageIds: string[]
/** 该 languageId 用的语法包 .wasm 的绝对路径(宿主负责加载,保证和解析器运行时是同一份)。 */
grammarPath: (languageId: string) => string
/** languageId 是这份源码的 languageId:一个插件覆盖几种方言时按它区分(JavaScript 没有 TypeScript 的抽象类、仅类型导入)。 */
toBlocks: (root: Node, source: string, languageId: string) => CodeBlock[]
/** 给语法树叶子节点定词性:源码着色用它,译文里照抄的词也按它着色、按它认出能查定义的名字。 */
tokenRole: (leaf: Node) => TokenRole
/** node 是写在表达式里的遍历(推导式、生成器表达式)时给出它的结构,别的节点没有;没有这种写法的语言不填。 */
iteration?: (node: Node) => IterationFacts | undefined
/** node 是一次调用(含新建对象)时给出它的结构,别的节点没有;没给的语言不展开调用,照常一行译文。 */
call?: (node: Node) => CallFacts | undefined
/** node 是执行一条命令(shell)时给出它的结构,交给命令规则读;别的节点没有,没有命令的语言不填。 */
command?: (node: Node) => CommandFacts | undefined
/** 一份源码里自己认得出的名字(见 LocalSymbol);VS Code 有这种语言的定义、高亮提供者时用不上,不填。 */
symbols?: (root: Node, source: string, languageId: string) => LocalSymbol[]
/** 这份源码里嵌着的别的语言的代码(见 EmbeddedCode),块里的(TextBlock.embedded)、属性表里的(HealthFacts.command)都算;没有的语言不填。 */
embedded?: (root: Node, source: string, languageId: string) => EmbeddedCode[]
/** 这门语言的代码被别的语言嵌着、当一串步骤执行时怎么分块(见 StepBlocks);不填就按 toBlocks 分、不算一步失败就停。 */
steps?: (root: Node, source: string, languageId: string) => StepBlocks
/** 认代码里给定义起的中文名(见「中文名」);不填就没有中文名。 */
displayNames?: DisplayNameReader
}
// ── 中文名(见「中文名」)──
/** 代码里给一个定义起的中文名:预览里这个定义和用到它的各处都显示中文名,悬停看原名。 */
export interface DisplayName {
/** 定义处那个名字(函数名、类名、参数名、变量名、字段名、枚举成员名……) */
name: SourceRange
/** 中文名 */
text: string
}
/** 一份源码里认出的中文名。 */
export interface DisplayNameFacts {
/** 起了中文名的定义,照源码顺序;一个定义几处都写了时语言插件定取哪个(明写的记号优先于说明文字) */
names: DisplayName[]
/** 写在注释里给代码阅读看的记号(`# @Comment 当前用户` 里的 `@Comment 当前用户`):宿主交给翻译插件,翻注释、说明时去掉 */
markers: SourceRange[]
}
/** 语言插件认中文名:写法(记号、说明文字里的哪一句)归语言插件;找用到它的各处、显示归宿主和预览。 */
export interface DisplayNameReader {
/** languageId → 工作区里这种语言的文件(glob,Python 是所有 .py 文件):宿主建工作区索引,认出别的文件里定义的中文名 */
files: Record<string, string>
/** 便宜的文字预筛:可能写了中文名的源码才解析 */
mentions: (source: string) => boolean
find: (root: Node, source: string, languageId: string) => DisplayNameFacts
}
/** 翻译插件翻一块时拿到的这份源码:源码字符串,和解析它的语言插件。 */
export interface SourceDocument {
source: string
language: LanguageAdapter
/** 写在注释里给代码阅读看的记号(中文名记号,见 DisplayNameFacts.markers):不是说明文字,翻注释、说明时去掉 */
markers: SourceRange[]
/**
* 调用的名字(CallFacts.name)的定义是哪种块:宿主问语言扩展定义在哪,用那个文件的语言插件分块认出来。
* 排视图数据是同步的,这里只交出已经查到的;还没查到、查不到、宿主不查(没给)都是 undefined,查到了宿主重排、重发。
*/
calleeKind?: (call: CallFacts) => BlockKind | undefined
/** 这个命令名的命令规则(这种语言注册的,先注册的算);没有、或宿主不给时没有,命令照抄。规则出错由宿主记下、当它没读。 */
commandRule?: (name: string) => CommandRule | undefined
/**
* 嵌着的代码(EmbeddedCode.node 是 node 的那段)读出来的样子:宿主用那种语言的语言插件、翻译插件读好了交出来。
* 那种语言没有注册、宿主不给时没有,翻译插件照抄。
*/
embedded?: (node: Node) => EmbeddedReading | undefined
}
/** 筛选属性和值的显示名,筛选栏、块的种类(图标的悬停提示)用它;没给名字的显示 id。 */
export interface AttributeNames {
/** 属性 id → 属性名,例如 access → 访问级别。 */
attributes: Record<string, string>
/** 属性 id → 值 id → 值名,例如 access → public → 公共、async → true → 是、kind → function → 函数。 */
values: Record<string, Record<string, string>>
}
/** 翻译插件:代码块 → 一行译文或属性表。 */
export interface Translator {
id: string // 'typescript.zh-template'
label: string // '中文直译'
languageIds: string[]
/** 译文用的语言(BCP 47 的语言部分,例如 'zh'):库规则按它挑自己的词表。 */
displayLanguage: string
/** 只翻 header 那一段;注释块给去掉注释符号的 Markdown 原文,跨几行写的调用连着摊开实参的表。 */
translate: (block: TextBlock, document: SourceDocument) => TextTranslation
/** 函数、类、枚举、导入、常量、变量块 → 属性表。 */
describe: (block: FactBlock, document: SourceDocument) => PropertyRow[]
/** 一个表达式或类型节点整个翻成一行译文,译法同属性表里的值:库规则翻默认值、类型时借它。 */
expression: (node: Node, document: SourceDocument) => Inline[]
/** 一段说明文字(注释、docstring、当说明用的字符串)去掉注释符号、引号和共同缩进后的 Markdown 原文,同属性表的「说明」;没有文字时没有。 */
doc: (node: Node, document: SourceDocument) => Markdown | undefined
/** 筛选属性和值叫什么,和属性表里的属性名、值用同一套叫法;不给就都显示 id。 */
attributeNames?: AttributeNames
}
/** 一种颜色在暗色、亮色主题下各用哪个(CSS 颜色值);高对比主题跟着暗 / 亮走。 */
export interface ThemeColor { dark: string, light: string }
/**
* 种类图标:插件自己定义的一组 lucide 图标(https://lucide.dev/icons 上的 kebab-case 名字)。前 26 个给内置种类(几种共用一个:
* 命令、管道、执行共用 square-terminal,解释器、解析指令共用 terminal,选项、设置共用 sliders-horizontal,字段、标签共用 tag),
* 后面的给库规则的种类;库规则的种类(KindBadge.icon)只能从这组里挑,要新图标就加进这组(webview 的 kindIcons.tsx 同时给它配上图标),
* 没有按任意名字找图标的路。
*/
export const KIND_ICON_NAMES = [
'import',
'square-function',
'box',
'braces',
'list-ordered',
'triangle-alert',
'tag',
'lock',
'variable',
'split',
'corner-down-right',
'repeat',
'shield',
'shield-alert',
'shield-check',
'corner-down-left',
'code',
'message-square-text',
'terminal',
'sliders-horizontal',
'signpost',
'square-terminal',
'layers',
'copy',
'activity',
'play',
'database',
'package',
'server-cog',
'calendar-clock',
'globe',
'network',
] as const
export type KindIconName = (typeof KIND_ICON_NAMES)[number]
/** 库规则的种类在预览里的样子:块左边只画图标(种类色),名字是悬停提示和读屏念的文字,筛选栏里这个种类也叫它。 */
export interface KindBadge { label: string, color: ThemeColor, icon: KindIconName }
/**
* 库规则认领一块后给出的替身,预览里它代替原来那块,下面挂着原块留下的子块:
* 规则自己的卡片(RuleCard),或认成契约里现成的一种属性表块(RuleFacts)。
*/
export type RuleBlock = RuleCard | RuleFacts
/** 规则自己的卡片:一张属性表,种类的图标、颜色、名字和每一行都由规则给(用词归规则)。 */
export interface RuleCard {
/** 种类 id,写成「规则 id.名字」(sqlalchemy.table):筛选的 kind 属性值就是它,筛选记住的也是它,要稳定。 */
kind: string
/** 种类的名字、颜色和图标:筛选栏里这个种类也叫这个名字。 */
badge: KindBadge
rows: PropertyRow[]
/** 块自己的名字(类名)在源码里的范围:跳到的定义就是它时闪整块。 */
defines?: SourceRange
/**
* 规则自己的筛选属性,叠在宿主从原块推出的属性上(同 BlockShape.attributes);kind 总是上面的 kind,这里填了不算。
* 属性和值的名字由规则给(LibraryRule.attributeNames)。
*/
attributes?: BlockAttributes
/**
* 并进这一块的子块(原块 children 里的对象本身):不再单独成块,内容由 rows 讲。
* 没并进来的子块(方法、别的成员、规则不讲的注释)不管写在哪,都照源码顺序照常是这一块的子块,也照常交给各规则看。
* 这一块的编辑范围是原块整条语句(类就是整个类),盖住留下的子块:它编辑时预览不画这些子块,同一行不会同时在两处能改。
*/
absorbs: CodeBlock[]
}
/**
* 把这块认成契约里现成的一种属性表块(Python 自带 enum 库的类 → 枚举块):宿主当语言插件报的这种块处理,
* 种类、种类名、颜色、属性表、筛选属性都和语言自带的这种块一样,由翻译插件排;规则只给 facts(库的知识),不给用词。
* 块的头部是原块整条语句(facts 里的节点都在里面),absorbs 同 RuleCard.absorbs。
*/
export interface RuleFacts {
as: BlockFacts
absorbs: CodeBlock[]
}
/** 库规则看一块时宿主给的:这份源码、它的导入,借当前翻译插件翻表达式、取说明的办法,和查名字的定义在工作区索引里是哪一项的办法。 */
export interface RuleContext {
document: SourceDocument
/** 这份源码里各处导入的 facts,照源码顺序:规则拿它认出库里名字的别名(`from sqlalchemy import Enum as SQLEnum`)。 */
imports: ImportFacts[]
/** 当前翻译插件的 displayLanguage;没有翻译插件时没有。规则按它挑自己的词表,没有这种语言的词表就用自己默认的那份。 */
displayLanguage?: string
/** 用当前翻译插件把一个表达式或类型节点翻成一行译文(Translator.expression):名字带源码下标,能跳到定义、高亮各处出现;没有翻译插件时照抄源码。 */
translate: (node: Node) => Inline[]
/** 用当前翻译插件取一段说明文字(Translator.doc);没有翻译插件、没有文字时没有。 */
doc: (node: Node) => Markdown | undefined
/**
* 源码里 name 这个名字(例如 raise 后面的异常类名)指的定义在 index 里认出的那一项,规则从定义处读出的值在 IndexEntry.value 里:
* 定义由语言扩展查(同 ⌘ + 点击),落在 index 认出的某一项的名字上才算;同一份源码里同名的名字按一处查。
* 认领是同步的,查要等工作区索引建好、语言扩展答话:宿主先当没查到(undefined)交给规则排,发出预览后再查,
* 查到的和上次不一样就按新结果重排一遍、重发;文档改了先用上次查到的,再重查。没有工作区时(单元测试)总是 undefined。
*/
lookup: (index: WorkspaceIndex, name: Node) => IndexEntry | undefined
}
/** 规则自己查得到定义的一个名字:哪个规则(LibraryRule.id),和规则自己认的键(Celery 的任务名)。 */
export interface RuleReference { rule: string, key: string }
/** 文件里的一个位置:行、列从 0 起,列按 UTF-16 计(同 VS Code)。 */
export interface RulePosition { line: number, character: number }
/** 规则查到的一处定义:文件(uri 字符串)和定义处那个名字的起止位置。 */
export interface RuleLocation { uri: string, start: RulePosition, end: RulePosition }
/** 建索引时交给规则的一份文件。 */
export interface IndexedFile {
/** 相对它所在的工作区文件夹的路径,用 / 分隔。 */
path: string
source: string
/** 同一个工作区文件夹里有没有这个文件(相对路径);只认按 WorkspaceIndex.include 找到的文件。算 Python 包路径时问 `__init__.py` 用。 */
exists: (path: string) => boolean
}
/**
* 规则从一份文件里认出的一项:键,定义处那个名字在源码里的范围,和规则从定义处读出、认领别的文件时要用的值
* (FastAPI 规则:异常类的 status_code;见 RuleContext.lookup)。
*/
export interface IndexEntry { key: string, name: SourceRange, value?: string }
/**
* 规则在整个工作区里建的索引(键 → 定义处):规则只说看哪些文件、从一份源码里认出什么;
* 找文件、预筛、解析、缓存、跟着文件改动更新都归宿主。第一次查时才建,之后只重看改了的文件。
*/
export interface WorkspaceIndex {
/** 同一个 id 宿主只建一份,写成「规则 id.名字」(celery.tasks)。 */
id: string
/** 按哪种语言解析:宿主用这个 languageId 的语言插件。 */
languageId: string
/**
* 看哪些文件:相对各工作区文件夹的 glob('**\/*.py');files.exclude、search.exclude 排除的,.venv、node_modules、__pycache__ 里的,
* 嵌套的 checkout(子仓库、git worktree)里的,和忽略文件(.gitignore、.ignore,按 search.useIgnoreFiles)忽略的不看。
*/
include: string
/** 便宜的文字预筛:源码里不可能有要找的东西就不解析。 */
mentions: (source: string) => boolean
/** 从一份文件的语法树里认出的各项;语法树只在调用期间有效。 */
entries: (root: Node, file: IndexedFile) => IndexEntry[]
}
/** 宿主给规则查定义用的工作区。 */
export interface RuleWorkspace {
/** 在 index 建的索引里查 key 的各处定义,照文件、行排好。 */
lookup: (index: WorkspaceIndex, key: string) => Promise<RuleLocation[]>
}
/**
* 库规则:认出某个库的一种写法(SQLAlchemy 的模型类),把那块整个换成一张专门的卡片,或认成现成的一种属性表块
* (enum 库的类 → 枚举块);文件导入了它关心的库才启用。
* 规则只经契约拿信息:块(kind、node、header、children、facts)和 RuleContext;卡片的用词(属性名、种类名、类型的译名)属于规则自己。
*/
export interface LibraryRule {
id: string // 'sqlalchemy'
label: string // 'SQLAlchemy 数据表'
languageIds: string[]
/**
* 关心的库(模块名):这份源码里有一条导入(ImportFacts.sources,去掉引号)等于它、或以「它.」「它/」开头(它的子模块)才启用。
* 宿主按语言插件给的导入判断,规则不用自己读导入语句。
*/
libraries: string[]
/**
* 看一块:认领就返回替身,不认就返回 undefined。宿主从顶层往下每块都问(被认领的块留下的子块照样问)。
* 几个规则都认同一块时先注册的算;规则抛错、或替身不合上面的约定时,宿主记日志、当它没认,接着问下一个规则。
*/
claim: (block: CodeBlock, context: RuleContext) => RuleBlock | undefined
/**
* 查规则自己标的引用(Segment.reference 里 rule 是这个规则的)的定义:预览里 ⌘ / Ctrl 悬停、点击这样的名字时宿主问它,不问语言扩展;
* 查到几处就跳、挑,和语言扩展查到的一样。没给就不能在段上标引用。
*/
resolve?: (key: string, workspace: RuleWorkspace) => Promise<RuleLocation[]>
/**
* 规则自己报的筛选属性(RuleCard.attributes,例如 FastAPI 接口的 httpMethod)和值叫什么,同 Translator.attributeNames:
* 用词归规则,按翻译插件的 displayLanguage 挑(没有翻译插件时是 undefined),没有这种语言的词表就用规则默认的那份。
* 筛选栏的名字先取翻译插件给的,没给的再取规则给的;都没给的显示 id。
*/
attributeNames?: (displayLanguage: string | undefined) => AttributeNames
}
export interface CodeReaderApi {
registerLanguage: (adapter: LanguageAdapter) => Disposable
registerTranslator: (translator: Translator) => Disposable
registerLibraryRule: (rule: LibraryRule) => Disposable
registerCommandRule: (rule: CommandRule) => Disposable
}