From d7cadd90c76c07c2e799bde288f7187970c8f02a Mon Sep 17 00:00:00 2001 From: Cacie Prins Date: Thu, 30 Jan 2025 12:15:20 -0500 Subject: [PATCH 1/3] refactor: lifts toPosix to remove circular dependency between stack_utils and source_map_utils --- .../driver/src/cypress/source_map_utils.ts | 8 ++-- packages/driver/src/cypress/stack_utils.ts | 9 +---- packages/driver/src/cypress/util/toPosix.ts | 5 +++ .../test/unit/cypress/util/toPosix.spec.ts | 40 +++++++++++++++++++ 4 files changed, 50 insertions(+), 12 deletions(-) create mode 100644 packages/driver/src/cypress/util/toPosix.ts create mode 100644 packages/driver/test/unit/cypress/util/toPosix.spec.ts diff --git a/packages/driver/src/cypress/source_map_utils.ts b/packages/driver/src/cypress/source_map_utils.ts index 035e4db11dd2..371a37bfdf9d 100644 --- a/packages/driver/src/cypress/source_map_utils.ts +++ b/packages/driver/src/cypress/source_map_utils.ts @@ -6,7 +6,7 @@ import type { BasicSourceMapConsumer } from 'source-map' import mappingsWasm from 'source-map/lib/mappings.wasm' import $utils from './utils' -import stackUtils from './stack_utils' +import { toPosix } from './util/toPosix' const sourceMapExtractionRegex = /\/\/\s*[@#]\s*sourceMappingURL\s*=\s*(data:[^\s]*)/g const regexDataUrl = /data:[^;\n]+(?:;charset=[^;\n]+)?;base64,([a-zA-Z0-9+/]+={0,2})/ // matches data urls @@ -23,7 +23,7 @@ const initializeSourceMapConsumer = async (script, sourceMap): Promise { } const getSourceContents = (filePath, sourceFile) => { - const posixFilePath = stackUtils.toPosix(filePath) + const posixFilePath = toPosix(filePath) if (!sourceMapConsumers[posixFilePath]) return null @@ -72,7 +72,7 @@ const getSourceContents = (filePath, sourceFile) => { } const getSourcePosition = (filePath, position) => { - const posixFilePath = stackUtils.toPosix(filePath) + const posixFilePath = toPosix(filePath) const sourceMapConsumer = sourceMapConsumers[posixFilePath] if (!sourceMapConsumer) return null diff --git a/packages/driver/src/cypress/stack_utils.ts b/packages/driver/src/cypress/stack_utils.ts index 2dc557d370dc..4a060dc46c43 100644 --- a/packages/driver/src/cypress/stack_utils.ts +++ b/packages/driver/src/cypress/stack_utils.ts @@ -6,7 +6,7 @@ import { codeFrameColumns } from '@babel/code-frame' import $utils from './utils' import $sourceMapUtils from './source_map_utils' - +import { toPosix } from './util/toPosix' // Intentionally deep-importing from @packages/errors so as to not bundle the entire @packages/errors in the client unnecessarily import { getStackLines, replacedStack, stackWithoutMessage, splitStack, unsplitStack, stackLineRegex } from '@packages/errors/src/stackUtils' @@ -175,12 +175,6 @@ const getCodeFrameFromSource = (sourceCode, { line, column: originalColumn, rela } } -export const toPosix = (file: string) => { - return Cypress.config('platform') === 'win32' - ? file.replaceAll('\\', '/') - : file -} - const getRelativePathFromRoot = (relativeFile: string, absoluteFile?: string) => { // at this point relativeFile is relative to the cypress config // we need it to be relative to the repo root, which is different for monorepos @@ -540,5 +534,4 @@ export default { stackWithUserInvocationStackSpliced, captureUserInvocationStack, getInvocationDetails, - toPosix, } diff --git a/packages/driver/src/cypress/util/toPosix.ts b/packages/driver/src/cypress/util/toPosix.ts new file mode 100644 index 000000000000..4361abd46bee --- /dev/null +++ b/packages/driver/src/cypress/util/toPosix.ts @@ -0,0 +1,5 @@ +export const toPosix = (file: string) => { + return Cypress.config('platform') === 'win32' + ? file.replaceAll('\\', '/') + : file +} diff --git a/packages/driver/test/unit/cypress/util/toPosix.spec.ts b/packages/driver/test/unit/cypress/util/toPosix.spec.ts new file mode 100644 index 000000000000..6e3c2213b2ce --- /dev/null +++ b/packages/driver/test/unit/cypress/util/toPosix.spec.ts @@ -0,0 +1,40 @@ +/** + * @vitest-environment jsdom + */ + +import { vi, describe, it, expect, beforeEach, MockedFunction } from 'vitest' + +import { toPosix } from '../../../../src/cypress/util/toPosix' + +describe('toPosix', () => { + let config: MockedFunction + + beforeEach(() => { + config = vi.fn() + + // @ts-expect-error + global.Cypress = { + config, + } + }) + + describe('on windows', () => { + beforeEach(() => { + config.mockReturnValue('win32') + }) + + it('replaces backslashes with forward slashes', () => { + expect(toPosix('C:\\some\\file')).toEqual('C:/some/file') + }) + }) + + describe(`on other OS'`, () => { + beforeEach(() => { + config.mockReturnValue('darwin64') + }) + + it('performs as an identity function', () => { + expect(toPosix('/some/file')).toEqual('/some/file') + }) + }) +}) From 37ce4b185aed4745bcefc2bf3ff464932d63531e Mon Sep 17 00:00:00 2001 From: Cacie Prins Date: Thu, 30 Jan 2025 12:17:50 -0500 Subject: [PATCH 2/3] camelCase to snake_case --- .../driver/src/cypress/source_map_utils.ts | 2 +- packages/driver/src/cypress/stack_utils.ts | 2 +- .../src/cypress/util/source_map_utils.ts | 123 ++++++++++++++++++ .../cypress/util/{toPosix.ts => to_posix.ts} | 0 .../test/unit/cypress/util/toPosix.spec.ts | 2 +- 5 files changed, 126 insertions(+), 3 deletions(-) create mode 100644 packages/driver/src/cypress/util/source_map_utils.ts rename packages/driver/src/cypress/util/{toPosix.ts => to_posix.ts} (100%) diff --git a/packages/driver/src/cypress/source_map_utils.ts b/packages/driver/src/cypress/source_map_utils.ts index 371a37bfdf9d..a6a5b2f31b91 100644 --- a/packages/driver/src/cypress/source_map_utils.ts +++ b/packages/driver/src/cypress/source_map_utils.ts @@ -6,7 +6,7 @@ import type { BasicSourceMapConsumer } from 'source-map' import mappingsWasm from 'source-map/lib/mappings.wasm' import $utils from './utils' -import { toPosix } from './util/toPosix' +import { toPosix } from './util/to_posix' const sourceMapExtractionRegex = /\/\/\s*[@#]\s*sourceMappingURL\s*=\s*(data:[^\s]*)/g const regexDataUrl = /data:[^;\n]+(?:;charset=[^;\n]+)?;base64,([a-zA-Z0-9+/]+={0,2})/ // matches data urls diff --git a/packages/driver/src/cypress/stack_utils.ts b/packages/driver/src/cypress/stack_utils.ts index 4a060dc46c43..e0300d90b4f4 100644 --- a/packages/driver/src/cypress/stack_utils.ts +++ b/packages/driver/src/cypress/stack_utils.ts @@ -6,7 +6,7 @@ import { codeFrameColumns } from '@babel/code-frame' import $utils from './utils' import $sourceMapUtils from './source_map_utils' -import { toPosix } from './util/toPosix' +import { toPosix } from './util/to_posix' // Intentionally deep-importing from @packages/errors so as to not bundle the entire @packages/errors in the client unnecessarily import { getStackLines, replacedStack, stackWithoutMessage, splitStack, unsplitStack, stackLineRegex } from '@packages/errors/src/stackUtils' diff --git a/packages/driver/src/cypress/util/source_map_utils.ts b/packages/driver/src/cypress/util/source_map_utils.ts new file mode 100644 index 000000000000..49edf058cf8b --- /dev/null +++ b/packages/driver/src/cypress/util/source_map_utils.ts @@ -0,0 +1,123 @@ +import _ from 'lodash' +import { SourceMapConsumer } from 'source-map' + +import type { BasicSourceMapConsumer } from 'source-map' +// @ts-ignore +import mappingsWasm from 'source-map/lib/mappings.wasm' + +import $utils from '../utils' +import { toPosix } from './to_posix' + +const sourceMapExtractionRegex = /\/\/\s*[@#]\s*sourceMappingURL\s*=\s*(data:[^\s]*)/g +const regexDataUrl = /data:[^;\n]+(?:;charset=[^;\n]+)?;base64,([a-zA-Z0-9+/]+={0,2})/ // matches data urls + +let sourceMapConsumers: Record = {} + +const initializeSourceMapConsumer = async (script, sourceMap): Promise => { + if (!sourceMap) return null + + // @ts-ignore + SourceMapConsumer.initialize({ + 'lib/mappings.wasm': mappingsWasm, + }) + + const consumer = await new SourceMapConsumer(sourceMap) + + sourceMapConsumers[toPosix(script.fullyQualifiedUrl)] = consumer + + return consumer +} + +const extractSourceMap = (fileContents) => { + let dataUrlMatch + + try { + let sourceMapMatch = fileContents.match(sourceMapExtractionRegex) + + if (!sourceMapMatch) return null + + const url = _.last(sourceMapMatch) as any + + dataUrlMatch = url.match(regexDataUrl) + } catch (err) { + // ignore unable to match regex. there's nothing we + // can do about it and we don't want to thrown an exception + if (err.message === 'Maximum call stack size exceeded') return null + + throw err + } + + if (!dataUrlMatch) return null + + const sourceMapBase64 = dataUrlMatch[1] + const sourceMap = base64toJs(sourceMapBase64) + + return sourceMap +} + +const getSourceContents = (filePath, sourceFile) => { + const posixFilePath = toPosix(filePath) + + if (!sourceMapConsumers[posixFilePath]) return null + + try { + return sourceMapConsumers[posixFilePath].sourceContentFor(sourceFile) + } catch (err) { + // ignore the sourceFile not being in the source map. there's nothing we + // can do about it and we don't want to thrown an exception + if (err && err.message.indexOf('not in the SourceMap') > -1) return + + throw err + } +} + +const getSourcePosition = (filePath, position) => { + const posixFilePath = toPosix(filePath) + const sourceMapConsumer = sourceMapConsumers[posixFilePath] + + if (!sourceMapConsumer) return null + + const { source, line, column } = sourceMapConsumer.originalPositionFor(position) + + if (!source || line == null || column == null) return + + // if the file is outside of the projectRoot + // originalPositionFor will not provide the correct relative path + // https://github.com/cypress-io/cypress/issues/16255 + // @ts-expect-error + const sourceIndex = sourceMapConsumer._absoluteSources.indexOf(source) + // @ts-expect-error + const file = sourceMapConsumer._sources.at(sourceIndex) + + return { + file, + line, + column, + } +} + +const base64toJs = (base64) => { + const mapString = $utils.decodeBase64Unicode(base64) + + try { + return JSON.parse(mapString) + } catch (err) { + return null + } +} + +const destroySourceMapConsumers = () => { + Object.values(sourceMapConsumers).forEach((consumer) => { + consumer.destroy() + }) + + sourceMapConsumers = {} +} + +export default { + getSourcePosition, + getSourceContents, + extractSourceMap, + initializeSourceMapConsumer, + destroySourceMapConsumers, +} diff --git a/packages/driver/src/cypress/util/toPosix.ts b/packages/driver/src/cypress/util/to_posix.ts similarity index 100% rename from packages/driver/src/cypress/util/toPosix.ts rename to packages/driver/src/cypress/util/to_posix.ts diff --git a/packages/driver/test/unit/cypress/util/toPosix.spec.ts b/packages/driver/test/unit/cypress/util/toPosix.spec.ts index 6e3c2213b2ce..b1c76f821471 100644 --- a/packages/driver/test/unit/cypress/util/toPosix.spec.ts +++ b/packages/driver/test/unit/cypress/util/toPosix.spec.ts @@ -4,7 +4,7 @@ import { vi, describe, it, expect, beforeEach, MockedFunction } from 'vitest' -import { toPosix } from '../../../../src/cypress/util/toPosix' +import { toPosix } from '../../../../src/cypress/util/to_posix' describe('toPosix', () => { let config: MockedFunction From 5067843b2e8aab39b66c7b503329c9993b3c54bc Mon Sep 17 00:00:00 2001 From: Cacie Prins Date: Thu, 30 Jan 2025 12:24:11 -0500 Subject: [PATCH 3/3] rm duplicated file --- .../src/cypress/util/source_map_utils.ts | 123 ------------------ 1 file changed, 123 deletions(-) delete mode 100644 packages/driver/src/cypress/util/source_map_utils.ts diff --git a/packages/driver/src/cypress/util/source_map_utils.ts b/packages/driver/src/cypress/util/source_map_utils.ts deleted file mode 100644 index 49edf058cf8b..000000000000 --- a/packages/driver/src/cypress/util/source_map_utils.ts +++ /dev/null @@ -1,123 +0,0 @@ -import _ from 'lodash' -import { SourceMapConsumer } from 'source-map' - -import type { BasicSourceMapConsumer } from 'source-map' -// @ts-ignore -import mappingsWasm from 'source-map/lib/mappings.wasm' - -import $utils from '../utils' -import { toPosix } from './to_posix' - -const sourceMapExtractionRegex = /\/\/\s*[@#]\s*sourceMappingURL\s*=\s*(data:[^\s]*)/g -const regexDataUrl = /data:[^;\n]+(?:;charset=[^;\n]+)?;base64,([a-zA-Z0-9+/]+={0,2})/ // matches data urls - -let sourceMapConsumers: Record = {} - -const initializeSourceMapConsumer = async (script, sourceMap): Promise => { - if (!sourceMap) return null - - // @ts-ignore - SourceMapConsumer.initialize({ - 'lib/mappings.wasm': mappingsWasm, - }) - - const consumer = await new SourceMapConsumer(sourceMap) - - sourceMapConsumers[toPosix(script.fullyQualifiedUrl)] = consumer - - return consumer -} - -const extractSourceMap = (fileContents) => { - let dataUrlMatch - - try { - let sourceMapMatch = fileContents.match(sourceMapExtractionRegex) - - if (!sourceMapMatch) return null - - const url = _.last(sourceMapMatch) as any - - dataUrlMatch = url.match(regexDataUrl) - } catch (err) { - // ignore unable to match regex. there's nothing we - // can do about it and we don't want to thrown an exception - if (err.message === 'Maximum call stack size exceeded') return null - - throw err - } - - if (!dataUrlMatch) return null - - const sourceMapBase64 = dataUrlMatch[1] - const sourceMap = base64toJs(sourceMapBase64) - - return sourceMap -} - -const getSourceContents = (filePath, sourceFile) => { - const posixFilePath = toPosix(filePath) - - if (!sourceMapConsumers[posixFilePath]) return null - - try { - return sourceMapConsumers[posixFilePath].sourceContentFor(sourceFile) - } catch (err) { - // ignore the sourceFile not being in the source map. there's nothing we - // can do about it and we don't want to thrown an exception - if (err && err.message.indexOf('not in the SourceMap') > -1) return - - throw err - } -} - -const getSourcePosition = (filePath, position) => { - const posixFilePath = toPosix(filePath) - const sourceMapConsumer = sourceMapConsumers[posixFilePath] - - if (!sourceMapConsumer) return null - - const { source, line, column } = sourceMapConsumer.originalPositionFor(position) - - if (!source || line == null || column == null) return - - // if the file is outside of the projectRoot - // originalPositionFor will not provide the correct relative path - // https://github.com/cypress-io/cypress/issues/16255 - // @ts-expect-error - const sourceIndex = sourceMapConsumer._absoluteSources.indexOf(source) - // @ts-expect-error - const file = sourceMapConsumer._sources.at(sourceIndex) - - return { - file, - line, - column, - } -} - -const base64toJs = (base64) => { - const mapString = $utils.decodeBase64Unicode(base64) - - try { - return JSON.parse(mapString) - } catch (err) { - return null - } -} - -const destroySourceMapConsumers = () => { - Object.values(sourceMapConsumers).forEach((consumer) => { - consumer.destroy() - }) - - sourceMapConsumers = {} -} - -export default { - getSourcePosition, - getSourceContents, - extractSourceMap, - initializeSourceMapConsumer, - destroySourceMapConsumers, -}