Skip to content

Commit 6cd2ae8

Browse files
committed
fix(core): getCallerInfo() for nodejs v20/23
1 parent e9aa720 commit 6cd2ae8

File tree

5 files changed

+128
-40
lines changed

5 files changed

+128
-40
lines changed

packages/core/src/lib/callstack/util.ts

+73-24
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,8 @@ const initInfo: CallerInfo = {
3434

3535
const nodeVersion = semver.coerce(process.version)
3636
const isNodeGteV20 = nodeVersion ? semver.gte(nodeVersion, '20.0.0') : false
37+
const isNodeGteV22 = nodeVersion ? semver.gte(nodeVersion, '22.9.0') : false
38+
const isNodeGteV23 = nodeVersion ? semver.gte(nodeVersion, '23.3.0') : false
3739

3840
/**
3941
* Nodejs execution options
@@ -123,41 +125,31 @@ interface CallerInfoOrigin {
123125
*/
124126
export function getCallerInfo(callerDistance = 0): CallerInfo {
125127
const depth = callerDistance + 1
126-
let ret: CallerInfo = {
128+
let info22: CallerInfo = {
127129
...initInfo,
128130
}
129-
130131
// @link https://github.com/nodejs/node/releases/tag/v22.9.0
131132
// @ts-ignore since node v22.9
132133
if (typeof util.getCallSite === 'function') {
133-
// @ts-ignore
134-
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment, @typescript-eslint/no-unsafe-call
135-
const callSites: CallerInfoOrigin[] = util.getCallSite(depth + 1)
136-
const site = callSites[depth]
137-
assert(site, 'stack empty')
138-
ret = {
139-
...ret,
140-
path: site.scriptName,
141-
fileName: site.scriptName,
142-
className: '',
143-
funcName: site.functionName,
144-
methodName: '',
145-
lineNumber: site.lineNumber,
146-
columnNumber: site.column,
147-
enclosingLineNumber: -1,
148-
enclosingColNumber: -1,
149-
}
134+
info22 = getCallSiteNative(depth, true)
150135
}
151136

152137
const stacks = getStackCallerSites(depth + 5)
153-
const site = pickSite(stacks.slice(depth - 1), ret)
138+
let site = stacks[depth]
154139
assert(site, 'stack empty')
140+
const fileName = site.getFileName() ?? ''
141+
let funcName = site.getFunctionName() ?? ''
155142

156-
const funcName = site.getFunctionName() ?? ''
143+
if (isNodeGteV22 && (fileName !== info22.path || funcName !== info22.funcName)) {
144+
const infoWoSourceMap = isNodeGteV23 ? getCallSiteNative(depth, false) : info22
145+
site = pickSite(stacks.slice(depth - 1), infoWoSourceMap)
146+
assert(site, 'stack empty after pickSite')
147+
}
148+
149+
funcName = site.getFunctionName() ?? ''
157150
const methodName = site.getMethodName() ?? ''
158151
const typeName = site.getTypeName() ?? ''
159152
const line = site.toString()
160-
161153
let className = typeName
162154
if (! className) {
163155
className = methodName
@@ -169,11 +161,68 @@ export function getCallerInfo(callerDistance = 0): CallerInfo {
169161
: ''
170162
}
171163
}
172-
ret.className = className
173-
ret.methodName = methodName
164+
if (info22.path) {
165+
info22.className = className
166+
info22.methodName = methodName
167+
if (! info22.methodName) {
168+
info22.methodName = info22.funcName
169+
}
170+
return info22
171+
}
172+
173+
const fileLine = site.getFileName()
174+
const info: CallerInfo = {
175+
...initInfo,
176+
path: fileLine ?? '',
177+
fileName: site.getFileName() ?? '',
178+
className,
179+
funcName,
180+
methodName,
181+
lineNumber: site.getLineNumber() ?? 0,
182+
columnNumber: site.getColumnNumber() ?? 0,
183+
}
184+
const ret = {
185+
...info22,
186+
...info,
187+
}
174188
if (! ret.methodName) {
175189
ret.methodName = ret.funcName
176190
}
191+
return ret
192+
}
193+
194+
/**
195+
*
196+
* @param sourceMap since node v23.3
197+
* @returns
198+
*/
199+
function getCallSiteNative(distance: number, sourceMap: boolean): CallerInfo {
200+
const depth = distance + 1
201+
const ret: CallerInfo = {
202+
...initInfo,
203+
}
204+
const depth2 = isNodeGteV23 ? depth + 2 : depth + 1
205+
const depth3 = isNodeGteV23 ? depth + 1 : depth
206+
207+
// @ts-ignore
208+
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment, @typescript-eslint/no-unsafe-call
209+
const callSites: CallerInfoOrigin[] = util.getCallSite(depth2, { sourceMap })
210+
const site = callSites[depth3]
211+
assert(site, 'stack empty')
212+
ret.path = site.scriptName
213+
ret.fileName = site.scriptName
214+
ret.funcName = site.functionName
215+
ret.lineNumber = site.lineNumber
216+
ret.columnNumber = site.column
217+
218+
if (sourceMap && ! ret.funcName) {
219+
// @ts-ignore
220+
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment, @typescript-eslint/no-unsafe-call
221+
const callSites2: CallerInfoOrigin[] = util.getCallSite(depth2, { sourceMap: false })
222+
const site2 = callSites2[depth3]
223+
assert(site2, 'stack2 empty')
224+
ret.funcName = site2.functionName
225+
}
177226

178227
return ret
179228
}

packages/core/test/callstack/33.caller-stack.test.ts

+1-3
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import { pathToFileURL } from 'node:url'
44

55
import { fileShortPath, genCurrentDirname, genCurrentFilename, getCallerStack } from '@waiting/shared-core'
66

7+
import { isNodeGteV23 } from '#@/root.config.js'
78

89
import { fake1, fake2, test1, test2, test3, test4, test5 } from './call-config.js'
910

@@ -17,16 +18,13 @@ const pathUrl = pathToFileURL(tmpFile)
1718
const path1 = pathUrl.href
1819

1920
describe(fileShortPath(import.meta.url), () => {
20-
2121
describe('Should demo1 work', () => {
2222
it('test1()', () => {
2323
const info = test1()
2424
console.log({ info })
2525
assert(info.path === path1, `expect path: "${path1}", but got: "${info.path}"`)
2626
assert(info.line === 6, `expect line: "6", but got: "${info.line}"`)
2727
assert(info.column === 22, `expect column: "22", but got: "${info.column}"`)
28-
assert(info.columnNumber === 24, `expect columnNumber: "24", but got: "${info.columnNumber}"`)
29-
// assert(info.enclosingColNumber === 8, `expect enclosingColNumber: "8", but got: "${info.enclosingColNumber}"`)
3028
})
3129
it('test2()', () => {
3230
const info = test2()

packages/core/test/callstack/34.util.getCallerInfo.test.ts

+30-9
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import assert from 'node:assert'
22

33
import { getCallerInfo } from '##/index.js'
44
import { fileShortPath } from '##/lib/helper.js'
5+
import { isNodeGteV23 } from '#@/root.config.js'
56

67
import { demo } from './34.demo1.js'
78
import { demo2 } from './34.demo2.js'
@@ -10,6 +11,7 @@ import { _demo, validateInfo } from './34a.config.js'
1011

1112
describe(fileShortPath(import.meta.url), () => {
1213

14+
1315
describe('getCallerInfo() ', () => {
1416
it('distance: 0', () => {
1517
const info = getCallerInfo(0)
@@ -18,15 +20,21 @@ describe(fileShortPath(import.meta.url), () => {
1820
})
1921

2022
it('distance: 1', () => {
21-
const info = _demo()
23+
const info = _demo() // line23
2224
validateInfo(info, import.meta.url)
2325
assert(info.line === -1, JSON.stringify(info, null, 2))
2426
assert(info.column === -1, JSON.stringify(info, null, 2))
2527
assert(info.funcName === '', JSON.stringify(info, null, 2))
2628
assert(info.methodName === '', JSON.stringify(info, null, 2))
2729
assert(info.className === 'Context', JSON.stringify(info, null, 2))
28-
assert(info.lineNumber === 15, JSON.stringify(info, null, 2))
29-
assert(info.columnNumber === 26, JSON.stringify(info, null, 2))
30+
if (isNodeGteV23) {
31+
assert(info.lineNumber === 23, JSON.stringify(info, null, 2))
32+
assert(info.columnNumber === 20, JSON.stringify(info, null, 2))
33+
}
34+
else {
35+
assert(info.lineNumber === 16, JSON.stringify(info, null, 2))
36+
assert(info.columnNumber === 26, JSON.stringify(info, null, 2))
37+
}
3038
})
3139

3240
it('demo', () => {
@@ -37,24 +45,37 @@ describe(fileShortPath(import.meta.url), () => {
3745
assert(info.funcName === '', JSON.stringify(info, null, 2))
3846
assert(info.methodName === '', JSON.stringify(info, null, 2))
3947
assert(info.className === 'Context', JSON.stringify(info, null, 2))
40-
assert(info.lineNumber === 26, JSON.stringify(info, null, 2))
41-
assert(info.columnNumber === 26, JSON.stringify(info, null, 2))
48+
if (isNodeGteV23) {
49+
assert(info.lineNumber === 41, JSON.stringify(info, null, 2))
50+
assert(info.columnNumber === 20, JSON.stringify(info, null, 2))
51+
}
52+
else {
53+
assert(info.lineNumber === 33, JSON.stringify(info, null, 2))
54+
assert(info.columnNumber === 26, JSON.stringify(info, null, 2))
55+
56+
}
4257
})
4358

44-
it('demo2', case22) // line44
59+
it('demo2', case22)
4560

4661
})
4762
})
4863

4964
function case22(): void {
50-
const info = demo2()
65+
const info = demo2() // line65
5166
validateInfo(info, 'test/callstack/34.util.getCallerInfo.test.ts')
5267
assert(info.line === -1, JSON.stringify(info, null, 2))
5368
assert(info.column === -1, JSON.stringify(info, null, 2))
5469
assert(info.funcName === 'case22', JSON.stringify(info, null, 2))
5570
assert(info.methodName === 'case22', JSON.stringify(info, null, 2))
5671
assert(info.className === 'Context', JSON.stringify(info, null, 2))
57-
assert(info.lineNumber === 40, JSON.stringify(info, null, 2))
58-
assert(info.columnNumber === 18, JSON.stringify(info, null, 2))
72+
if (isNodeGteV23) {
73+
assert(info.lineNumber === 65, JSON.stringify(info, null, 2))
74+
assert(info.columnNumber === 16, JSON.stringify(info, null, 2))
75+
}
76+
else {
77+
assert(info.lineNumber === 53, JSON.stringify(info, null, 2))
78+
assert(info.columnNumber === 18, JSON.stringify(info, null, 2))
79+
}
5980
}
6081

packages/core/test/callstack/35.util.getCallerInfo.class.test.ts

+17-4
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import assert from 'node:assert'
22

33
import { getCallerInfo } from '##/index.js'
44
import { fileShortPath } from '##/lib/helper.js'
5+
import { isNodeGteV23 } from '#@/root.config.js'
56

67
import { Foo, _demo, validateInfo } from './34a.config.js'
78

@@ -16,8 +17,14 @@ describe(fileShortPath(import.meta.url), () => {
1617
assert(info.className === 'Foo', `expect Foo, but got ${info.className}`)
1718
assert(info.funcName === 'foo', `expect foo, but got ${info.funcName}`)
1819
assert(info.srcPath === '', `expect blank, but got ${info.srcPath}`)
19-
assert(info.columnNumber === 16, `expect 16, but got ${info.columnNumber}`)
20-
assert(info.lineNumber === 10, `expect 10, but got ${info.lineNumber}`)
20+
if (isNodeGteV23) {
21+
assert(info.columnNumber === 12, `expect 12, but got ${info.columnNumber}`)
22+
assert(info.lineNumber === 16, `expect 16, but got ${info.lineNumber}`)
23+
}
24+
else {
25+
assert(info.columnNumber === 16, `expect 16, but got ${info.columnNumber}`)
26+
assert(info.lineNumber === 10, `expect 10, but got ${info.lineNumber}`)
27+
}
2128
})
2229

2330
it('distance: 1', () => {
@@ -27,8 +34,14 @@ describe(fileShortPath(import.meta.url), () => {
2734
assert(info.className === 'Foo', `expect Foo, but got ${info.className}`)
2835
assert(info.funcName === 'barz', `expect foo, but got ${info.funcName}`)
2936
assert(info.srcPath === '', `expect blank, but got ${info.srcPath}`)
30-
assert(info.columnNumber === 21, `expect 21, but got ${info.columnNumber}`)
31-
assert(info.lineNumber === 16, `expect 16, but got ${info.lineNumber}`)
37+
if (isNodeGteV23) {
38+
assert(info.columnNumber === 17, `expect 17, but got ${info.columnNumber}`)
39+
assert(info.lineNumber === 24, `expect 24, but got ${info.lineNumber}`)
40+
}
41+
else {
42+
assert(info.columnNumber === 21, `expect 21, but got ${info.columnNumber}`)
43+
assert(info.lineNumber === 16, `expect 16, but got ${info.lineNumber}`)
44+
}
3245
})
3346
})
3447
})

packages/core/test/root.config.ts

+7
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import { join } from 'node:path'
22

33
import { genCurrentDirname } from '@waiting/shared-core'
4+
import semver from 'semver'
45

56

67
export const testDir = genCurrentDirname(import.meta.url)
@@ -31,3 +32,9 @@ export const testConfig = {
3132
TEST,
3233
} as TestConfig
3334

35+
36+
const nodeVersion = semver.coerce(process.version)
37+
export const isNodeGteV20 = nodeVersion ? semver.gte(nodeVersion, '20.0.0') : false
38+
export const isNodeGteV22 = nodeVersion ? semver.gte(nodeVersion, '22.9.0') : false
39+
export const isNodeGteV23 = nodeVersion ? semver.gte(nodeVersion, '23.3.0') : false
40+

0 commit comments

Comments
 (0)