style: 完成所有文件的lint

This commit is contained in:
claude-code-best
2026-05-01 21:39:30 +08:00
parent d136872cc9
commit 6182015005
1333 changed files with 68255 additions and 77882 deletions

View File

@@ -1,19 +1,16 @@
import type { ToolResultBlockParam } from '@anthropic-ai/sdk/resources/index.mjs'
import React from 'react'
import { CtrlOToExpand } from 'src/components/CtrlOToExpand.js'
import { FallbackToolUseErrorMessage } from 'src/components/FallbackToolUseErrorMessage.js'
import { MessageResponse } from 'src/components/MessageResponse.js'
import { Box, Text } from '@anthropic/ink'
import { getDisplayPath } from 'src/utils/file.js'
import { extractTag } from 'src/utils/messages.js'
import type { Input, Output } from './LSPTool.js'
import { getSymbolAtPosition } from './symbolContext.js'
import type { ToolResultBlockParam } from '@anthropic-ai/sdk/resources/index.mjs';
import React from 'react';
import { CtrlOToExpand } from 'src/components/CtrlOToExpand.js';
import { FallbackToolUseErrorMessage } from 'src/components/FallbackToolUseErrorMessage.js';
import { MessageResponse } from 'src/components/MessageResponse.js';
import { Box, Text } from '@anthropic/ink';
import { getDisplayPath } from 'src/utils/file.js';
import { extractTag } from 'src/utils/messages.js';
import type { Input, Output } from './LSPTool.js';
import { getSymbolAtPosition } from './symbolContext.js';
// Lookup map for operation-specific labels
const OPERATION_LABELS: Record<
Input['operation'],
{ singular: string; plural: string; special?: string }
> = {
const OPERATION_LABELS: Record<Input['operation'], { singular: string; plural: string; special?: string }> = {
goToDefinition: { singular: 'definition', plural: 'definitions' },
findReferences: { singular: 'reference', plural: 'references' },
documentSymbol: { singular: 'symbol', plural: 'symbols' },
@@ -23,7 +20,7 @@ const OPERATION_LABELS: Record<
prepareCallHierarchy: { singular: 'call item', plural: 'call items' },
incomingCalls: { singular: 'caller', plural: 'callers' },
outgoingCalls: { singular: 'callee', plural: 'callees' },
}
};
/**
* Reusable component for LSP result summaries with collapsed/expanded views
@@ -35,19 +32,18 @@ function LSPResultSummary({
content,
verbose,
}: {
operation: Input['operation']
resultCount: number
fileCount: number
content: string
verbose: boolean
operation: Input['operation'];
resultCount: number;
fileCount: number;
content: string;
verbose: boolean;
}): React.ReactNode {
// Get label configuration for this operation
const labelConfig = OPERATION_LABELS[operation] || {
singular: 'result',
plural: 'results',
}
const countLabel =
resultCount === 1 ? labelConfig.singular : labelConfig.plural
};
const countLabel = resultCount === 1 ? labelConfig.singular : labelConfig.plural;
const primaryText =
operation === 'hover' && resultCount > 0 && labelConfig.special ? (
@@ -57,7 +53,7 @@ function LSPResultSummary({
Found <Text bold>{resultCount} </Text>
{countLabel}
</Text>
)
);
const secondaryText =
fileCount > 1 ? (
@@ -66,7 +62,7 @@ function LSPResultSummary({
across <Text bold>{fileCount} </Text>
files
</Text>
) : null
) : null;
if (verbose) {
return (
@@ -82,7 +78,7 @@ function LSPResultSummary({
<Text>{content}</Text>
</Box>
</Box>
)
);
}
return (
@@ -92,22 +88,19 @@ function LSPResultSummary({
{secondaryText} {resultCount > 0 && <CtrlOToExpand />}
</Text>
</MessageResponse>
)
);
}
export function userFacingName(): string {
return 'LSP'
return 'LSP';
}
export function renderToolUseMessage(
input: Partial<Input>,
{ verbose }: { verbose: boolean },
): React.ReactNode {
export function renderToolUseMessage(input: Partial<Input>, { verbose }: { verbose: boolean }): React.ReactNode {
if (!input.operation) {
return null
return null;
}
const parts: string[] = []
const parts: string[] = [];
// For position-based operations (goToDefinition, findReferences, hover, goToImplementation),
// show the symbol at the position for better context
@@ -121,58 +114,46 @@ export function renderToolUseMessage(
input.character !== undefined
) {
// Convert from 1-based (user input) to 0-based (internal file reading)
const symbol = getSymbolAtPosition(
input.filePath,
input.line - 1,
input.character - 1,
)
const displayPath = verbose
? input.filePath
: getDisplayPath(input.filePath)
const symbol = getSymbolAtPosition(input.filePath, input.line - 1, input.character - 1);
const displayPath = verbose ? input.filePath : getDisplayPath(input.filePath);
if (symbol) {
parts.push(`operation: "${input.operation}"`)
parts.push(`symbol: "${symbol}"`)
parts.push(`in: "${displayPath}"`)
parts.push(`operation: "${input.operation}"`);
parts.push(`symbol: "${symbol}"`);
parts.push(`in: "${displayPath}"`);
} else {
parts.push(`operation: "${input.operation}"`)
parts.push(`file: "${displayPath}"`)
parts.push(`position: ${input.line}:${input.character}`)
parts.push(`operation: "${input.operation}"`);
parts.push(`file: "${displayPath}"`);
parts.push(`position: ${input.line}:${input.character}`);
}
return parts.join(', ')
return parts.join(', ');
}
// For other operations (documentSymbol, workspaceSymbol),
// show operation and file without position details
parts.push(`operation: "${input.operation}"`)
parts.push(`operation: "${input.operation}"`);
if (input.filePath) {
const displayPath = verbose
? input.filePath
: getDisplayPath(input.filePath)
parts.push(`file: "${displayPath}"`)
const displayPath = verbose ? input.filePath : getDisplayPath(input.filePath);
parts.push(`file: "${displayPath}"`);
}
return parts.join(', ')
return parts.join(', ');
}
export function renderToolUseErrorMessage(
result: ToolResultBlockParam['content'],
{ verbose }: { verbose: boolean },
): React.ReactNode {
if (
!verbose &&
typeof result === 'string' &&
extractTag(result, 'tool_use_error')
) {
if (!verbose && typeof result === 'string' && extractTag(result, 'tool_use_error')) {
return (
<MessageResponse>
<Text color="error">LSP operation failed</Text>
</MessageResponse>
)
);
}
return <FallbackToolUseErrorMessage result={result} verbose={verbose} />
return <FallbackToolUseErrorMessage result={result} verbose={verbose} />;
}
export function renderToolResultMessage(
@@ -190,7 +171,7 @@ export function renderToolResultMessage(
content={output.result}
verbose={verbose}
/>
)
);
}
// Fallback for error cases where counts aren't available
@@ -199,5 +180,5 @@ export function renderToolResultMessage(
<MessageResponse>
<Text>{output.result}</Text>
</MessageResponse>
)
);
}

View File

@@ -1,7 +1,7 @@
import { mock, describe, expect, test } from "bun:test";
import { debugMock } from "../../../../../../tests/mocks/debug";
import { mock, describe, expect, test } from 'bun:test'
import { debugMock } from '../../../../../../tests/mocks/debug'
mock.module("src/utils/debug.ts", debugMock);
mock.module('src/utils/debug.ts', debugMock)
const {
formatGoToDefinitionResult,
@@ -12,23 +12,36 @@ const {
formatPrepareCallHierarchyResult,
formatIncomingCallsResult,
formatOutgoingCallsResult,
} = await import("../formatters");
} = await import('../formatters')
// Minimal LSP type stubs for testing
const makeLocation = (uri: string, startLine: number, startChar: number, endLine: number, endChar: number) => ({
const makeLocation = (
uri: string,
startLine: number,
startChar: number,
endLine: number,
endChar: number,
) => ({
uri,
range: {
start: { line: startLine, character: startChar },
end: { line: endLine, character: endChar },
},
});
})
const makeSymbol = (name: string, kind: number, range: { start: { line: number; character: number }; end: { line: number; character: number } }) => ({
const makeSymbol = (
name: string,
kind: number,
range: {
start: { line: number; character: number }
end: { line: number; character: number }
},
) => ({
name,
kind,
range,
children: undefined,
});
})
const makeCallItem = (name: string, uri: string, line: number) => ({
name,
@@ -42,145 +55,153 @@ const makeCallItem = (name: string, uri: string, line: number) => ({
start: { line: line, character: 0 },
end: { line: line, character: name.length },
},
});
})
describe("formatGoToDefinitionResult", () => {
test("returns no definitions message for null", () => {
const result = formatGoToDefinitionResult(null);
expect(result).toContain("No definition found");
});
describe('formatGoToDefinitionResult', () => {
test('returns no definitions message for null', () => {
const result = formatGoToDefinitionResult(null)
expect(result).toContain('No definition found')
})
test("formats single location", () => {
const loc = makeLocation("file:///src/foo.ts", 10, 5, 10, 15);
const result = formatGoToDefinitionResult(loc);
expect(result).toContain("foo.ts");
test('formats single location', () => {
const loc = makeLocation('file:///src/foo.ts', 10, 5, 10, 15)
const result = formatGoToDefinitionResult(loc)
expect(result).toContain('foo.ts')
// LSP lines are 0-based, display is 1-based → line 10 = display line 11
expect(result).toContain("11");
});
expect(result).toContain('11')
})
test("formats array of locations", () => {
test('formats array of locations', () => {
const locs = [
makeLocation("file:///src/a.ts", 1, 0, 1, 5),
makeLocation("file:///src/b.ts", 5, 0, 5, 5),
];
const result = formatGoToDefinitionResult(locs);
expect(result).toContain("a.ts");
expect(result).toContain("b.ts");
});
});
makeLocation('file:///src/a.ts', 1, 0, 1, 5),
makeLocation('file:///src/b.ts', 5, 0, 5, 5),
]
const result = formatGoToDefinitionResult(locs)
expect(result).toContain('a.ts')
expect(result).toContain('b.ts')
})
})
describe("formatFindReferencesResult", () => {
test("returns no references message for null", () => {
expect(formatFindReferencesResult(null)).toContain("No references found");
});
describe('formatFindReferencesResult', () => {
test('returns no references message for null', () => {
expect(formatFindReferencesResult(null)).toContain('No references found')
})
test("formats references", () => {
test('formats references', () => {
const refs = [
makeLocation("file:///src/a.ts", 1, 0, 1, 5),
makeLocation("file:///src/b.ts", 3, 0, 3, 5),
];
const result = formatFindReferencesResult(refs);
expect(result).toContain("a.ts");
expect(result).toContain("b.ts");
});
});
makeLocation('file:///src/a.ts', 1, 0, 1, 5),
makeLocation('file:///src/b.ts', 3, 0, 3, 5),
]
const result = formatFindReferencesResult(refs)
expect(result).toContain('a.ts')
expect(result).toContain('b.ts')
})
})
describe("formatHoverResult", () => {
test("returns no hover message for null", () => {
expect(formatHoverResult(null)).toContain("No hover information");
});
describe('formatHoverResult', () => {
test('returns no hover message for null', () => {
expect(formatHoverResult(null)).toContain('No hover information')
})
test("formats hover with string contents", () => {
test('formats hover with string contents', () => {
const hover = {
contents: { kind: "plaintext", value: "string" },
range: makeLocation("file:///a.ts", 0, 0, 0, 5).range,
};
const result = formatHoverResult(hover as any);
expect(result).toContain("string");
});
});
contents: { kind: 'plaintext', value: 'string' },
range: makeLocation('file:///a.ts', 0, 0, 0, 5).range,
}
const result = formatHoverResult(hover as any)
expect(result).toContain('string')
})
})
describe("formatDocumentSymbolResult", () => {
test("returns no symbols message for null", () => {
expect(formatDocumentSymbolResult(null)).toContain("No symbols found");
});
describe('formatDocumentSymbolResult', () => {
test('returns no symbols message for null', () => {
expect(formatDocumentSymbolResult(null)).toContain('No symbols found')
})
test("returns no symbols for empty array", () => {
expect(formatDocumentSymbolResult([])).toContain("No symbols found");
});
test('returns no symbols for empty array', () => {
expect(formatDocumentSymbolResult([])).toContain('No symbols found')
})
test("formats document symbols", () => {
test('formats document symbols', () => {
const symbols = [
makeSymbol("MyClass", 5, { start: { line: 0, character: 0 }, end: { line: 10, character: 0 } }),
makeSymbol("myMethod", 6, { start: { line: 2, character: 0 }, end: { line: 5, character: 0 } }),
];
const result = formatDocumentSymbolResult(symbols as any);
expect(result).toContain("MyClass");
expect(result).toContain("myMethod");
});
});
makeSymbol('MyClass', 5, {
start: { line: 0, character: 0 },
end: { line: 10, character: 0 },
}),
makeSymbol('myMethod', 6, {
start: { line: 2, character: 0 },
end: { line: 5, character: 0 },
}),
]
const result = formatDocumentSymbolResult(symbols as any)
expect(result).toContain('MyClass')
expect(result).toContain('myMethod')
})
})
describe("formatWorkspaceSymbolResult", () => {
test("returns no symbols for null", () => {
expect(formatWorkspaceSymbolResult(null)).toContain("No symbols found");
});
describe('formatWorkspaceSymbolResult', () => {
test('returns no symbols for null', () => {
expect(formatWorkspaceSymbolResult(null)).toContain('No symbols found')
})
test("formats workspace symbols", () => {
test('formats workspace symbols', () => {
const symbols = [
{
name: "SearchResult",
name: 'SearchResult',
kind: 12,
location: makeLocation("file:///src/a.ts", 0, 0, 0, 5),
location: makeLocation('file:///src/a.ts', 0, 0, 0, 5),
},
];
const result = formatWorkspaceSymbolResult(symbols as any);
expect(result).toContain("SearchResult");
});
});
]
const result = formatWorkspaceSymbolResult(symbols as any)
expect(result).toContain('SearchResult')
})
})
describe("formatPrepareCallHierarchyResult", () => {
test("returns no items for null", () => {
expect(formatPrepareCallHierarchyResult(null)).toContain("No call hierarchy");
});
describe('formatPrepareCallHierarchyResult', () => {
test('returns no items for null', () => {
expect(formatPrepareCallHierarchyResult(null)).toContain(
'No call hierarchy',
)
})
test("formats call hierarchy items", () => {
const items = [makeCallItem("main", "file:///src/main.ts", 5)];
const result = formatPrepareCallHierarchyResult(items as any);
expect(result).toContain("main");
expect(result).toContain("main.ts");
});
});
test('formats call hierarchy items', () => {
const items = [makeCallItem('main', 'file:///src/main.ts', 5)]
const result = formatPrepareCallHierarchyResult(items as any)
expect(result).toContain('main')
expect(result).toContain('main.ts')
})
})
describe("formatIncomingCallsResult", () => {
test("returns no calls for null", () => {
expect(formatIncomingCallsResult(null)).toContain("No incoming calls");
});
describe('formatIncomingCallsResult', () => {
test('returns no calls for null', () => {
expect(formatIncomingCallsResult(null)).toContain('No incoming calls')
})
test("formats incoming calls", () => {
test('formats incoming calls', () => {
const calls = [
{
from: makeCallItem("caller", "file:///src/a.ts", 3),
fromRanges: [makeLocation("file:///src/a.ts", 3, 0, 3, 5).range],
from: makeCallItem('caller', 'file:///src/a.ts', 3),
fromRanges: [makeLocation('file:///src/a.ts', 3, 0, 3, 5).range],
},
];
const result = formatIncomingCallsResult(calls as any);
expect(result).toContain("caller");
});
});
]
const result = formatIncomingCallsResult(calls as any)
expect(result).toContain('caller')
})
})
describe("formatOutgoingCallsResult", () => {
test("returns no calls for null", () => {
expect(formatOutgoingCallsResult(null)).toContain("No outgoing calls");
});
describe('formatOutgoingCallsResult', () => {
test('returns no calls for null', () => {
expect(formatOutgoingCallsResult(null)).toContain('No outgoing calls')
})
test("formats outgoing calls", () => {
test('formats outgoing calls', () => {
const calls = [
{
to: makeCallItem("callee", "file:///src/b.ts", 10),
fromRanges: [makeLocation("file:///src/main.ts", 5, 0, 5, 5).range],
to: makeCallItem('callee', 'file:///src/b.ts', 10),
fromRanges: [makeLocation('file:///src/main.ts', 5, 0, 5, 5).range],
},
];
const result = formatOutgoingCallsResult(calls as any);
expect(result).toContain("callee");
});
});
]
const result = formatOutgoingCallsResult(calls as any)
expect(result).toContain('callee')
})
})

View File

@@ -1,37 +1,37 @@
import { describe, expect, test } from "bun:test";
import { isValidLSPOperation } from "../schemas";
import { describe, expect, test } from 'bun:test'
import { isValidLSPOperation } from '../schemas'
describe("isValidLSPOperation", () => {
describe('isValidLSPOperation', () => {
const validOps = [
"goToDefinition",
"findReferences",
"hover",
"documentSymbol",
"workspaceSymbol",
"goToImplementation",
"prepareCallHierarchy",
"incomingCalls",
"outgoingCalls",
];
'goToDefinition',
'findReferences',
'hover',
'documentSymbol',
'workspaceSymbol',
'goToImplementation',
'prepareCallHierarchy',
'incomingCalls',
'outgoingCalls',
]
test.each(validOps)("returns true for valid operation: %s", (op) => {
expect(isValidLSPOperation(op)).toBe(true);
});
test.each(validOps)('returns true for valid operation: %s', op => {
expect(isValidLSPOperation(op)).toBe(true)
})
test("returns false for invalid operation", () => {
expect(isValidLSPOperation("invalidOp")).toBe(false);
});
test('returns false for invalid operation', () => {
expect(isValidLSPOperation('invalidOp')).toBe(false)
})
test("returns false for empty string", () => {
expect(isValidLSPOperation("")).toBe(false);
});
test('returns false for empty string', () => {
expect(isValidLSPOperation('')).toBe(false)
})
test("returns false for undefined", () => {
expect(isValidLSPOperation(undefined as any)).toBe(false);
});
test('returns false for undefined', () => {
expect(isValidLSPOperation(undefined as any)).toBe(false)
})
test("is case sensitive", () => {
expect(isValidLSPOperation("GoToDefinition")).toBe(false);
expect(isValidLSPOperation("HOVER")).toBe(false);
});
});
test('is case sensitive', () => {
expect(isValidLSPOperation('GoToDefinition')).toBe(false)
expect(isValidLSPOperation('HOVER')).toBe(false)
})
})