diff --git a/integration-tests/cypress/cypress-atr.spec.js b/integration-tests/cypress/cypress-atr.spec.js index 96f95fd2fb0..a24057ba035 100644 --- a/integration-tests/cypress/cypress-atr.spec.js +++ b/integration-tests/cypress/cypress-atr.spec.js @@ -94,10 +94,7 @@ moduleTypes.forEach(({ // cypress-fail-fast is required as an incompatible plugin. // typescript is required to compile .cy.ts spec files in the pre-compiled JS tests. - // typescript@5 is pinned because typescript@6 emits "use strict" on line 1 for - // non-module files, shifting compiled line numbers and breaking source map resolution. - // TODO: Update tests files accordingly and test with different TS versions - useSandbox([`cypress@${version}`, 'cypress-fail-fast@7.1.0', 'typescript@5'], true) + useSandbox([`cypress@${version}`, 'cypress-fail-fast@7.1.0', 'typescript'], true) before(async function () { // Note: Cypress binary is already installed during useSandbox() via the postinstall script diff --git a/integration-tests/cypress/cypress-efd.spec.js b/integration-tests/cypress/cypress-efd.spec.js index c730b34d611..6fabdc9d9b1 100644 --- a/integration-tests/cypress/cypress-efd.spec.js +++ b/integration-tests/cypress/cypress-efd.spec.js @@ -91,10 +91,7 @@ moduleTypes.forEach(({ // cypress-fail-fast is required as an incompatible plugin. // typescript is required to compile .cy.ts spec files in the pre-compiled JS tests. - // typescript@5 is pinned because typescript@6 emits "use strict" on line 1 for - // non-module files, shifting compiled line numbers and breaking source map resolution. - // TODO: Update tests files accordingly and test with different TS versions - useSandbox([`cypress@${version}`, 'cypress-fail-fast@7.1.0', 'typescript@5'], true) + useSandbox([`cypress@${version}`, 'cypress-fail-fast@7.1.0', 'typescript'], true) before(async function () { // Note: Cypress binary is already installed during useSandbox() via the postinstall script diff --git a/integration-tests/cypress/cypress-final-status.spec.js b/integration-tests/cypress/cypress-final-status.spec.js index d540799c0b4..2ef97ccc585 100644 --- a/integration-tests/cypress/cypress-final-status.spec.js +++ b/integration-tests/cypress/cypress-final-status.spec.js @@ -88,10 +88,7 @@ moduleTypes.forEach(({ // cypress-fail-fast is required as an incompatible plugin. // typescript is required to compile .cy.ts spec files in the pre-compiled JS tests. - // typescript@5 is pinned because typescript@6 emits "use strict" on line 1 for - // non-module files, shifting compiled line numbers and breaking source map resolution. - // TODO: Update tests files accordingly and test with different TS versions - useSandbox([`cypress@${version}`, 'cypress-fail-fast@7.1.0', 'typescript@5'], true) + useSandbox([`cypress@${version}`, 'cypress-fail-fast@7.1.0', 'typescript'], true) before(async function () { // Note: Cypress binary is already installed during useSandbox() via the postinstall script diff --git a/integration-tests/cypress/cypress-impacted-tests.spec.js b/integration-tests/cypress/cypress-impacted-tests.spec.js index 557ac2ddbab..4a1bf9a5b97 100644 --- a/integration-tests/cypress/cypress-impacted-tests.spec.js +++ b/integration-tests/cypress/cypress-impacted-tests.spec.js @@ -102,10 +102,7 @@ moduleTypes.forEach(({ // cypress-fail-fast is required as an incompatible plugin. // typescript is required to compile .cy.ts spec files in the pre-compiled JS tests. - // typescript@5 is pinned because typescript@6 emits "use strict" on line 1 for - // non-module files, shifting compiled line numbers and breaking source map resolution. - // TODO: Update tests files accordingly and test with different TS versions - useSandbox([`cypress@${version}`, 'cypress-fail-fast@7.1.0', 'typescript@5'], true) + useSandbox([`cypress@${version}`, 'cypress-fail-fast@7.1.0', 'typescript'], true) before(async function () { // Note: Cypress binary is already installed during useSandbox() via the postinstall script diff --git a/integration-tests/cypress/cypress-itr.spec.js b/integration-tests/cypress/cypress-itr.spec.js index 234684d68e2..5c24713caf6 100644 --- a/integration-tests/cypress/cypress-itr.spec.js +++ b/integration-tests/cypress/cypress-itr.spec.js @@ -90,10 +90,7 @@ moduleTypes.forEach(({ // cypress-fail-fast is required as an incompatible plugin. // typescript is required to compile .cy.ts spec files in the pre-compiled JS tests. - // typescript@5 is pinned because typescript@6 emits "use strict" on line 1 for - // non-module files, shifting compiled line numbers and breaking source map resolution. - // TODO: Update tests files accordingly and test with different TS versions - useSandbox([`cypress@${version}`, 'cypress-fail-fast@7.1.0', 'typescript@5'], true) + useSandbox([`cypress@${version}`, 'cypress-fail-fast@7.1.0', 'typescript'], true) before(async function () { // Note: Cypress binary is already installed during useSandbox() via the postinstall script diff --git a/integration-tests/cypress/cypress-reporting.spec.js b/integration-tests/cypress/cypress-reporting.spec.js index df45d0277eb..8d9c68881b9 100644 --- a/integration-tests/cypress/cypress-reporting.spec.js +++ b/integration-tests/cypress/cypress-reporting.spec.js @@ -39,7 +39,10 @@ const { const { DD_HOST_CPU_COUNT } = require('../../packages/dd-trace/src/plugins/util/env') const { ERROR_MESSAGE, ERROR_TYPE, COMPONENT } = require('../../packages/dd-trace/src/constants') const { DD_MAJOR, NODE_MAJOR } = require('../../version') -const { resolveSourceLineForTest } = require('../../packages/datadog-plugin-cypress/src/source-map-utils') +const { + resolveOriginalSourceFile, + resolveSourceLineForTest, +} = require('../../packages/datadog-plugin-cypress/src/source-map-utils') const RECEIVER_STOP_TIMEOUT = 20000 const version = process.env.CYPRESS_VERSION @@ -59,6 +62,30 @@ function compilePrecompiledTypeScriptSpecs (cwd, env) { } } +/** + * @param {string} cwd + * @returns {void} + */ +function configureCypressTypeScriptCompilation (cwd) { + // Cypress's webpack preprocessor resolves TypeScript config from the spec directory. + const tsconfig = { + compilerOptions: { + rootDir: '.', + target: 'ES2020', + module: 'commonjs', + sourceMap: true, + skipLibCheck: true, + }, + } + + const typescriptVersion = require(path.join(cwd, 'node_modules/typescript/package.json')).version + if (semver.gte(typescriptVersion, '6.0.0')) { + tsconfig.compilerOptions.ignoreDeprecations = '6.0' + } + + fs.writeFileSync(path.join(cwd, 'cypress/e2e/tsconfig.json'), JSON.stringify(tsconfig, null, 2)) +} + function shouldTestsRun (type) { if (DD_MAJOR === 5) { if (NODE_MAJOR <= 16) { @@ -119,10 +146,7 @@ moduleTypes.forEach(({ // cypress-fail-fast is required as an incompatible plugin. // typescript is required to compile .cy.ts spec files in the pre-compiled JS tests. - // typescript@5 is pinned because typescript@6 emits "use strict" on line 1 for - // non-module files, shifting compiled line numbers and breaking source map resolution. - // TODO: Update tests files accordingly and test with different TS versions - useSandbox([`cypress@${version}`, 'cypress-fail-fast@7.1.0', 'typescript@5'], true) + useSandbox([`cypress@${version}`, 'cypress-fail-fast@7.1.0', 'typescript'], true) before(async function () { // Note: Cypress binary is already installed during useSandbox() via the postinstall script @@ -676,6 +700,7 @@ moduleTypes.forEach(({ ) over10It('reports tests with a TypeScript config file', async () => { + let testOutput = '' const receiverPromise = receiver .gatherPayloadsMaxTimeout(({ url }) => url.endsWith('/api/v2/citestcycle'), (payloads) => { const events = payloads @@ -690,7 +715,13 @@ moduleTypes.forEach(({ [TEST_STATUS]: 'pass', [TEST_FRAMEWORK]: 'cypress', }, - }) + }, `got events: ${JSON.stringify(events.map(event => ({ + resource: event.content.resource, + sourceFile: event.content.meta?.[TEST_SOURCE_FILE], + status: event.content.meta?.[TEST_STATUS], + framework: event.content.meta?.[TEST_FRAMEWORK], + error: event.content.meta?.[ERROR_MESSAGE], + })), null, 2)}\nCypress output:\n${testOutput}`) }, 20000) const envVars = getCiVisAgentlessConfig(receiver.port) @@ -706,6 +737,12 @@ moduleTypes.forEach(({ }, } ) + childProcess.stdout?.on('data', chunk => { + testOutput += chunk.toString() + }) + childProcess.stderr?.on('data', chunk => { + testOutput += chunk.toString() + }) const [[exitCode]] = await Promise.all([ once(childProcess, 'exit'), @@ -1294,6 +1331,33 @@ moduleTypes.forEach(({ } }) + over12It('resolves source file when generated first line is unmapped', () => { + const tempDir = fs.mkdtempSync(path.join(os.tmpdir(), 'dd-cypress-source-map-')) + const compiledFilePath = path.join(tempDir, 'spec-prologue.js') + const sourceMapPath = `${compiledFilePath}.map` + + try { + fs.writeFileSync(compiledFilePath, [ + '"use strict";', + 'it("source mapped title", () => {})', + '', + ].join('\n')) + + fs.writeFileSync(sourceMapPath, JSON.stringify({ + version: 3, + file: 'spec-prologue.js', + sourceRoot: '', + sources: ['spec-prologue.ts'], + names: [], + mappings: ';AAEA', + })) + + assert.strictEqual(resolveOriginalSourceFile(compiledFilePath), path.join(tempDir, 'spec-prologue.ts')) + } finally { + fs.rmSync(tempDir, { recursive: true, force: true }) + } + }) + over12It('uses declaration scanning fallback when invocationDetails line is invalid', async function () { const envVars = getCiVisAgentlessConfig(receiver.port) @@ -1429,6 +1493,8 @@ moduleTypes.forEach(({ over12It('reports correct source file and line for typescript test files compiled by cypress', async function () { // Remove any pre-compiled dist files to ensure Cypress compiles the .ts file itself cleanupPrecompiledSourceLineDist(cwd) + configureCypressTypeScriptCompilation(cwd) + let testOutput = '' const receiverPromise = receiver .gatherPayloadsMaxTimeout(({ url }) => url.endsWith('/api/v2/citestcycle'), (payloads) => { @@ -1438,7 +1504,18 @@ moduleTypes.forEach(({ event.content.resource.includes('spec source line') ) - assert.strictEqual(tsTestEvents.length, 2, 'should have two typescript test events') + assert.strictEqual( + tsTestEvents.length, + 2, + `should have two typescript test events, got events: ${JSON.stringify(events.map(event => ({ + type: event.type, + resource: event.content.resource, + sourceFile: event.content.meta?.[TEST_SOURCE_FILE], + sourceStart: event.content.metrics?.[TEST_SOURCE_START], + status: event.content.meta?.[TEST_STATUS], + error: event.content.meta?.[ERROR_MESSAGE], + })), null, 2)}\nCypress output:\n${testOutput}` + ) const itTestEvent = tsTestEvents.find(e => e.content.resource.includes('reports correct line number')) const testTestEvent = tsTestEvents.find( @@ -1482,6 +1559,12 @@ moduleTypes.forEach(({ SPEC_PATTERN: 'cypress/e2e/spec-source-line.cy.ts', }, }) + childProcess.stdout?.on('data', chunk => { + testOutput += chunk.toString() + }) + childProcess.stderr?.on('data', chunk => { + testOutput += chunk.toString() + }) const [[exitCode]] = await Promise.all([once(childProcess, 'exit'), receiverPromise]) assert.strictEqual(exitCode, 0, 'cypress process should exit successfully') diff --git a/integration-tests/cypress/cypress-test-management.spec.js b/integration-tests/cypress/cypress-test-management.spec.js index 96c768b4dee..adef82fae75 100644 --- a/integration-tests/cypress/cypress-test-management.spec.js +++ b/integration-tests/cypress/cypress-test-management.spec.js @@ -97,10 +97,7 @@ moduleTypes.forEach(({ // cypress-fail-fast is required as an incompatible plugin. // typescript is required to compile .cy.ts spec files in the pre-compiled JS tests. - // typescript@5 is pinned because typescript@6 emits "use strict" on line 1 for - // non-module files, shifting compiled line numbers and breaking source map resolution. - // TODO: Update tests files accordingly and test with different TS versions - useSandbox([`cypress@${version}`, 'cypress-fail-fast@7.1.0', 'typescript@5'], true) + useSandbox([`cypress@${version}`, 'cypress-fail-fast@7.1.0', 'typescript'], true) before(async function () { // Note: Cypress binary is already installed during useSandbox() via the postinstall script diff --git a/integration-tests/playwright/playwright-active-test-span.spec.js b/integration-tests/playwright/playwright-active-test-span.spec.js index 921930795a4..6647a394967 100644 --- a/integration-tests/playwright/playwright-active-test-span.spec.js +++ b/integration-tests/playwright/playwright-active-test-span.spec.js @@ -50,8 +50,7 @@ versions.forEach((version) => { this.retries(2) this.timeout(80000) - // TODO: Update tests files accordingly and test with different TS versions - useSandbox([`@playwright/test@${version}`, '@types/node', 'typescript@5'], true) + useSandbox([`@playwright/test@${version}`, '@types/node', 'typescript'], true) before(function (done) { // Increase timeout for this hook specifically to account for slow chromium installation in CI diff --git a/integration-tests/playwright/playwright-atr.spec.js b/integration-tests/playwright/playwright-atr.spec.js index a7f3feacb85..1c5fbdf098d 100644 --- a/integration-tests/playwright/playwright-atr.spec.js +++ b/integration-tests/playwright/playwright-atr.spec.js @@ -45,8 +45,7 @@ versions.forEach((version) => { this.retries(2) this.timeout(80000) - // TODO: Update tests files accordingly and test with different TS versions - useSandbox([`@playwright/test@${version}`, '@types/node', 'typescript@5'], true) + useSandbox([`@playwright/test@${version}`, '@types/node', 'typescript'], true) before(function (done) { // Increase timeout for this hook specifically to account for slow chromium installation in CI diff --git a/integration-tests/playwright/playwright-efd.spec.js b/integration-tests/playwright/playwright-efd.spec.js index 7657de5277b..320d74b5469 100644 --- a/integration-tests/playwright/playwright-efd.spec.js +++ b/integration-tests/playwright/playwright-efd.spec.js @@ -51,8 +51,7 @@ versions.forEach((version) => { this.retries(2) this.timeout(80000) - // TODO: Update tests files accordingly and test with different TS versions - useSandbox([`@playwright/test@${version}`, '@types/node', 'typescript@5'], true) + useSandbox([`@playwright/test@${version}`, '@types/node', 'typescript'], true) before(function (done) { // Increase timeout for this hook specifically to account for slow chromium installation in CI diff --git a/integration-tests/playwright/playwright-impacted-tests.spec.js b/integration-tests/playwright/playwright-impacted-tests.spec.js index abf31b4572c..041b176e957 100644 --- a/integration-tests/playwright/playwright-impacted-tests.spec.js +++ b/integration-tests/playwright/playwright-impacted-tests.spec.js @@ -51,8 +51,7 @@ versions.forEach((version) => { this.retries(2) this.timeout(80000) - // TODO: Update tests files accordingly and test with different TS versions - useSandbox([`@playwright/test@${version}`, '@types/node', 'typescript@5'], true) + useSandbox([`@playwright/test@${version}`, '@types/node', 'typescript'], true) before(function (done) { // Increase timeout for this hook specifically to account for slow chromium installation in CI diff --git a/integration-tests/playwright/playwright-reporting.spec.js b/integration-tests/playwright/playwright-reporting.spec.js index 460ba97bdd3..39606315840 100644 --- a/integration-tests/playwright/playwright-reporting.spec.js +++ b/integration-tests/playwright/playwright-reporting.spec.js @@ -57,8 +57,7 @@ versions.forEach((version) => { this.retries(2) this.timeout(80000) - // TODO: Update tests files accordingly and test with different TS versions - useSandbox([`@playwright/test@${version}`, '@types/node', 'typescript@5'], true) + useSandbox([`@playwright/test@${version}`, '@types/node', 'typescript'], true) before(function (done) { // Increase timeout for this hook specifically to account for slow chromium installation in CI @@ -279,10 +278,24 @@ versions.forEach((version) => { receiver.gatherPayloadsMaxTimeout(({ url }) => url === '/api/v2/citestcycle', payloads => { const events = payloads.flatMap(({ payload }) => payload.events) const testEvents = events.filter(event => event.type === 'test') - assertObjectContains(testEvents.map(test => test.content.resource).sort(), [ + const expectedResources = [ 'playwright-tests-ts/one-test.js.playwright should work with passing tests', 'playwright-tests-ts/one-test.js.playwright should work with skipped tests', - ]) + ] + const actualResources = testEvents.map(test => test.content.resource).sort() + for (const expectedResource of expectedResources) { + assert.ok( + actualResources.includes(expectedResource), + `expected ${expectedResource}, got events: ${JSON.stringify(events.map(event => ({ + type: event.type, + resource: event.content.resource, + sourceFile: event.content.meta?.[TEST_SOURCE_FILE], + sourceStart: event.content.metrics?.[TEST_SOURCE_START], + status: event.content.meta?.[TEST_STATUS], + error: event.content.meta?.[ERROR_MESSAGE], + })), null, 2)}\nPlaywright output:\n${testOutput}` + ) + } assert.deepStrictEqual( testEvents .map(test => ({ diff --git a/integration-tests/playwright/playwright-test-management.spec.js b/integration-tests/playwright/playwright-test-management.spec.js index 57c51646e13..1bf830cc849 100644 --- a/integration-tests/playwright/playwright-test-management.spec.js +++ b/integration-tests/playwright/playwright-test-management.spec.js @@ -53,8 +53,7 @@ versions.forEach((version) => { this.retries(2) this.timeout(80000) - // TODO: Update tests files accordingly and test with different TS versions - useSandbox([`@playwright/test@${version}`, '@types/node', 'typescript@5'], true) + useSandbox([`@playwright/test@${version}`, '@types/node', 'typescript'], true) before(function (done) { // Increase timeout for this hook specifically to account for slow chromium installation in CI diff --git a/integration-tests/tsconfig.json b/integration-tests/tsconfig.json index 9981bb6ffe2..5f6a1d9ce58 100644 --- a/integration-tests/tsconfig.json +++ b/integration-tests/tsconfig.json @@ -4,7 +4,8 @@ "module": "commonjs", "outDir": "ci-visibility/playwright-tests-ts-out", "sourceMap": true, - "skipLibCheck": true + "skipLibCheck": true, + "types": ["node"] }, "include": ["ci-visibility/playwright-tests-ts"], "files": ["playwright.config.ts"] diff --git a/packages/datadog-instrumentations/src/cypress-config.js b/packages/datadog-instrumentations/src/cypress-config.js index 13a573bc055..7e163dc578f 100644 --- a/packages/datadog-instrumentations/src/cypress-config.js +++ b/packages/datadog-instrumentations/src/cypress-config.js @@ -310,6 +310,58 @@ function createConfigWrapper (originalConfigFile) { return wrapperFile } +/** + * @param {string} projectRoot + * @returns {boolean} + */ +function isTypeScript6OrNewer (projectRoot) { + try { + // eslint-disable-next-line n/no-unpublished-require + const packageJsonPath = require.resolve('typescript/package.json', { paths: [projectRoot] }) + const { version } = JSON.parse(fs.readFileSync(packageJsonPath, 'utf8')) + const major = Number(String(version).split('.')[0]) + return major >= 6 + } catch { + return false + } +} + +/** + * @param {string} projectRoot + * @param {string} configFilePath + * @returns {() => void} + */ +function configureTsNodeForTypeScript6 (projectRoot, configFilePath) { + const configExt = path.extname(configFilePath) + if (configExt !== '.ts' && configExt !== '.cts' && configExt !== '.mts') return () => {} + if (!isTypeScript6OrNewer(projectRoot)) return () => {} + + /* eslint-disable eslint-rules/eslint-process-env */ + const previousCompilerOptions = process.env.TS_NODE_COMPILER_OPTIONS + let compilerOptions = {} + if (previousCompilerOptions) { + try { + compilerOptions = JSON.parse(previousCompilerOptions) + } catch { + compilerOptions = {} + } + } + + process.env.TS_NODE_COMPILER_OPTIONS = JSON.stringify({ + ...compilerOptions, + ignoreDeprecations: '6.0', + }) + + return () => { + if (previousCompilerOptions === undefined) { + delete process.env.TS_NODE_COMPILER_OPTIONS + } else { + process.env.TS_NODE_COMPILER_OPTIONS = previousCompilerOptions + } + } + /* eslint-enable eslint-rules/eslint-process-env */ +} + /** * Wraps the Cypress config file for a CLI start() call. When an explicit * configFile is provided, creates a temp wrapper that imports the original @@ -350,10 +402,12 @@ function wrapCliConfigFileOptions (options) { try { const wrapperFile = createConfigWrapper(configFilePath) + const restoreTsNodeCompilerOptions = configureTsNodeForTypeScript6(projectRoot, configFilePath) return { options: { ...options, configFile: wrapperFile }, cleanup: () => { + restoreTsNodeCompilerOptions() try { fs.unlinkSync(wrapperFile) } catch { /* best effort */ } }, } diff --git a/packages/datadog-plugin-cypress/src/cypress-plugin.js b/packages/datadog-plugin-cypress/src/cypress-plugin.js index 37dd285b61c..2658e3fb83d 100644 --- a/packages/datadog-plugin-cypress/src/cypress-plugin.js +++ b/packages/datadog-plugin-cypress/src/cypress-plugin.js @@ -100,7 +100,7 @@ const { } = require('../../dd-trace/src/plugins/util/env') const { DD_MAJOR } = require('../../../version') const { - resolveOriginalSourcePosition, + resolveOriginalSourceFile, resolveSourceLineForTest, shouldTrustInvocationDetailsLine, } = require('./source-map-utils') @@ -518,8 +518,7 @@ class CypressPlugin { this.ciVisEvent(TELEMETRY_EVENT_CREATED, 'suite') if (testSuiteAbsolutePath) { - const resolvedSuitePosition = resolveOriginalSourcePosition(testSuiteAbsolutePath, 1) - const resolvedSuiteAbsolutePath = resolvedSuitePosition ? resolvedSuitePosition.sourceFile : testSuiteAbsolutePath + const resolvedSuiteAbsolutePath = resolveOriginalSourceFile(testSuiteAbsolutePath) || testSuiteAbsolutePath const testSourceFile = getTestSuitePath(resolvedSuiteAbsolutePath, this.repositoryRoot) testSuiteSpanMetadata[TEST_SOURCE_FILE] = testSourceFile testSuiteSpanMetadata[TEST_SOURCE_START] = 1 @@ -950,8 +949,9 @@ class CypressPlugin { if (this.itrCorrelationId) { finishedTest.testSpan.setTag(ITR_CORRELATION_ID, this.itrCorrelationId) } - const resolvedSpecPosition = spec.absolute ? resolveOriginalSourcePosition(spec.absolute, 1) : null - const resolvedSpecAbsolutePath = resolvedSpecPosition ? resolvedSpecPosition.sourceFile : spec.absolute + const resolvedSpecAbsolutePath = spec.absolute + ? resolveOriginalSourceFile(spec.absolute) || spec.absolute + : spec.absolute const testSourceFile = resolvedSpecAbsolutePath && this.repositoryRoot ? getTestSuitePath(resolvedSpecAbsolutePath, this.repositoryRoot) : spec.relative diff --git a/packages/datadog-plugin-cypress/src/source-map-utils.js b/packages/datadog-plugin-cypress/src/source-map-utils.js index d9ece9ce8ad..233c1491663 100644 --- a/packages/datadog-plugin-cypress/src/source-map-utils.js +++ b/packages/datadog-plugin-cypress/src/source-map-utils.js @@ -165,6 +165,48 @@ function resolveOriginalSourcePosition (absoluteFilePath, generatedLine) { return null } +/** + * Given a generated file's absolute path, returns the first original source file + * referenced by its source map. This is useful for file-level metadata when the + * first generated line has no source mapping, such as TypeScript-emitted prologue + * statements. + * @param {string} absoluteFilePath - Absolute path to the generated file + * @returns {string | null} + */ +function resolveOriginalSourceFile (absoluteFilePath) { + const sourceMap = getCachedSourceMap(absoluteFilePath) + if (!sourceMap) return null + const { mappings, sources, sourceRoot } = sourceMap + if (!mappings || !sources?.length) return null + + const mapDir = path.dirname(absoluteFilePath) + const cursor = { pos: 0 } + let srcFile = 0 + + const lines = mappings.split(';') + for (const line of lines) { + if (!line) continue + cursor.pos = 0 + while (cursor.pos < line.length) { + decodeVLQ(line, cursor) // genCol - not needed + if (cursor.pos < line.length && line[cursor.pos] !== ',') { + srcFile += decodeVLQ(line, cursor) + decodeVLQ(line, cursor) // srcLine - not needed + decodeVLQ(line, cursor) // srcCol - not needed + if (cursor.pos < line.length && line[cursor.pos] !== ',') { + decodeVLQ(line, cursor) // namesIndex - not needed + } + + const sourcePath = sources[srcFile] + if (!sourcePath) return null + return resolveSourcePath(mapDir, sourceRoot, sourcePath) + } + if (cursor.pos < line.length && line[cursor.pos] === ',') cursor.pos++ + } + } + return null +} + /** * Convert a template literal body (the text between backticks, with `${…}` interpolations) * into a regex that matches the runtime-evaluated string. Each `${…}` expression is replaced @@ -294,4 +336,9 @@ function resolveSourceLineForTest (absoluteFilePath, testName, invocationStack) return null } -module.exports = { resolveOriginalSourcePosition, resolveSourceLineForTest, shouldTrustInvocationDetailsLine } +module.exports = { + resolveOriginalSourceFile, + resolveOriginalSourcePosition, + resolveSourceLineForTest, + shouldTrustInvocationDetailsLine, +}