From 31c87585190d181582e4ee29c65190be4f7a7670 Mon Sep 17 00:00:00 2001 From: Alem Tuzlak Date: Thu, 18 Sep 2025 13:23:56 +0200 Subject: [PATCH 01/10] feat: improve accuracy of console logs and go to source --- .changeset/major-lamps-float.md | 5 + .../devtools-vite/src/enhance-logs.test.ts | 157 ++++++++++++++++++ packages/devtools-vite/src/enhance-logs.ts | 64 +++++++ .../devtools-vite/src/inject-source.test.ts | 60 +++---- packages/devtools-vite/src/inject-source.ts | 3 +- packages/devtools-vite/src/plugin.ts | 38 +---- packages/devtools-vite/src/utils.ts | 3 +- 7 files changed, 264 insertions(+), 66 deletions(-) create mode 100644 .changeset/major-lamps-float.md create mode 100644 packages/devtools-vite/src/enhance-logs.test.ts create mode 100644 packages/devtools-vite/src/enhance-logs.ts diff --git a/.changeset/major-lamps-float.md b/.changeset/major-lamps-float.md new file mode 100644 index 00000000..44887da4 --- /dev/null +++ b/.changeset/major-lamps-float.md @@ -0,0 +1,5 @@ +--- +'@tanstack/devtools-vite': patch +--- + +improved accuracy of go to source to go to exact column and also improved accuracy of enhanced console logs to go to exact console log location diff --git a/packages/devtools-vite/src/enhance-logs.test.ts b/packages/devtools-vite/src/enhance-logs.test.ts new file mode 100644 index 00000000..005de4a8 --- /dev/null +++ b/packages/devtools-vite/src/enhance-logs.test.ts @@ -0,0 +1,157 @@ +import { describe, expect, test } from 'vitest' +import { enhanceConsoleLog } from './enhance-logs' + +const removeEmptySpace = (str: string) => { + return str.replace(/\s/g, '').trim() +} + +describe('remove-devtools', () => { + test('it adds enhanced console.logs to console.log()', () => { + const output = removeEmptySpace( + enhanceConsoleLog( + ` + console.log('This is a log') + `, + 'test.jsx', + 3000 + ).code, + ) + expect(output).toBe( + removeEmptySpace(` + console.log("LOG test.jsx:2:9 - http://localhost:3000/__tsd/open-source?source=test.jsx%3A2%3A9\\n\\u2192", 'This is a log'); + `), + ) + }) + + test('it does not add enhanced console.logs to console.log that is not called', () => { + const output = removeEmptySpace( + enhanceConsoleLog( + ` + console.log + `, + 'test.jsx', + 3000 + ).code, + ) + expect(output).toBe( + removeEmptySpace(` + console.log + `), + ) + }) + + test('it does not add enhanced console.logs to console.log that is inside a comment', () => { + const output = removeEmptySpace( + enhanceConsoleLog( + ` + // console.log('This is a log') + `, + 'test.jsx', + 3000 + ).code, + ) + expect(output).toBe( + removeEmptySpace(` + // console.log('This is a log') + `), + ) + }) + + test('it does not add enhanced console.logs to console.log that is inside a multiline comment', () => { + const output = removeEmptySpace( + enhanceConsoleLog( + ` + /* + console.log('This is a log') + */ + `, + 'test.jsx', + 3000 + ).code, + ) + expect(output).toBe( + removeEmptySpace(` + /* + console.log('This is a log') + */ + `), + ) + }) + + + + test('it does not add enhanced console.error to console.error that is inside a comment', () => { + const output = removeEmptySpace( + enhanceConsoleLog( + ` + // console.error('This is a log') + `, + 'test.jsx', + 3000 + ).code, + ) + expect(output).toBe( + removeEmptySpace(` + // console.error('This is a log') + `), + ) + }) + + test('it does not add enhanced console.error to console.error that is inside a multiline comment', () => { + const output = removeEmptySpace( + enhanceConsoleLog( + ` + /* + console.error('This is a log') + */ + `, + 'test.jsx', + 3000 + ).code, + ) + expect(output).toBe( + removeEmptySpace(` + /* + console.error('This is a log') + */ + `), + ) + }) + + + test('it adds enhanced console.error to console.error()', () => { + const output = removeEmptySpace( + enhanceConsoleLog( + ` + console.error('This is a log') + `, + 'test.jsx', + 3000 + ).code, + ) + expect(output).toBe( + removeEmptySpace(` + console.error("LOG test.jsx:2:9 - http://localhost:3000/__tsd/open-source?source=test.jsx%3A2%3A9\\n\\u2192", 'This is a log'); + `), + ) + }) + + test('it does not add enhanced console.error to console.error that is not called', () => { + const output = removeEmptySpace( + enhanceConsoleLog( + ` + console.log + `, + 'test.jsx', + 3000 + ).code, + ) + expect(output).toBe( + removeEmptySpace(` + console.log + `), + ) + }) + + +}) diff --git a/packages/devtools-vite/src/enhance-logs.ts b/packages/devtools-vite/src/enhance-logs.ts new file mode 100644 index 00000000..b1fecfef --- /dev/null +++ b/packages/devtools-vite/src/enhance-logs.ts @@ -0,0 +1,64 @@ +import chalk from 'chalk' +import { normalizePath } from 'vite' +import { gen, parse, t, trav } from './babel' +import type { types as Babel } from '@babel/core' +import type { ParseResult } from '@babel/parser' + + +const transform = (ast: ParseResult, filePath: string, port: number) => { + let didTransform = false + + trav(ast, { + CallExpression(path) { + const callee = path.node.callee + // Match console.log(...) or console.error(...) + if ( + callee.type === 'MemberExpression' && + callee.object.type === 'Identifier' && + callee.object.name === 'console' && + callee.property.type === 'Identifier' && + (callee.property.name === 'log' || callee.property.name === "error") + ) { + const location = path.node.loc + if (!location) { + return + } + const [lineNumber, column] = [location.start.line, location.start.column] + // Insert a first argument if not already present + // (You can customize this string as needed) + const finalPath = `${filePath}:${lineNumber}:${column + 1}` + path.node.arguments.unshift( + t.stringLiteral(`${chalk.magenta('LOG')} ${chalk.blueBright(`${finalPath} - http://localhost:${port}/__tsd/open-source?source=${encodeURIComponent(finalPath)}`)}\n → `) + ) + didTransform = true + } + } + }) + + return didTransform +} + +export function enhanceConsoleLog(code: string, id: string, port: number) { + const [filePath] = id.split('?') + // eslint-disable-next-line @typescript-eslint/no-non-null-asserted-optional-chain + const location = filePath?.replace(normalizePath(process.cwd()), '')! + + try { + const ast = parse(code, { + sourceType: 'module', + plugins: ['jsx', 'typescript'], + }) + const didTransform = transform(ast, location, port) + if (!didTransform) { + return { code } + } + return gen(ast, { + sourceMaps: true, + retainLines: true, + filename: id, + sourceFileName: filePath, + }) + } catch (e) { + return { code } + } +} diff --git a/packages/devtools-vite/src/inject-source.test.ts b/packages/devtools-vite/src/inject-source.test.ts index b145f799..fd6134f8 100644 --- a/packages/devtools-vite/src/inject-source.test.ts +++ b/packages/devtools-vite/src/inject-source.test.ts @@ -79,7 +79,7 @@ describe('inject source', () => { expect(output).toBe( removeEmptySpace(` export const Route = createFileRoute("/test")({ - component: function() { return
Hello World
; } + component: function() { return
Hello World
; } }); `), ) @@ -159,7 +159,7 @@ describe('inject source', () => { expect(output).toBe( removeEmptySpace(` export const Route = createFileRoute("/test")({ - component: function({...rest}) { return
Hello World
; } + component: function({...rest}) { return
Hello World
; } }); `), ) @@ -181,7 +181,7 @@ describe('inject source', () => { expect(output).toBe( removeEmptySpace(` export const Route = createFileRoute("/test")({ - component: () =>
Hello World
+ component: () =>
Hello World
}); `), ) @@ -261,7 +261,7 @@ describe('inject source', () => { expect(output).toBe( removeEmptySpace(` export const Route = createFileRoute("/test")({ - component: ({...rest}) =>
Hello World
+ component: ({...rest}) =>
Hello World
}); `), ) @@ -286,7 +286,7 @@ describe('inject source', () => { removeEmptySpace(` function Parent({ ...props }) { function Child({ ...props }) { - return
; + return
; } return ; } @@ -312,7 +312,7 @@ function test({...props }) { import Custom from "external"; function test({...props }) { - return ; + return ; }`), ) }) @@ -330,7 +330,7 @@ function test({...props }) { expect(output).toBe( removeEmptySpace(` function test(props) { - return
; } @@ -396,7 +396,7 @@ function test(props) { expect(output).toBe( removeEmptySpace(` function test({...props}) { - return
+ return
; } @@ -420,7 +420,7 @@ function test({...props}) { expect(output).toBe( removeEmptySpace(` function test({...rest}) { - return
+ return
; } @@ -462,7 +462,7 @@ function test({ children, ...rest }) { expect(output).toBe( removeEmptySpace(` function test({ ...props }) { - return
; }; @@ -629,7 +629,7 @@ function test({ children, ...rest }) { expect(output).toBe( removeEmptySpace(` const ButtonWithProps = function test({...props}) { - return
+ return
; }; @@ -653,7 +653,7 @@ function test({ children, ...rest }) { expect(output).toBe( removeEmptySpace(` const ButtonWithProps = function test({...rest}) { - return
+ return
; }; @@ -695,7 +695,7 @@ function test({ children, ...rest }) { expect(output).toBe( removeEmptySpace(` const ButtonWithProps = function test({ ...props }) { - return
; }; @@ -862,7 +862,7 @@ function test({ children, ...rest }) { expect(output).toBe( removeEmptySpace(` const ButtonWithProps = ({...props}) => { - return
+ return
; }; @@ -886,7 +886,7 @@ function test({ children, ...rest }) { expect(output).toBe( removeEmptySpace(` const ButtonWithProps = ({...rest}) => { - return
+ return
; }; @@ -908,7 +908,7 @@ function test({ children, ...rest }) { expect(output).toBe( removeEmptySpace(` const ButtonWithProps = ({ children, ...rest }) => { - return