Fix(grep): memory overflow in grep search and enhance test coverage (#5911)

Co-authored-by: Jacob Richman <jacob314@gmail.com>
This commit is contained in:
fuyou
2025-08-22 14:10:45 +08:00
committed by GitHub
parent 51f642f0a9
commit ef46d64ae5
20 changed files with 2566 additions and 36 deletions

View File

@@ -9,6 +9,7 @@ import { render } from 'ink-testing-library';
import { DiffRenderer } from './DiffRenderer.js';
import * as CodeColorizer from '../../utils/CodeColorizer.js';
import { vi } from 'vitest';
import { EOL } from 'os';
describe('<OverflowProvider><DiffRenderer /></OverflowProvider>', () => {
const mockColorizeCode = vi.spyOn(CodeColorizer, 'colorizeCode');
@@ -29,7 +30,7 @@ index 0000000..e69de29
+++ b/test.py
@@ -0,0 +1 @@
+print("hello world")
`;
`.replace(/\n/g, EOL);
render(
<OverflowProvider>
<DiffRenderer
@@ -57,7 +58,7 @@ index 0000000..e69de29
+++ b/test.unknown
@@ -0,0 +1 @@
+some content
`;
`.replace(/\n/g, EOL);
render(
<OverflowProvider>
<DiffRenderer
@@ -85,7 +86,7 @@ index 0000000..e69de29
+++ b/test.txt
@@ -0,0 +1 @@
+some text content
`;
`.replace(/\n/g, EOL);
render(
<OverflowProvider>
<DiffRenderer diffContent={newFileDiffContent} terminalWidth={80} />
@@ -109,7 +110,7 @@ index 0000001..0000002 100644
@@ -1 +1 @@
-old line
+new line
`;
`.replace(/\n/g, EOL);
const { lastFrame } = render(
<OverflowProvider>
<DiffRenderer
@@ -139,7 +140,7 @@ index 0000001..0000002 100644
index 1234567..1234567 100644
--- a/file.txt
+++ b/file.txt
`;
`.replace(/\n/g, EOL);
const { lastFrame } = render(
<OverflowProvider>
<DiffRenderer
@@ -176,7 +177,7 @@ index 123..456 100644
@@ -10,2 +10,2 @@
context line 10
context line 11
`;
`.replace(/\n/g, EOL);
const { lastFrame } = render(
<OverflowProvider>
<DiffRenderer
@@ -213,7 +214,7 @@ index abc..def 100644
context line 13
context line 14
context line 15
`;
`.replace(/\n/g, EOL);
const { lastFrame } = render(
<OverflowProvider>
<DiffRenderer
@@ -247,7 +248,7 @@ index 123..789 100644
-const anotherOld = 'test';
+const anotherNew = 'test';
console.log('end of second hunk');
`;
`.replace(/\n/g, EOL);
it.each([
{
@@ -317,7 +318,7 @@ fileDiff Index: file.txt
-const anotherOld = 'test';
+const anotherNew = 'test';
\\ No newline at end of file
`;
`.replace(/\n/g, EOL);
const { lastFrame } = render(
<OverflowProvider>
<DiffRenderer
@@ -347,7 +348,7 @@ fileDiff Index: Dockerfile
+RUN npm install
+RUN npm run build
\\ No newline at end of file
`;
`.replace(/\n/g, EOL);
const { lastFrame } = render(
<OverflowProvider>
<DiffRenderer

View File

@@ -6,6 +6,7 @@
import React from 'react';
import { Box, Text } from 'ink';
import { EOL } from 'os';
import { Colors } from '../../colors.js';
import crypto from 'crypto';
import { colorizeCode, colorizeLine } from '../../utils/CodeColorizer.js';
@@ -20,7 +21,7 @@ interface DiffLine {
}
function parseDiffWithLineNumbers(diffContent: string): DiffLine[] {
const lines = diffContent.split('\n');
const lines = diffContent.split(EOL);
const result: DiffLine[] = [];
let currentOldLine = 0;
let currentNewLine = 0;

View File

@@ -9,6 +9,7 @@ import { describe, it, expect, vi, beforeEach } from 'vitest';
import { MarkdownDisplay } from './MarkdownDisplay.js';
import { LoadedSettings } from '../../config/settings.js';
import { SettingsContext } from '../contexts/SettingsContext.js';
import { EOL } from 'os';
describe('<MarkdownDisplay />', () => {
const baseProps = {
@@ -54,7 +55,7 @@ describe('<MarkdownDisplay />', () => {
## Header 2
### Header 3
#### Header 4
`;
`.replace(/\n/g, EOL);
const { lastFrame } = render(
<SettingsContext.Provider value={mockSettings}>
<MarkdownDisplay {...baseProps} text={text} />
@@ -64,7 +65,10 @@ describe('<MarkdownDisplay />', () => {
});
it('renders a fenced code block with a language', () => {
const text = '```javascript\nconst x = 1;\nconsole.log(x);\n```';
const text = '```javascript\nconst x = 1;\nconsole.log(x);\n```'.replace(
/\n/g,
EOL,
);
const { lastFrame } = render(
<SettingsContext.Provider value={mockSettings}>
<MarkdownDisplay {...baseProps} text={text} />
@@ -74,7 +78,7 @@ describe('<MarkdownDisplay />', () => {
});
it('renders a fenced code block without a language', () => {
const text = '```\nplain text\n```';
const text = '```\nplain text\n```'.replace(/\n/g, EOL);
const { lastFrame } = render(
<SettingsContext.Provider value={mockSettings}>
<MarkdownDisplay {...baseProps} text={text} />
@@ -84,7 +88,7 @@ describe('<MarkdownDisplay />', () => {
});
it('handles unclosed (pending) code blocks', () => {
const text = '```typescript\nlet y = 2;';
const text = '```typescript\nlet y = 2;'.replace(/\n/g, EOL);
const { lastFrame } = render(
<SettingsContext.Provider value={mockSettings}>
<MarkdownDisplay {...baseProps} text={text} isPending={true} />
@@ -98,7 +102,7 @@ describe('<MarkdownDisplay />', () => {
- item A
* item B
+ item C
`;
`.replace(/\n/g, EOL);
const { lastFrame } = render(
<SettingsContext.Provider value={mockSettings}>
<MarkdownDisplay {...baseProps} text={text} />
@@ -112,7 +116,7 @@ describe('<MarkdownDisplay />', () => {
* Level 1
* Level 2
* Level 3
`;
`.replace(/\n/g, EOL);
const { lastFrame } = render(
<SettingsContext.Provider value={mockSettings}>
<MarkdownDisplay {...baseProps} text={text} />
@@ -125,7 +129,7 @@ describe('<MarkdownDisplay />', () => {
const text = `
1. First item
2. Second item
`;
`.replace(/\n/g, EOL);
const { lastFrame } = render(
<SettingsContext.Provider value={mockSettings}>
<MarkdownDisplay {...baseProps} text={text} />
@@ -141,7 +145,7 @@ Hello
World
***
Test
`;
`.replace(/\n/g, EOL);
const { lastFrame } = render(
<SettingsContext.Provider value={mockSettings}>
<MarkdownDisplay {...baseProps} text={text} />
@@ -156,7 +160,7 @@ Test
|----------|:--------:|
| Cell 1 | Cell 2 |
| Cell 3 | Cell 4 |
`;
`.replace(/\n/g, EOL);
const { lastFrame } = render(
<SettingsContext.Provider value={mockSettings}>
<MarkdownDisplay {...baseProps} text={text} />
@@ -170,7 +174,7 @@ Test
Some text before.
| A | B |
|---|
| 1 | 2 |`;
| 1 | 2 |`.replace(/\n/g, EOL);
const { lastFrame } = render(
<SettingsContext.Provider value={mockSettings}>
<MarkdownDisplay {...baseProps} text={text} />
@@ -182,7 +186,7 @@ Some text before.
it('inserts a single space between paragraphs', () => {
const text = `Paragraph 1.
Paragraph 2.`;
Paragraph 2.`.replace(/\n/g, EOL);
const { lastFrame } = render(
<SettingsContext.Provider value={mockSettings}>
<MarkdownDisplay {...baseProps} text={text} />
@@ -205,7 +209,7 @@ some code
\`\`\`
Another paragraph.
`;
`.replace(/\n/g, EOL);
const { lastFrame } = render(
<SettingsContext.Provider value={mockSettings}>
<MarkdownDisplay {...baseProps} text={text} />
@@ -215,7 +219,7 @@ Another paragraph.
});
it('hides line numbers in code blocks when showLineNumbers is false', () => {
const text = '```javascript\nconst x = 1;\n```';
const text = '```javascript\nconst x = 1;\n```'.replace(/\n/g, EOL);
const settings = new LoadedSettings(
{ path: '', settings: {} },
{ path: '', settings: { showLineNumbers: false } },
@@ -234,7 +238,7 @@ Another paragraph.
});
it('shows line numbers in code blocks by default', () => {
const text = '```javascript\nconst x = 1;\n```';
const text = '```javascript\nconst x = 1;\n```'.replace(/\n/g, EOL);
const { lastFrame } = render(
<SettingsContext.Provider value={mockSettings}>
<MarkdownDisplay {...baseProps} text={text} />

View File

@@ -6,6 +6,7 @@
import React from 'react';
import { Text, Box } from 'ink';
import { EOL } from 'os';
import { Colors } from '../colors.js';
import { colorizeCode } from './CodeColorizer.js';
import { TableRenderer } from './TableRenderer.js';
@@ -34,7 +35,7 @@ const MarkdownDisplayInternal: React.FC<MarkdownDisplayProps> = ({
}) => {
if (!text) return <></>;
const lines = text.split('\n');
const lines = text.split(EOL);
const headerRegex = /^ *(#{1,4}) +(.*)/;
const codeFenceRegex = /^ *(`{3,}|~{3,}) *(\w*?) *$/;
const ulItemRegex = /^([ \t]*)([-*+]) +(.*)/;