🐛 fix(vscode): React 规则在导入了 react 的 .ts / .js 文件上也启用,自定义钩子和钩子调用照常出卡片
This commit is contained in:
1 parent
2ccec54747
commit
9f736d92e7
23 files changed
+141
-36
No files matched your search
@@ -406,7 +406,7 @@ describe('blockView', () => {
|
||||
}
|
||||
const source = 'class Box:\n pass\n'
|
||||
const [view] = await withBlocks(language, 'python', source, blocks =>
|
||||
toBlockViews(blocks, { source, language, markers: [] }, pythonZhTranslator, { rules: [rule], report: () => {} }))
|
||||
toBlockViews(blocks, { source, language, markers: [] }, pythonZhTranslator, { languageId: 'python', rules: [rule], report: () => {} }))
|
||||
expect(view?.concepts).toEqual([
|
||||
{ id: 'class', label: '类', icon: 'box' },
|
||||
{ id: 'python.abstract', label: '抽象', icon: 'braces' },
|
||||
|
||||
@@ -236,16 +236,19 @@ function embeddedReading({ steps, document, translator }: EmbeddedPart): Embedde
|
||||
}
|
||||
|
||||
/**
|
||||
* 用哪些库规则(某 languageId 注册的,按注册顺序;这份源码导入了规则关心的库才启用),规则出错时怎么记,
|
||||
* 用哪些库规则(languageId 这种语言注册的,按注册顺序;这份源码导入了规则关心的库才启用,见 [activeRules]),规则出错时怎么记,
|
||||
* 规则查名字的定义时交出眼下知道的(RuleContext.lookup,见 ruleLookups.ts);没给时总是没查到。
|
||||
*/
|
||||
export interface RuleRun {
|
||||
/** 这份源码的 languageId:规则在有的语言里不看导入就启用([LibraryRule.alwaysIn]) */
|
||||
languageId: string
|
||||
rules: readonly LibraryRule[]
|
||||
report: (message: string) => void
|
||||
lookup?: RuleContext['lookup']
|
||||
}
|
||||
|
||||
const NO_RULES: RuleRun = { rules: [], report: () => {} }
|
||||
/** 不用库规则(嵌着的代码):没有规则,languageId 用不上。 */
|
||||
const NO_RULES: RuleRun = { languageId: '', rules: [], report: () => {} }
|
||||
|
||||
/** 源码里一段的结尾:到哪个字符为止(不含),那个字符在第几行。 */
|
||||
type SpanEnd = Pick<SourceSpan, 'endIndex' | 'endRow'>
|
||||
@@ -366,7 +369,7 @@ export function toBlockViews(
|
||||
*/
|
||||
function layer(layerBlocks: readonly CodeBlock[], layerDocument: SourceDocument, layerTranslator: Translator | undefined, layerRules: RuleRun, writable: boolean): (list: readonly CodeBlock[], prefix: string, scope: string, from?: number) => BlockView[] {
|
||||
const imports = importsIn(layerBlocks)
|
||||
const active = activeRules(layerRules.rules, imports)
|
||||
const active = activeRules(layerRules.rules, imports, layerRules.languageId)
|
||||
const context: RuleContext = {
|
||||
document: layerDocument,
|
||||
imports,
|
||||
|
||||
@@ -1343,9 +1343,14 @@ export interface LibraryRule {
|
||||
/**
|
||||
* 关心的库(模块名):这份源码里有一条导入(ImportFacts.sources,去掉引号)等于它、或以「它.」「它/」「它::」开头(它的子模块;Rust 的路径用 ::)才启用。
|
||||
* 宿主按语言插件给的导入判断,规则不用自己读导入语句。空数组是不看导入、languageIds 里这几种语言的源码都启用
|
||||
* (React 组件:新的 JSX 写法不用导入 react,TSX / JSX 文件里就是 React 组件)。
|
||||
* (Pydantic:子类文件常常只导入父模型)。
|
||||
*/
|
||||
libraries: string[]
|
||||
/**
|
||||
* languageIds 里不看导入就启用的那几种语言,别的语言照 libraries 看导入;不填就是都照 libraries 看。
|
||||
* React:新的 JSX 写法不用导入 react,TSX / JSX 文件都启用;.ts / .js 里的 `useXxx` 不一定是钩子(zustand 的 store),导入了 react 才启用。
|
||||
*/
|
||||
alwaysIn?: string[]
|
||||
/**
|
||||
* 看一块:认领就返回替身,不认就返回 undefined。宿主从顶层往下每块都问(被认领的块留下的子块照样问)。
|
||||
* 几个规则都认同一块时先注册的算;规则抛错、或替身不合上面的约定时,宿主记日志、当它没认,接着问下一个规则。
|
||||
|
||||
@@ -12,7 +12,7 @@ const py = createPythonAdapter(WASM_DIR)
|
||||
const SNAPSHOT = readFixture('celeryApp.py')
|
||||
|
||||
function views(source: string): Promise<BlockView[]> {
|
||||
return withBlocks(py, 'python', source, blocks => toBlockViews(blocks, { source, language: py, markers: [] }, pythonZhTranslator, { rules: [celeryRule], report: () => {} }))
|
||||
return withBlocks(py, 'python', source, blocks => toBlockViews(blocks, { source, language: py, markers: [] }, pythonZhTranslator, { languageId: 'python', rules: [celeryRule], report: () => {} }))
|
||||
}
|
||||
|
||||
/** 记下问了哪边:规则(引用的键)还是语言扩展(下标)。 */
|
||||
|
||||
@@ -80,7 +80,7 @@ export async function documentMessage(
|
||||
const rules = registry.rulesFor(languageId)
|
||||
const { blocks, colors, displayNames, symbols, imports, kinds, bindings } = await readBlockViews(runtime, adapter, translator, languageId, source, {
|
||||
...(document.fileName && { fileName: document.fileName }),
|
||||
rules: { rules, report, lookup },
|
||||
rules: { languageId, rules, report, lookup },
|
||||
calleeKind,
|
||||
commandRule: commandRuleLookup(registry.commandRulesFor(languageId), report),
|
||||
embedded: (embeddedId) => {
|
||||
|
||||
@@ -97,7 +97,7 @@ describe('react 钩子卡片发给页面', () => {
|
||||
const ts = createTypeScriptAdapter(WASM_DIR)
|
||||
const source = ['import { useState } from "react";', 'export function Box() {', ' const [n, setN] = useState(0);', ' return <i>{n}</i>;', '}'].join('\n')
|
||||
const views = await withBlocks(ts, 'typescriptreact', source, blocks =>
|
||||
toBlockViews(blocks, { source, language: ts, markers: [] }, typescriptZhTranslator, { rules: [reactRule], report: () => {} }))
|
||||
toBlockViews(blocks, { source, language: ts, markers: [] }, typescriptZhTranslator, { languageId: 'typescriptreact', rules: [reactRule], report: () => {} }))
|
||||
const hook = pageBlocks(views, source)[1].children[0]
|
||||
expect(hook.kind).toBe('react.hook')
|
||||
expect(hook.tint).toEqual({ dark: '#61dafb', light: '#0a7ea4' })
|
||||
|
||||
@@ -0,0 +1,42 @@
|
||||
"use client";
|
||||
|
||||
import { useEffect } from "react";
|
||||
import { useRouter } from "next/navigation";
|
||||
|
||||
import { useDesktopShellInfo } from "@/hooks/use-desktop-shell-info";
|
||||
import { useSidebarCollapsed } from "@/lib/sidebar-collapsed";
|
||||
|
||||
import { useDesktopShell } from "./store";
|
||||
import { tauriBridge } from "./tauri-bridge";
|
||||
|
||||
/**
|
||||
* 挂载时经 [tauriBridge] 取一次全量状态与壳留存的活动,之后靠事件同步;不在壳里时什么也不取。
|
||||
*
|
||||
* 另在这里注册窗口内快捷键。
|
||||
*/
|
||||
export const useShellEvents = () => {
|
||||
const router = useRouter();
|
||||
const shell = useDesktopShellInfo();
|
||||
const { toggle: toggleSidebar } = useSidebarCollapsed();
|
||||
|
||||
useEffect(() => {
|
||||
const s = useDesktopShell.getState();
|
||||
void tauriBridge.state().then((state) => state && s.applyState(state));
|
||||
const subs = [
|
||||
tauriBridge.listen("desktop://new-session", () => router.push("/chat/code")),
|
||||
];
|
||||
return () => {
|
||||
void Promise.all(subs).then((unlisteners) => unlisteners.forEach((unlisten) => unlisten?.()));
|
||||
};
|
||||
}, [router]);
|
||||
|
||||
// 输入框有焦点时也生效: 这些组合键输入框本来就不吃
|
||||
useEffect(() => {
|
||||
if (!shell) return;
|
||||
const onKey = (e: KeyboardEvent) => {
|
||||
if (e.key === "b") toggleSidebar();
|
||||
};
|
||||
window.addEventListener("keydown", onKey);
|
||||
return () => window.removeEventListener("keydown", onKey);
|
||||
}, [shell, toggleSidebar]);
|
||||
};
|
||||
@@ -24,7 +24,7 @@ const RULES = [sqlalchemyRule, celeryRule, enumRule, dataclassRule]
|
||||
|
||||
function views(adapter: LanguageAdapter, languageId: string, translator: Translator, source: string): Promise<BlockView[]> {
|
||||
return withBlocks(adapter, languageId, source, blocks =>
|
||||
toBlockViews(blocks, { source, language: adapter, markers: [] }, translator, { rules: RULES, report: () => {} }))
|
||||
toBlockViews(blocks, { source, language: adapter, markers: [] }, translator, { languageId, rules: RULES, report: () => {} }))
|
||||
}
|
||||
|
||||
const pyViews = (source: string): Promise<BlockView[]> => views(py, 'python', pythonZhTranslator, source)
|
||||
|
||||
@@ -16,6 +16,7 @@ const sample = readFixture('rustLang.rs')
|
||||
function views(source: string, fileName?: string): Promise<BlockView[]> {
|
||||
return withBlocks(adapter, 'rust', source, blocks =>
|
||||
toBlockViews(blocks, { source, language: adapter, markers: [], ...(fileName && { fileName }) }, rustZhTranslator, {
|
||||
languageId: 'rust',
|
||||
rules: [rustImplRule],
|
||||
report: () => {},
|
||||
}))
|
||||
|
||||
@@ -48,7 +48,7 @@ const throwing: LibraryRule = {
|
||||
}
|
||||
|
||||
async function views(source: string, rules: LibraryRule[], reports: string[] = []): Promise<BlockView[]> {
|
||||
const run: RuleRun = { rules, report: message => reports.push(message) }
|
||||
const run: RuleRun = { languageId: 'python', rules, report: message => reports.push(message) }
|
||||
return withBlocks(py, 'python', source, blocks => toBlockViews(blocks, { source, language: py, markers: [] }, pythonZhTranslator, run))
|
||||
}
|
||||
|
||||
@@ -56,9 +56,9 @@ function flatten(list: BlockView[]): BlockView[] {
|
||||
return list.flatMap(view => [view, ...flatten(view.children)])
|
||||
}
|
||||
|
||||
/** 语法树用完即释放,节点要在里面读完。 */
|
||||
function activates(source: string, rule: LibraryRule): Promise<boolean> {
|
||||
return withBlocks(py, 'python', source, blocks => activeRules([rule], importsIn(blocks)).length > 0)
|
||||
/** 按 Python 解析的源码当成 languageId 这种语言的,规则启不启用;语法树用完即释放,节点要在里面读完。 */
|
||||
function activates(source: string, rule: LibraryRule, languageId = 'python'): Promise<boolean> {
|
||||
return withBlocks(py, 'python', source, blocks => activeRules([rule], importsIn(blocks), languageId).length > 0)
|
||||
}
|
||||
|
||||
describe('库规则:启用', () => {
|
||||
@@ -94,6 +94,13 @@ describe('库规则:启用', () => {
|
||||
expect(await activates('import os', { ...cardRule('any'), libraries: [] })).toBe(true)
|
||||
expect(await activates('x = 1', { ...cardRule('any'), libraries: [] })).toBe(true)
|
||||
})
|
||||
|
||||
it('alwaysIn 里的语言不看导入就启用,别的语言照 libraries 看导入(React:TSX 不用导入 react,.ts 导入了 react 才启用)', async () => {
|
||||
const rule: LibraryRule = { ...cardRule('any'), languageIds: ['python', 'cython'], alwaysIn: ['cython'] }
|
||||
expect(await activates('import os', rule, 'cython')).toBe(true)
|
||||
expect(await activates('import os', rule, 'python')).toBe(false)
|
||||
expect(await activates('import sqlalchemy', rule, 'python')).toBe(true)
|
||||
})
|
||||
})
|
||||
|
||||
describe('库规则:认领', () => {
|
||||
|
||||
@@ -26,10 +26,15 @@ export function fromLibrary(module: string, library: string): boolean {
|
||||
return module === library || module.startsWith(`${library}.`) || module.startsWith(`${library}/`) || module.startsWith(`${library}::`)
|
||||
}
|
||||
|
||||
/** 这份源码启用的规则,保持注册顺序:导入了规则关心的某个库,或规则不看导入(libraries 是空的)。 */
|
||||
export function activeRules(rules: readonly LibraryRule[], imports: readonly ImportFacts[]): LibraryRule[] {
|
||||
/**
|
||||
* languageId 这种语言的一份源码启用的规则,保持注册顺序:导入了规则关心的某个库,或规则不看导入
|
||||
* (libraries 是空的,或这种语言在 alwaysIn 里)。
|
||||
*/
|
||||
export function activeRules(rules: readonly LibraryRule[], imports: readonly ImportFacts[], languageId: string): LibraryRule[] {
|
||||
const modules = imports.flatMap(facts => facts.sources.map(moduleName))
|
||||
return rules.filter(rule => rule.libraries.length === 0 || rule.libraries.some(library => modules.some(module => fromLibrary(module, library))))
|
||||
return rules.filter(rule => rule.libraries.length === 0
|
||||
|| rule.alwaysIn?.includes(languageId)
|
||||
|| rule.libraries.some(library => modules.some(module => fromLibrary(module, library))))
|
||||
}
|
||||
|
||||
/** 一块被哪个规则认领、替身是什么。 */
|
||||
|
||||
@@ -27,7 +27,7 @@ const ROUTES = [
|
||||
].join('\n')
|
||||
|
||||
function read(source: string): ReturnType<typeof readBlockViews> {
|
||||
return readBlockViews(runtime, py, pythonZhTranslator, 'python', source, { rules: { rules: [enumRule], report: () => {} } })
|
||||
return readBlockViews(runtime, py, pythonZhTranslator, 'python', source, { rules: { languageId: 'python', rules: [enumRule], report: () => {} } })
|
||||
}
|
||||
|
||||
function allProperties(blocks: readonly BlockView[]): string[] {
|
||||
|
||||
@@ -13,7 +13,7 @@ const SNAPSHOT = readFixture('celeryApp.py')
|
||||
|
||||
async function views(source: string, reports: string[] = []): Promise<BlockView[]> {
|
||||
return withBlocks(py, 'python', source, blocks =>
|
||||
toBlockViews(blocks, { source, language: py, markers: [] }, pythonZhTranslator, { rules: [celeryRule], report: message => reports.push(message) }))
|
||||
toBlockViews(blocks, { source, language: py, markers: [] }, pythonZhTranslator, { languageId: 'python', rules: [celeryRule], report: message => reports.push(message) }))
|
||||
}
|
||||
|
||||
function properties(view: BlockView | undefined): PropertyRow[] {
|
||||
|
||||
@@ -19,7 +19,7 @@ const RULES = [sqlalchemyRule, celeryRule, enumRule, dataclassRule]
|
||||
|
||||
async function views(source: string, reports: string[] = []): Promise<BlockView[]> {
|
||||
return withBlocks(py, 'python', source, blocks =>
|
||||
toBlockViews(blocks, { source, language: py, markers: [] }, pythonZhTranslator, { rules: RULES, report: message => reports.push(message) }))
|
||||
toBlockViews(blocks, { source, language: py, markers: [] }, pythonZhTranslator, { languageId: 'python', rules: RULES, report: message => reports.push(message) }))
|
||||
}
|
||||
|
||||
function properties(view: BlockView | undefined): PropertyRow[] {
|
||||
|
||||
@@ -21,11 +21,11 @@ const RULES = [sqlalchemyRule, celeryRule, enumRule]
|
||||
|
||||
async function views(source: string, reports: string[] = []): Promise<BlockView[]> {
|
||||
return withBlocks(py, 'python', source, blocks =>
|
||||
toBlockViews(blocks, { source, language: py, markers: [] }, pythonZhTranslator, { rules: RULES, report: message => reports.push(message) }))
|
||||
toBlockViews(blocks, { source, language: py, markers: [] }, pythonZhTranslator, { languageId: 'python', rules: RULES, report: message => reports.push(message) }))
|
||||
}
|
||||
|
||||
function tsViews(source: string): Promise<BlockView[]> {
|
||||
return withBlocks(ts, 'typescript', source, blocks => toBlockViews(blocks, { source, language: ts, markers: [] }, typescriptZhTranslator, { rules: RULES, report: () => {} }))
|
||||
return withBlocks(ts, 'typescript', source, blocks => toBlockViews(blocks, { source, language: ts, markers: [] }, typescriptZhTranslator, { languageId: 'typescript', rules: RULES, report: () => {} }))
|
||||
}
|
||||
|
||||
function properties(view: BlockView | undefined): PropertyRow[] {
|
||||
|
||||
@@ -23,7 +23,7 @@ function known(values: Record<string, string>): RuleContext['lookup'] {
|
||||
|
||||
async function views(source: string, lookup?: RuleContext['lookup'], reports: string[] = []): Promise<BlockView[]> {
|
||||
return withBlocks(py, 'python', source, blocks =>
|
||||
toBlockViews(blocks, { source, language: py, markers: [] }, pythonZhTranslator, { rules: [fastapiRule], report: message => reports.push(message), lookup }))
|
||||
toBlockViews(blocks, { source, language: py, markers: [] }, pythonZhTranslator, { languageId: 'python', rules: [fastapiRule], report: message => reports.push(message), lookup }))
|
||||
}
|
||||
|
||||
function flatten(list: readonly BlockView[]): BlockView[] {
|
||||
|
||||
@@ -22,7 +22,7 @@ const RULES = [sqlalchemyRule, celeryRule, enumRule, dataclassRule, pydanticRule
|
||||
|
||||
async function views(source: string, reports: string[] = [], lookup?: RuleContext['lookup']): Promise<BlockView[]> {
|
||||
return withBlocks(py, 'python', source, blocks =>
|
||||
toBlockViews(blocks, { source, language: py, markers: [] }, pythonZhTranslator, { rules: RULES, report: message => reports.push(message), lookup }))
|
||||
toBlockViews(blocks, { source, language: py, markers: [] }, pythonZhTranslator, { languageId: 'python', rules: RULES, report: message => reports.push(message), lookup }))
|
||||
}
|
||||
|
||||
async function indexEntries(source: string): Promise<IndexEntry[]> {
|
||||
|
||||
@@ -1,9 +1,11 @@
|
||||
import type { Memento } from 'vscode'
|
||||
import type { BlockView } from '../blockView'
|
||||
import type { PropertyItem, PropertyRow, RuleContext } from '../contract'
|
||||
import { describe, expect, it } from 'vitest'
|
||||
import { toBlockViews } from '../blockView'
|
||||
import { diffBlockTrees } from '../diff/blockDiff'
|
||||
import { createTypeScriptAdapter } from '../languages/typescript'
|
||||
import { CodeReaderRegistry } from '../registry'
|
||||
import { inlineOf, previewText, propertyLines, propertyRow, readFixture, viewLines, WASM_DIR, withBlocks } from '../testSupport'
|
||||
import { typescriptZhTranslator } from '../translators/typescriptZh'
|
||||
import { reactRule } from './react'
|
||||
@@ -18,6 +20,7 @@ const SETTINGS = readFixture('desktopSettingsDialog.tsx.txt')
|
||||
const PLAN = readFixture('planChecklist.tsx.txt')
|
||||
const QUEUED = readFixture('queuedBubble.tsx.txt')
|
||||
const HOOKS = readFixture('hooksPanel.tsx.txt')
|
||||
const SHELL_EVENTS = readFixture('useShellEvents.ts.txt')
|
||||
|
||||
/** 钩子卡片的描边、钩子图标:所有钩子一样的紫 */
|
||||
const HOOK = { dark: '#b180d7', light: '#7f3fa6' }
|
||||
@@ -35,9 +38,13 @@ function known(values: Record<string, string>): RuleContext['lookup'] {
|
||||
return (_index, node) => values[node.text] === undefined ? undefined : { key: node.text, name: { startIndex: 0, endIndex: 0 }, value: values[node.text] }
|
||||
}
|
||||
|
||||
async function views(source: string, lookup?: RuleContext['lookup'], reports: string[] = []): Promise<BlockView[]> {
|
||||
return withBlocks(ts, 'typescriptreact', source, blocks =>
|
||||
toBlockViews(blocks, { source, language: ts, markers: [] }, typescriptZhTranslator, { rules: [reactRule], report: message => reports.push(message), lookup }))
|
||||
/** 只注册了 React 规则的注册表:同宿主一样照文件的 languageId 从这里挑规则。 */
|
||||
const registry = new CodeReaderRegistry({ get: () => undefined, update: async () => {} } as Pick<Memento, 'get' | 'update'>)
|
||||
registry.registerLibraryRule(reactRule)
|
||||
|
||||
async function views(source: string, lookup?: RuleContext['lookup'], reports: string[] = [], languageId = 'typescriptreact'): Promise<BlockView[]> {
|
||||
return withBlocks(ts, languageId, source, blocks =>
|
||||
toBlockViews(blocks, { source, language: ts, markers: [] }, typescriptZhTranslator, { languageId, rules: registry.rulesFor(languageId), report: message => reports.push(message), lookup }))
|
||||
}
|
||||
|
||||
function flatten(list: readonly BlockView[]): BlockView[] {
|
||||
@@ -83,9 +90,41 @@ function kinds(view: BlockView): string[] {
|
||||
}
|
||||
|
||||
describe('react 库规则:认领', () => {
|
||||
it('只管 TSX / JSX,不看导入(新的 JSX 写法不用导入 react)', () => {
|
||||
expect(reactRule.languageIds).toEqual(['typescriptreact', 'javascriptreact'])
|
||||
expect(reactRule.libraries).toEqual([])
|
||||
it('tsx / jsx 不看导入就启用(新的 JSX 写法不用导入 react),.ts / .js 导入了 react 才启用', () => {
|
||||
expect(reactRule.languageIds).toEqual(['typescriptreact', 'javascriptreact', 'typescript', 'javascript'])
|
||||
expect(reactRule.libraries).toEqual(['react'])
|
||||
expect(reactRule.alwaysIn).toEqual(['typescriptreact', 'javascriptreact'])
|
||||
})
|
||||
|
||||
it('luffy 的 use-shell-events.ts:导入了 react 的 .ts 文件里,自定义钩子出自定义钩子卡片,里面的钩子调用出钩子卡片', async () => {
|
||||
const blocks = await views(SHELL_EVENTS, undefined, [], 'typescript')
|
||||
expect(blocks.map(view => `${view.kind} ${view.startLine + 1}`)).toEqual(['statement 1', 'import 3', 'import 4', 'import 6', 'import 7', 'import 9', 'import 10', 'react.customHook 12'])
|
||||
const hook = blocks[7]
|
||||
expect(lines(hook)).toEqual([
|
||||
'名称: useShellEvents ⟦具名导出|⟧',
|
||||
'说明: 挂载时经 [tauriBridge] 取一次全量状态与壳留存的活动,之后靠事件同步;不在壳里时什么也不取。\n\n另在这里注册窗口内快捷键。',
|
||||
'参数: 无',
|
||||
])
|
||||
expect(kinds(hook)).toEqual(['react.hook 18', 'react.hook 19', 'react.hook 20', 'react.hook 22', 'react.hook 33'])
|
||||
expect(lines(childAt(hook, 18))).toEqual(['钩子: useRouter', '返回: 1 个', ' router | 路由器,用于页面跳转'])
|
||||
expect(lines(childAt(hook, 20))).toEqual(['钩子: useSidebarCollapsed', '返回: 1 个', ' toggleSidebar'])
|
||||
expect(lines(childAt(hook, 22))).toEqual(['钩子: useEffect', '参数: 2 个', ' effect | ⟦函数|· 4 句⟧', ' deps | router 变化时'])
|
||||
expect(rowLines(childAt(hook, 33), '参数')).toEqual(['参数: 2 个', ' effect | ⟦函数|· 4 句⟧', ' deps | shell、toggleSidebar 变化时'])
|
||||
})
|
||||
|
||||
it('没导入 react 的 .ts 文件不启用:zustand 的 store 叫 useXxx 也照常是常量、函数块;同样的源码写在 .tsx 里照常认', async () => {
|
||||
const source = [
|
||||
'import { create } from "zustand";',
|
||||
'export const useDesktopShell = create<State>((set) => ({ title: "" }));',
|
||||
'export function useTitle() {',
|
||||
' const title = useDesktopShell((s) => s.title);',
|
||||
' return title;',
|
||||
'}',
|
||||
].join('\n')
|
||||
const reports: string[] = []
|
||||
expect((await views(source, undefined, reports, 'typescript')).map(view => view.kind)).toEqual(['import', 'const', 'function'])
|
||||
expect((await views(source, undefined, reports, 'typescriptreact')).map(view => view.kind)).toEqual(['import', 'const', 'react.customHook'])
|
||||
expect(reports).toEqual([])
|
||||
})
|
||||
|
||||
it('luffy 的 DesktopTitlebar:五条导入后面是组件卡片,只有名称 / 说明 / 构造参数;组件体照源码顺序是子块,"use client" 和文档注释不单独成块', async () => {
|
||||
|
||||
@@ -20,7 +20,8 @@ import { hint, joined, name, plain, range } from './segments'
|
||||
* `.map` 写「对 … 的每一项」并标 key;属性值是元素、条件或列表时属性名单独成行,值照同一套大纲缩进在下面。
|
||||
* 回调、函数里的语句照常是这些卡片的子块;普通变量、if、注释照语言默认的卡片。表格的格子里只写名字和位置,不放函数体。
|
||||
* 文件开头的 "use client" 讲成了「客户端组件」、组件上面的 JSDoc 讲成了「说明」,都不再单独成块。
|
||||
* 新的 JSX 写法不用导入 react,规则不看导入:TSX / JSX 文件都启用。用词都在词表里,按翻译插件的显示语言挑。
|
||||
* 新的 JSX 写法不用导入 react,TSX / JSX 文件不看导入就启用;自定义钩子常写在 .ts / .js 文件里,这两种导入了 react 才启用
|
||||
* (没有 JSX,只认得出自定义钩子和钩子调用)。用词都在词表里,按翻译插件的显示语言挑。
|
||||
*/
|
||||
|
||||
const RULE_ID = 'react'
|
||||
@@ -1193,8 +1194,9 @@ function omitted(block: CodeBlock): boolean {
|
||||
export const reactRule: LibraryRule = {
|
||||
id: RULE_ID,
|
||||
label: 'React 组件',
|
||||
languageIds: ['typescriptreact', 'javascriptreact'],
|
||||
libraries: [],
|
||||
languageIds: ['typescriptreact', 'javascriptreact', 'typescript', 'javascript'],
|
||||
libraries: ['react'],
|
||||
alwaysIn: ['typescriptreact', 'javascriptreact'],
|
||||
claim: (block, context) => {
|
||||
const env: Env = { context, words: WORDS[context.displayLanguage ?? ''] ?? ZH }
|
||||
const component = componentOf(block)
|
||||
|
||||
@@ -18,6 +18,7 @@ const rules = [rustImplRule, serdeRule, thiserrorRule, axumRule, sqlxRule, tauri
|
||||
function views(source: string): Promise<BlockView[]> {
|
||||
return withBlocks(adapter, 'rust', source, blocks =>
|
||||
toBlockViews(blocks, { source, language: adapter, markers: [] }, rustZhTranslator, {
|
||||
languageId: 'rust',
|
||||
rules,
|
||||
report: (message) => {
|
||||
throw new Error(message)
|
||||
|
||||
@@ -13,7 +13,7 @@ const SNAPSHOT = readFixture('sqlalchemyModels.py')
|
||||
|
||||
async function views(source: string, reports: string[] = []): Promise<BlockView[]> {
|
||||
return withBlocks(py, 'python', source, blocks =>
|
||||
toBlockViews(blocks, { source, language: py, markers: [] }, pythonZhTranslator, { rules: [sqlalchemyRule], report: message => reports.push(message) }))
|
||||
toBlockViews(blocks, { source, language: py, markers: [] }, pythonZhTranslator, { languageId: 'python', rules: [sqlalchemyRule], report: message => reports.push(message) }))
|
||||
}
|
||||
|
||||
function flatten(list: BlockView[]): BlockView[] {
|
||||
|
||||
Reference in new issue
Block a user