From ef96529c7ac0ac6e58c332ed28d2c92ff0bfbf5c Mon Sep 17 00:00:00 2001 From: Ajay U Date: Tue, 15 Sep 2026 12:15:02 -0400 Subject: [PATCH] chore: remove two dead TS helpers from utils.ts removeAnsiChars duplicated the strip-ansi dep already used elsewhere; switch its one call site to that import instead. Add a jest mock for strip-ansi since the real package is ESM-only and breaks the CJS test runner. moveCodeSectionBoundaryMarkersToNewLine had no production callers, only its own test. Co-Authored-By: Claude Sonnet 5 --- jest.config.js | 1 + src/utils.ts | 42 ++---------------------- tests/ts/__mocks__/strip-ansi.ts | 13 ++++++++ tests/ts/utils.test.ts | 55 -------------------------------- 4 files changed, 16 insertions(+), 95 deletions(-) create mode 100644 tests/ts/__mocks__/strip-ansi.ts diff --git a/jest.config.js b/jest.config.js index c4baeac9..7861b7d0 100644 --- a/jest.config.js +++ b/jest.config.js @@ -21,6 +21,7 @@ module.exports = { // The tests don't depend on real tokenization, so stub it out. moduleNameMapper: { '^tiktoken$': '/tests/ts/__mocks__/tiktoken.ts', + '^strip-ansi$': '/tests/ts/__mocks__/strip-ansi.ts', '\\.svg$': '/tests/ts/__mocks__/svg.ts', '^@jupyterlab/apputils$': '/tests/ts/__mocks__/jupyterlab-apputils.ts', diff --git a/src/utils.ts b/src/utils.ts index 9cc23fa3..174191bb 100644 --- a/src/utils.ts +++ b/src/utils.ts @@ -7,19 +7,12 @@ import { IDocumentManager } from '@jupyterlab/docmanager'; import { FileDialog } from '@jupyterlab/filebrowser'; import { encoding_for_model } from 'tiktoken'; import { NotebookPanel } from '@jupyterlab/notebook'; +import stripAnsi from 'strip-ansi'; import { shellSingleQuote } from './shell-utils'; const tiktoken_encoding = encoding_for_model('gpt-4o'); -export function removeAnsiChars(str: string): string { - return str.replace( - // eslint-disable-next-line no-control-regex - /[\u001b\u009b][[()#;?]*(?:[0-9]{1,4}(?:;[0-9]{0,4})*)?[0-9A-ORZcf-nqry=><]/g, - '' - ); -} - export async function waitForDuration(duration: number): Promise { return new Promise(resolve => { setTimeout(() => { @@ -28,37 +21,6 @@ export async function waitForDuration(duration: number): Promise { }); } -export function moveCodeSectionBoundaryMarkersToNewLine( - source: string -): string { - const existingLines = source.split('\n'); - const newLines = []; - for (const line of existingLines) { - if (line.length > 3 && line.startsWith('```')) { - newLines.push('```'); - let remaining = line.substring(3); - if (remaining.startsWith('python')) { - if (remaining.length === 6) { - continue; - } - remaining = remaining.substring(6); - } - if (remaining.endsWith('```')) { - newLines.push(remaining.substring(0, remaining.length - 3)); - newLines.push('```'); - } else { - newLines.push(remaining); - } - } else if (line.length > 3 && line.endsWith('```')) { - newLines.push(line.substring(0, line.length - 3)); - newLines.push('```'); - } else { - newLines.push(line); - } - } - return newLines.join('\n'); -} - export function extractLLMGeneratedCode(code: string): string { // Strip our backend-emitted stream-interruption marker. The Claude inline // handler pushes it into the same text channel so the diff pane shows @@ -122,7 +84,7 @@ export function markdownToComment(source: string): string { export function formatJupyterError(output: any): string { const head = `${output.ename ?? 'Error'}: ${output.evalue ?? ''}`.trim(); const tb = Array.isArray(output.traceback) - ? output.traceback.map((line: string) => removeAnsiChars(line)).join('\n') + ? output.traceback.map((line: string) => stripAnsi(line)).join('\n') : ''; return tb ? `${head}\n${tb}` : head; } diff --git a/tests/ts/__mocks__/strip-ansi.ts b/tests/ts/__mocks__/strip-ansi.ts new file mode 100644 index 00000000..b5e3e763 --- /dev/null +++ b/tests/ts/__mocks__/strip-ansi.ts @@ -0,0 +1,13 @@ +// Copyright (c) Mehmet Bektas + +// strip-ansi ships ESM-only, which jest's CJS test runner can't load +// directly. The real package is a single regex-based string replace, so +// this mock reimplements that regex rather than reworking the jest +// transform pipeline for one dependency. +export default function stripAnsi(str: string): string { + return str.replace( + // eslint-disable-next-line no-control-regex + /[›][[()#;?]*(?:[0-9]{1,4}(?:;[0-9]{0,4})*)?[0-9A-ORZcf-nqry=><]/g, + '' + ); +} diff --git a/tests/ts/utils.test.ts b/tests/ts/utils.test.ts index 1645aa0f..65c5f699 100644 --- a/tests/ts/utils.test.ts +++ b/tests/ts/utils.test.ts @@ -1,8 +1,6 @@ // Copyright (c) Mehmet Bektas import { - removeAnsiChars, - moveCodeSectionBoundaryMarkersToNewLine, extractLLMGeneratedCode, markdownToComment, compareSelectionPoints, @@ -19,59 +17,6 @@ import { writeTextToClipboard } from '../../src/utils'; -describe('removeAnsiChars', () => { - it('strips colour escape sequences', () => { - const colored = '\u001b[31merror\u001b[0m: oops'; - expect(removeAnsiChars(colored)).toBe('error: oops'); - }); - - it('strips cursor-control escape sequences', () => { - expect(removeAnsiChars('hi\u001b[2Athere')).toBe('hithere'); - }); - - it('returns plain strings unchanged', () => { - expect(removeAnsiChars('plain text')).toBe('plain text'); - }); - - it('handles empty input', () => { - expect(removeAnsiChars('')).toBe(''); - }); -}); - -describe('moveCodeSectionBoundaryMarkersToNewLine', () => { - it('splits an opening fence that has trailing content', () => { - const input = '```pythonprint("hi")'; - expect(moveCodeSectionBoundaryMarkersToNewLine(input)).toBe( - '```\nprint("hi")' - ); - }); - - it('splits a fence that opens and closes on a single line', () => { - const input = '```pythonprint("hi")```'; - expect(moveCodeSectionBoundaryMarkersToNewLine(input)).toBe( - '```\nprint("hi")\n```' - ); - }); - - it('drops a redundant language tag when nothing follows it', () => { - expect(moveCodeSectionBoundaryMarkersToNewLine('```python')).toBe('```'); - }); - - it('moves a trailing fence onto its own line', () => { - const input = 'print("hi")```'; - expect(moveCodeSectionBoundaryMarkersToNewLine(input)).toBe( - 'print("hi")\n```' - ); - }); - - it('strips a redundant python language tag from a well-formed fence', () => { - const input = '```python\nprint("hi")\n```'; - expect(moveCodeSectionBoundaryMarkersToNewLine(input)).toBe( - '```\nprint("hi")\n```' - ); - }); -}); - describe('extractLLMGeneratedCode', () => { it('extracts the body between matched fences', () => { const wrapped = '```python\nprint("hi")\n```';