diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 2f585035f552..f2eadf738a54 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -896,15 +896,15 @@ jobs: - name: Overwrite typescript version if: matrix.typescript - run: yarn add --dev --ignore-workspace-root-check typescript@${{ matrix.typescript }} + run: node ./scripts/use-ts-version.js ${{ matrix.typescript }} + working-directory: dev-packages/node-integration-tests - name: Run integration tests env: NODE_VERSION: ${{ matrix.node }} TS_VERSION: ${{ matrix.typescript }} - run: | - cd dev-packages/node-integration-tests - yarn test + working-directory: dev-packages/node-integration-tests + run: yarn test job_remix_integration_tests: name: Remix v${{ matrix.remix }} (Node ${{ matrix.node }}) Tests diff --git a/dev-packages/node-integration-tests/scripts/use-ts-version.js b/dev-packages/node-integration-tests/scripts/use-ts-version.js new file mode 100644 index 000000000000..0b64d735436c --- /dev/null +++ b/dev-packages/node-integration-tests/scripts/use-ts-version.js @@ -0,0 +1,23 @@ +/* eslint-disable no-console */ +const { execSync } = require('child_process'); +const { join } = require('path'); +const { writeFileSync } = require('fs'); + +const cwd = join(__dirname, '../../..'); + +const tsVersion = process.argv[2] || '3.8'; + +console.log(`Installing typescript@${tsVersion}...`); + +execSync(`yarn add --dev --ignore-workspace-root-check typescript@${tsVersion}`, { stdio: 'inherit', cwd }); + +console.log('Removing unsupported tsconfig options...'); + +const baseTscConfigPath = join(cwd, 'packages/typescript/tsconfig.json'); + +const tsConfig = require(baseTscConfigPath); + +// TS 3.8 fails build when it encounteres a config option it does not understand, so we remove it :( +delete tsConfig.compilerOptions.noUncheckedIndexedAccess; + +writeFileSync(baseTscConfigPath, JSON.stringify(tsConfig, null, 2)); diff --git a/dev-packages/node-integration-tests/suites/express/multiple-routers/complex-router/test.ts b/dev-packages/node-integration-tests/suites/express/multiple-routers/complex-router/test.ts index d3791083f1f1..a544095982c7 100644 --- a/dev-packages/node-integration-tests/suites/express/multiple-routers/complex-router/test.ts +++ b/dev-packages/node-integration-tests/suites/express/multiple-routers/complex-router/test.ts @@ -9,7 +9,7 @@ afterAll(() => { conditionalTest({ min: 16 })('complex-router', () => { test('should construct correct url with multiple parameterized routers, when param is also contain in middle layer route and express used multiple middlewares with route', done => { // parse node.js major version - const [major] = process.versions.node.split('.').map(Number); + const [major = 0] = process.versions.node.split('.').map(Number); // Split test result base on major node version because regex d flag is support from node 16+ const EXPECTED_TRANSACTION = @@ -36,7 +36,7 @@ conditionalTest({ min: 16 })('complex-router', () => { test('should construct correct url with multiple parameterized routers, when param is also contain in middle layer route and express used multiple middlewares with route and original url has query params', done => { // parse node.js major version - const [major] = process.versions.node.split('.').map(Number); + const [major = 0] = process.versions.node.split('.').map(Number); // Split test result base on major node version because regex d flag is support from node 16+ const EXPECTED_TRANSACTION = major >= 16 @@ -62,7 +62,7 @@ conditionalTest({ min: 16 })('complex-router', () => { test('should construct correct url with multiple parameterized routers, when param is also contain in middle layer route and express used multiple middlewares with route and original url ends with trailing slash and has query params', done => { // parse node.js major version - const [major] = process.versions.node.split('.').map(Number); + const [major = 0] = process.versions.node.split('.').map(Number); // Split test result base on major node version because regex d flag is support from node 16+ const EXPECTED_TRANSACTION = major >= 16 diff --git a/dev-packages/node-integration-tests/suites/express/multiple-routers/middle-layer-parameterized/test.ts b/dev-packages/node-integration-tests/suites/express/multiple-routers/middle-layer-parameterized/test.ts index a92d8e738e29..f6548ed161b9 100644 --- a/dev-packages/node-integration-tests/suites/express/multiple-routers/middle-layer-parameterized/test.ts +++ b/dev-packages/node-integration-tests/suites/express/multiple-routers/middle-layer-parameterized/test.ts @@ -9,7 +9,7 @@ afterAll(() => { conditionalTest({ min: 16 })('middle-layer-parameterized', () => { test('should construct correct url with multiple parameterized routers, when param is also contain in middle layer route', done => { // parse node.js major version - const [major] = process.versions.node.split('.').map(Number); + const [major = 0] = process.versions.node.split('.').map(Number); // Split test result base on major node version because regex d flag is support from node 16+ const EXPECTED_TRANSACTION = major >= 16 diff --git a/dev-packages/node-integration-tests/suites/public-api/LocalVariables/test.ts b/dev-packages/node-integration-tests/suites/public-api/LocalVariables/test.ts index f700ddd6ac67..c63fe2daf5df 100644 --- a/dev-packages/node-integration-tests/suites/public-api/LocalVariables/test.ts +++ b/dev-packages/node-integration-tests/suites/public-api/LocalVariables/test.ts @@ -44,7 +44,7 @@ conditionalTest({ min: 18 })('LocalVariables integration', () => { .ignore('session') .expect({ event: event => { - for (const frame of event.exception?.values?.[0].stacktrace?.frames || []) { + for (const frame of event.exception?.values?.[0]?.stacktrace?.frames || []) { expect(frame.vars).toBeUndefined(); } }, diff --git a/dev-packages/node-integration-tests/suites/public-api/startSpan/with-nested-spans/test.ts b/dev-packages/node-integration-tests/suites/public-api/startSpan/with-nested-spans/test.ts index e5ebd5bf9abc..f7a1678e1eb6 100644 --- a/dev-packages/node-integration-tests/suites/public-api/startSpan/with-nested-spans/test.ts +++ b/dev-packages/node-integration-tests/suites/public-api/startSpan/with-nested-spans/test.ts @@ -10,7 +10,7 @@ test('should report finished spans as children of the root transaction.', done = .expect({ transaction: transaction => { const rootSpanId = transaction.contexts?.trace?.span_id; - const span3Id = transaction.spans?.[1].span_id; + const span3Id = transaction.spans?.[1]?.span_id; expect(rootSpanId).toEqual(expect.any(String)); expect(span3Id).toEqual(expect.any(String)); diff --git a/dev-packages/node-integration-tests/suites/tsconfig.json b/dev-packages/node-integration-tests/suites/tsconfig.json new file mode 100644 index 000000000000..120c3aff3716 --- /dev/null +++ b/dev-packages/node-integration-tests/suites/tsconfig.json @@ -0,0 +1,3 @@ +{ + "extends": "../tsconfig.test.json", +} diff --git a/dev-packages/node-integration-tests/utils/index.ts b/dev-packages/node-integration-tests/utils/index.ts index ac0bbf997d0b..52223f4417a2 100644 --- a/dev-packages/node-integration-tests/utils/index.ts +++ b/dev-packages/node-integration-tests/utils/index.ts @@ -196,7 +196,13 @@ export class TestEnv { * @returns The extracted envelope. */ public async getEnvelopeRequest(options?: DataCollectorOptions): Promise>> { - return (await this.getMultipleEnvelopeRequest({ ...options, count: 1 }))[0]; + const requests = await this.getMultipleEnvelopeRequest({ ...options, count: 1 }); + + if (!requests[0]) { + throw new Error('No requests found'); + } + + return requests[0]; } /** @@ -239,7 +245,7 @@ export class TestEnv { .post('/api/1337/envelope/', body => { const envelope = parseEnvelope(body); - if (envelopeType.includes(envelope[1].type as EnvelopeItemType)) { + if (envelopeType.includes(envelope[1]?.type as EnvelopeItemType)) { envelopes.push(envelope); } else { return false; @@ -296,7 +302,7 @@ export class TestEnv { .post('/api/1337/envelope/', body => { const envelope = parseEnvelope(body); - if (options.envelopeType.includes(envelope[1].type as EnvelopeItemType)) { + if (options.envelopeType.includes(envelope[1]?.type as EnvelopeItemType)) { reqCount++; return true; } diff --git a/dev-packages/overhead-metrics/src/util/github.ts b/dev-packages/overhead-metrics/src/util/github.ts index e48cbb55eeea..707d9529f8ae 100644 --- a/dev-packages/overhead-metrics/src/util/github.ts +++ b/dev-packages/overhead-metrics/src/util/github.ts @@ -13,7 +13,7 @@ const octokit = new Octokit({ // log: console, }); -const [, owner, repo] = (await Git.repository).split('/'); +const [, owner, repo] = (await Git.repository).split('/') as [string, string, string]; const defaultArgs = { owner, repo }; async function downloadArtifact(url: string, path: string): Promise { @@ -51,15 +51,10 @@ async function tryAddOrUpdateComment(commentBuilder: PrCommentBuilder): Promise< For example, refs/heads/feature-branch-1. */ let prNumber: number | undefined; - if ( - typeof process.env.GITHUB_REF == 'string' && - process.env.GITHUB_REF.length > 0 && - process.env.GITHUB_REF.startsWith('refs/pull/') - ) { - prNumber = parseInt(process.env.GITHUB_REF.split('/')[2]); - console.log( - `Determined PR number ${prNumber} based on GITHUB_REF environment variable: '${process.env.GITHUB_REF}'`, - ); + const githubRef = process.env.GITHUB_REF; + if (typeof githubRef == 'string' && githubRef.length > 0 && githubRef.startsWith('refs/pull/')) { + prNumber = parseInt(githubRef.split('/')[2] as string); + console.log(`Determined PR number ${prNumber} based on GITHUB_REF environment variable: '${githubRef}'`); } else if (!(await Git.branchIsBase)) { prNumber = ( await octokit.rest.pulls.list({ @@ -155,7 +150,9 @@ export const GitHub = { status: 'success', }); - if (workflowRuns.data.total_count == 0) { + const firstRun = workflowRuns.data.workflow_runs[0]; + + if (workflowRuns.data.total_count == 0 || !firstRun) { console.warn(`Couldn't find any successful run for workflow '${workflow.name}'`); return; } @@ -163,7 +160,7 @@ export const GitHub = { const artifact = ( await octokit.actions.listWorkflowRunArtifacts({ ...defaultArgs, - run_id: workflowRuns.data.workflow_runs[0].id, + run_id: firstRun.id, }) ).data.artifacts.find(it => it.name == artifactName); diff --git a/dev-packages/test-utils/src/event-proxy-server.ts b/dev-packages/test-utils/src/event-proxy-server.ts index 1274f77062bd..25ddbfe94966 100644 --- a/dev-packages/test-utils/src/event-proxy-server.ts +++ b/dev-packages/test-utils/src/event-proxy-server.ts @@ -59,7 +59,7 @@ export async function startEventProxyServer(options: EventProxyServerOptions): P ? zlib.gunzipSync(Buffer.concat(proxyRequestChunks)).toString() : Buffer.concat(proxyRequestChunks).toString(); - const envelopeHeader: EnvelopeItem[0] = JSON.parse(proxyRequestBody.split('\n')[0]); + const envelopeHeader: EnvelopeItem[0] = JSON.parse(proxyRequestBody.split('\n')[0] as string); const shouldForwardEventToSentry = options.forwardToSentry != null ? options.forwardToSentry : true; diff --git a/packages/aws-serverless/src/sdk.ts b/packages/aws-serverless/src/sdk.ts index 905672241ffb..30fdbc19635f 100644 --- a/packages/aws-serverless/src/sdk.ts +++ b/packages/aws-serverless/src/sdk.ts @@ -141,7 +141,7 @@ export function tryPatchHandler(taskRoot: string, handlerPath: string): void { return; } - const [, handlerMod, handlerName] = match; + const [, handlerMod = '', handlerName = ''] = match; let obj: HandlerBag; try { diff --git a/packages/aws-serverless/test/sdk.test.ts b/packages/aws-serverless/test/sdk.test.ts index 9a085ad1ec78..4f5daf3b94a9 100644 --- a/packages/aws-serverless/test/sdk.test.ts +++ b/packages/aws-serverless/test/sdk.test.ts @@ -502,7 +502,7 @@ describe('AWSLambda', () => { expect(evtProcessor).toBeInstanceOf(Function); // @ts-expect-error just mocking around... - expect(evtProcessor(event).exception.values[0].mechanism).toEqual({ + expect(evtProcessor(event).exception.values[0]?.mechanism).toEqual({ handled: false, type: 'generic', }); diff --git a/packages/browser-utils/src/metrics/browserMetrics.ts b/packages/browser-utils/src/metrics/browserMetrics.ts index bb9969f1fde6..6999641a641f 100644 --- a/packages/browser-utils/src/metrics/browserMetrics.ts +++ b/packages/browser-utils/src/metrics/browserMetrics.ts @@ -282,13 +282,14 @@ export function addPerformanceEntries(span: Span): void { _addTtfbRequestTimeToMeasurements(_measurements); ['fcp', 'fp', 'lcp'].forEach(name => { - if (!_measurements[name] || !transactionStartTime || timeOrigin >= transactionStartTime) { + const measurement = _measurements[name]; + if (!measurement || !transactionStartTime || timeOrigin >= transactionStartTime) { return; } // The web vitals, fcp, fp, lcp, and ttfb, all measure relative to timeOrigin. // Unfortunately, timeOrigin is not captured within the span span data, so these web vitals will need // to be adjusted to be relative to span.startTimestamp. - const oldValue = _measurements[name].value; + const oldValue = measurement.value; const measurementTimestamp = timeOrigin + msToSec(oldValue); // normalizedValue should be in milliseconds @@ -296,7 +297,7 @@ export function addPerformanceEntries(span: Span): void { const delta = normalizedValue - oldValue; DEBUG_BUILD && logger.log(`[Measurements] Normalized ${name} from ${oldValue} to ${normalizedValue} (${delta})`); - _measurements[name].value = normalizedValue; + measurement.value = normalizedValue; }); const fidMark = _measurements['mark.fid']; @@ -320,8 +321,8 @@ export function addPerformanceEntries(span: Span): void { delete _measurements.cls; } - Object.keys(_measurements).forEach(measurementName => { - setMeasurement(measurementName, _measurements[measurementName].value, _measurements[measurementName].unit); + Object.entries(_measurements).forEach(([measurementName, measurement]) => { + setMeasurement(measurementName, measurement.value, measurement.unit); }); _tagMetricInfo(span); diff --git a/packages/browser-utils/src/metrics/web-vitals/getCLS.ts b/packages/browser-utils/src/metrics/web-vitals/getCLS.ts index f72b0aa309a0..380cf2e54d47 100644 --- a/packages/browser-utils/src/metrics/web-vitals/getCLS.ts +++ b/packages/browser-utils/src/metrics/web-vitals/getCLS.ts @@ -70,6 +70,8 @@ export const onCLS = (onReport: CLSReportCallback, opts: ReportOpts = {}): void // session. if ( sessionValue && + firstSessionEntry && + lastSessionEntry && entry.startTime - lastSessionEntry.startTime < 1000 && entry.startTime - firstSessionEntry.startTime < 5000 ) { diff --git a/packages/browser-utils/src/metrics/web-vitals/getINP.ts b/packages/browser-utils/src/metrics/web-vitals/getINP.ts index fa2d90da3371..96558f2cf109 100644 --- a/packages/browser-utils/src/metrics/web-vitals/getINP.ts +++ b/packages/browser-utils/src/metrics/web-vitals/getINP.ts @@ -74,7 +74,7 @@ const processEntry = (entry: PerformanceEventTiming) => { if ( existingInteraction || longestInteractionList.length < MAX_INTERACTIONS_TO_CONSIDER || - entry.duration > minLongestInteraction.latency + (minLongestInteraction && entry.duration > minLongestInteraction.latency) ) { // If the interaction already exists, update it. Otherwise create one. if (existingInteraction) { diff --git a/packages/browser-utils/test/browser/browserMetrics.test.ts b/packages/browser-utils/test/browser/browserMetrics.test.ts index 555dfde1c92f..8e67df1645d0 100644 --- a/packages/browser-utils/test/browser/browserMetrics.test.ts +++ b/packages/browser-utils/test/browser/browserMetrics.test.ts @@ -221,7 +221,7 @@ describe('_addResourceSpans', () => { }, ]; for (let i = 0; i < table.length; i++) { - const { initiatorType, op } = table[i]; + const { initiatorType, op } = table[i]!; const entry: ResourceEntry = { initiatorType, }; @@ -250,7 +250,7 @@ describe('_addResourceSpans', () => { _addResourceSpans(span, entry, resourceEntryName, 100, 23, 345); expect(spans).toHaveLength(1); - expect(spanToJSON(spans[0])).toEqual( + expect(spanToJSON(spans[0]!)).toEqual( expect.objectContaining({ data: { [SEMANTIC_ATTRIBUTE_SENTRY_OP]: 'resource.css', @@ -284,7 +284,7 @@ describe('_addResourceSpans', () => { _addResourceSpans(span, entry, resourceEntryName, 100, 23, 345); expect(spans).toHaveLength(1); - expect(spanToJSON(spans[0])).toEqual( + expect(spanToJSON(spans[0]!)).toEqual( expect.objectContaining({ data: { [SEMANTIC_ATTRIBUTE_SENTRY_OP]: 'resource.css', @@ -321,7 +321,7 @@ describe('_addResourceSpans', () => { _addResourceSpans(span, entry, resourceEntryName, 100, 23, 345); expect(spans).toHaveLength(1); - expect(spanToJSON(spans[0])).toEqual( + expect(spanToJSON(spans[0]!)).toEqual( expect.objectContaining({ data: { [SEMANTIC_ATTRIBUTE_SENTRY_OP]: 'resource.css', diff --git a/packages/browser/src/eventbuilder.ts b/packages/browser/src/eventbuilder.ts index fcb7808178f2..ebd4cee78b2a 100644 --- a/packages/browser/src/eventbuilder.ts +++ b/packages/browser/src/eventbuilder.ts @@ -89,7 +89,8 @@ function eventFromPlainObject( const frames = parseStackFrames(stackParser, syntheticException); if (frames.length) { // event.exception.values[0] has been set above - event.exception.values[0].stacktrace = { frames }; + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion + event.exception.values[0]!.stacktrace = { frames }; } } diff --git a/packages/browser/src/integrations/httpclient.ts b/packages/browser/src/integrations/httpclient.ts index b48cc69d09c0..af1c662007c3 100644 --- a/packages/browser/src/integrations/httpclient.ts +++ b/packages/browser/src/integrations/httpclient.ts @@ -77,28 +77,8 @@ function _fetchResponseHandler( let requestHeaders, responseHeaders, requestCookies, responseCookies; if (_shouldSendDefaultPii()) { - [{ headers: requestHeaders, cookies: requestCookies }, { headers: responseHeaders, cookies: responseCookies }] = [ - { cookieHeader: 'Cookie', obj: request }, - { cookieHeader: 'Set-Cookie', obj: response }, - ].map(({ cookieHeader, obj }) => { - const headers = _extractFetchHeaders(obj.headers); - let cookies; - - try { - const cookieString = headers[cookieHeader] || headers[cookieHeader.toLowerCase()] || undefined; - - if (cookieString) { - cookies = _parseCookieString(cookieString); - } - } catch (e) { - DEBUG_BUILD && logger.log(`Could not extract cookies from header ${cookieHeader}`); - } - - return { - headers, - cookies, - }; - }); + [requestHeaders, requestCookies] = _parseCookieHeaders('Cookie', request); + [responseHeaders, responseCookies] = _parseCookieHeaders('Set-Cookie', response); } const event = _createEvent({ @@ -115,6 +95,26 @@ function _fetchResponseHandler( } } +function _parseCookieHeaders( + cookieHeader: string, + obj: Request | Response, +): [Record, Record | undefined] { + const headers = _extractFetchHeaders(obj.headers); + let cookies; + + try { + const cookieString = headers[cookieHeader] || headers[cookieHeader.toLowerCase()] || undefined; + + if (cookieString) { + cookies = _parseCookieString(cookieString); + } + } catch (e) { + DEBUG_BUILD && logger.log(`Could not extract cookies from header ${cookieHeader}`); + } + + return [headers, cookies]; +} + /** * Interceptor function for XHR requests * @@ -192,7 +192,9 @@ function _getResponseSizeFromHeaders(headers?: Record): number | function _parseCookieString(cookieString: string): Record { return cookieString.split('; ').reduce((acc: Record, cookie: string) => { const [key, value] = cookie.split('='); - acc[key] = value; + if (key && value) { + acc[key] = value; + } return acc; }, {}); } @@ -228,7 +230,9 @@ function _getXHRResponseHeaders(xhr: XMLHttpRequest): Record { return headers.split('\r\n').reduce((acc: Record, line: string) => { const [key, value] = line.split(': '); - acc[key] = value; + if (key && value) { + acc[key] = value; + } return acc; }, {}); } diff --git a/packages/browser/src/profiling/utils.ts b/packages/browser/src/profiling/utils.ts index 7c22f938f619..48e87450b0e8 100644 --- a/packages/browser/src/profiling/utils.ts +++ b/packages/browser/src/profiling/utils.ts @@ -74,7 +74,8 @@ if (isUserAgentData(userAgentData)) { OS_PLATFORM_VERSION = ua.platformVersion || ''; if (ua.fullVersionList && ua.fullVersionList.length > 0) { - const firstUa = ua.fullVersionList[ua.fullVersionList.length - 1]; + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion + const firstUa = ua.fullVersionList[ua.fullVersionList.length - 1]!; OS_BROWSER = `${firstUa.brand} ${firstUa.version}`; } }) @@ -239,12 +240,13 @@ export function convertJSSelfProfileToSampledFormat(input: JSSelfProfile): Profi }, }; - if (!input.samples.length) { + const firstSample = input.samples[0]; + if (!firstSample) { return profile; } // We assert samples.length > 0 above and timestamp should always be present - const start = input.samples[0].timestamp; + const start = firstSample.timestamp; // The JS SDK might change it's time origin based on some heuristic (see See packages/utils/src/time.ts) // when that happens, we need to ensure we are correcting the profile timings so the two timelines stay in sync. // Since JS self profiling time origin is always initialized to performance.timeOrigin, we need to adjust for @@ -253,9 +255,7 @@ export function convertJSSelfProfileToSampledFormat(input: JSSelfProfile): Profi typeof performance.timeOrigin === 'number' ? performance.timeOrigin : browserPerformanceTimeOrigin || 0; const adjustForOriginChange = origin - (browserPerformanceTimeOrigin || origin); - for (let i = 0; i < input.samples.length; i++) { - const jsSample = input.samples[i]; - + input.samples.forEach((jsSample, i) => { // If sample has no stack, add an empty sample if (jsSample.stackId === undefined) { if (EMPTY_STACK_ID === undefined) { @@ -270,7 +270,7 @@ export function convertJSSelfProfileToSampledFormat(input: JSSelfProfile): Profi stack_id: EMPTY_STACK_ID, thread_id: THREAD_ID_STRING, }; - continue; + return; } let stackTop: JSSelfProfileStack | undefined = input.stacks[jsSample.stackId]; @@ -285,7 +285,7 @@ export function convertJSSelfProfileToSampledFormat(input: JSSelfProfile): Profi const frame = input.frames[stackTop.frameId]; // If our frame has not been indexed yet, index it - if (profile.frames[stackTop.frameId] === undefined) { + if (frame && profile.frames[stackTop.frameId] === undefined) { profile.frames[stackTop.frameId] = { function: frame.name, abs_path: typeof frame.resourceId === 'number' ? input.resources[frame.resourceId] : undefined, @@ -307,7 +307,7 @@ export function convertJSSelfProfileToSampledFormat(input: JSSelfProfile): Profi profile['stacks'][STACK_ID] = stack; profile['samples'][i] = sample; STACK_ID++; - } + }); return profile; } diff --git a/packages/browser/src/stack-parsers.ts b/packages/browser/src/stack-parsers.ts index 033d6ee3411b..2031c1861654 100644 --- a/packages/browser/src/stack-parsers.ts +++ b/packages/browser/src/stack-parsers.ts @@ -66,20 +66,20 @@ const chromeEvalRegex = /\((\S*)(?::(\d+))(?::(\d+))\)/; // See: https://github.com/getsentry/sentry-javascript/issues/6880 const chromeStackParserFn: StackLineParserFn = line => { // If the stack line has no function name, we need to parse it differently - const noFnParts = chromeRegexNoFnName.exec(line); + const noFnParts = chromeRegexNoFnName.exec(line) as null | [string, string, string, string]; if (noFnParts) { const [, filename, line, col] = noFnParts; return createFrame(filename, UNKNOWN_FUNCTION, +line, +col); } - const parts = chromeRegex.exec(line); + const parts = chromeRegex.exec(line) as null | [string, string, string, string, string]; if (parts) { const isEval = parts[2] && parts[2].indexOf('eval') === 0; // start of line if (isEval) { - const subMatch = chromeEvalRegex.exec(parts[2]); + const subMatch = chromeEvalRegex.exec(parts[2]) as null | [string, string, string, string]; if (subMatch) { // throw out eval line/column and use top-most line/column number @@ -109,12 +109,12 @@ const geckoREgex = const geckoEvalRegex = /(\S+) line (\d+)(?: > eval line \d+)* > eval/i; const gecko: StackLineParserFn = line => { - const parts = geckoREgex.exec(line); + const parts = geckoREgex.exec(line) as null | [string, string, string, string, string, string]; if (parts) { const isEval = parts[3] && parts[3].indexOf(' > eval') > -1; if (isEval) { - const subMatch = geckoEvalRegex.exec(parts[3]); + const subMatch = geckoEvalRegex.exec(parts[3]) as null | [string, string, string]; if (subMatch) { // throw out eval line/column and use top-most line number @@ -140,7 +140,7 @@ export const geckoStackLineParser: StackLineParser = [GECKO_PRIORITY, gecko]; const winjsRegex = /^\s*at (?:((?:\[object object\])?.+) )?\(?((?:[-a-z]+):.*?):(\d+)(?::(\d+))?\)?\s*$/i; const winjs: StackLineParserFn = line => { - const parts = winjsRegex.exec(line); + const parts = winjsRegex.exec(line) as null | [string, string, string, string, string]; return parts ? createFrame(parts[2], parts[1] || UNKNOWN_FUNCTION, +parts[3], parts[4] ? +parts[4] : undefined) @@ -152,7 +152,7 @@ export const winjsStackLineParser: StackLineParser = [WINJS_PRIORITY, winjs]; const opera10Regex = / line (\d+).*script (?:in )?(\S+)(?:: in function (\S+))?$/i; const opera10: StackLineParserFn = line => { - const parts = opera10Regex.exec(line); + const parts = opera10Regex.exec(line) as null | [string, string, string, string]; return parts ? createFrame(parts[2], parts[3] || UNKNOWN_FUNCTION, +parts[1]) : undefined; }; @@ -162,7 +162,7 @@ const opera11Regex = / line (\d+), column (\d+)\s*(?:in (?:]+)>|([^)]+))\(.*\))? in (.*):\s*$/i; const opera11: StackLineParserFn = line => { - const parts = opera11Regex.exec(line); + const parts = opera11Regex.exec(line) as null | [string, string, string, string, string, string]; return parts ? createFrame(parts[5], parts[3] || parts[4] || UNKNOWN_FUNCTION, +parts[1], +parts[2]) : undefined; }; @@ -198,7 +198,7 @@ const extractSafariExtensionDetails = (func: string, filename: string): [string, return isSafariExtension || isSafariWebExtension ? [ - func.indexOf('@') !== -1 ? func.split('@')[0] : UNKNOWN_FUNCTION, + func.indexOf('@') !== -1 ? (func.split('@')[0] as string) : UNKNOWN_FUNCTION, isSafariExtension ? `safari-extension:${filename}` : `safari-web-extension:${filename}`, ] : [func, filename]; diff --git a/packages/browser/src/tracing/request.ts b/packages/browser/src/tracing/request.ts index 89b87ba6b757..b7b01b5e8ce3 100644 --- a/packages/browser/src/tracing/request.ts +++ b/packages/browser/src/tracing/request.ts @@ -193,13 +193,13 @@ export function extractNetworkProtocol(nextHopProtocol: string): { name: string; for (const char of nextHopProtocol) { // http/1.1 etc. if (char === '/') { - [name, version] = nextHopProtocol.split('/'); + [name, version] = nextHopProtocol.split('/') as [string, string]; break; } // h2, h3 etc. if (!isNaN(Number(char))) { name = _name === 'h' ? 'http' : _name; - version = nextHopProtocol.split(_name)[1]; + version = nextHopProtocol.split(_name)[1] as string; break; } _name += char; diff --git a/packages/browser/src/transports/offline.ts b/packages/browser/src/transports/offline.ts index 4a435b2972fa..a6755d8f43b5 100644 --- a/packages/browser/src/transports/offline.ts +++ b/packages/browser/src/transports/offline.ts @@ -82,12 +82,13 @@ export function unshift(store: Store, value: Uint8Array | string, maxQueueSize: export function shift(store: Store): Promise { return store(store => { return keys(store).then(keys => { - if (keys.length === 0) { + const firstKey = keys[0]; + if (firstKey == null) { return undefined; } - return promisifyRequest(store.get(keys[0])).then(value => { - store.delete(keys[0]); + return promisifyRequest(store.get(firstKey)).then(value => { + store.delete(firstKey); return promisifyRequest(store.transaction).then(() => value); }); }); diff --git a/packages/browser/test/unit/index.test.ts b/packages/browser/test/unit/index.test.ts index f1cb7c971d71..f0aa2db66417 100644 --- a/packages/browser/test/unit/index.test.ts +++ b/packages/browser/test/unit/index.test.ts @@ -214,7 +214,7 @@ describe('SentryBrowser', () => { captureMessage('event'); await flush(2000); - expect(beforeSend.mock.calls[0][0].breadcrumbs).toHaveLength(2); + expect(beforeSend.mock.calls[0]?.[0]?.breadcrumbs).toHaveLength(2); }); }); @@ -228,12 +228,12 @@ describe('SentryBrowser', () => { await flush(2000); - const event = beforeSend.mock.calls[0][0]; + const event = beforeSend.mock.calls[0]?.[0]; expect(event.exception).toBeDefined(); expect(event.exception.values[0]).toBeDefined(); - expect(event.exception.values[0].type).toBe('Error'); - expect(event.exception.values[0].value).toBe('test'); - expect(event.exception.values[0].stacktrace.frames).not.toHaveLength(0); + expect(event.exception.values[0]?.type).toBe('Error'); + expect(event.exception.values[0]?.value).toBe('test'); + expect(event.exception.values[0]?.stacktrace.frames).not.toHaveLength(0); }); it('should capture a message', done => { @@ -358,8 +358,8 @@ describe('SentryBrowser initialization', () => { const sdkData = getClient()?.getOptions()._metadata?.sdk || {}; expect(sdkData?.name).toBe('sentry.javascript.browser'); - expect(sdkData?.packages?.[0].name).toBe('npm:@sentry/browser'); - expect(sdkData?.packages?.[0].version).toBe(SDK_VERSION); + expect(sdkData?.packages?.[0]?.name).toBe('npm:@sentry/browser'); + expect(sdkData?.packages?.[0]?.version).toBe(SDK_VERSION); expect(sdkData?.version).toBe(SDK_VERSION); }); @@ -369,7 +369,7 @@ describe('SentryBrowser initialization', () => { const sdkData = getClient()?.getOptions()._metadata?.sdk || {}; - expect(sdkData.packages?.[0].name).toBe('loader:@sentry/browser'); + expect(sdkData.packages?.[0]?.name).toBe('loader:@sentry/browser'); delete global.SENTRY_SDK_SOURCE; }); @@ -379,7 +379,7 @@ describe('SentryBrowser initialization', () => { const sdkData = getClient()?.getOptions()._metadata?.sdk || {}; - expect(sdkData.packages?.[0].name).toBe('cdn:@sentry/browser'); + expect(sdkData.packages?.[0]?.name).toBe('cdn:@sentry/browser'); expect(utils.getSDKSource).toBeCalledTimes(1); spy.mockRestore(); }); @@ -391,8 +391,8 @@ describe('SentryBrowser initialization', () => { const sdkData = client.getOptions()._metadata?.sdk as any; expect(sdkData.name).toBe('sentry.javascript.browser'); - expect(sdkData.packages[0].name).toBe('npm:@sentry/browser'); - expect(sdkData.packages[0].version).toBe(SDK_VERSION); + expect(sdkData.packages[0]?.name).toBe('npm:@sentry/browser'); + expect(sdkData.packages[0]?.version).toBe(SDK_VERSION); expect(sdkData.version).toBe(SDK_VERSION); }); @@ -419,8 +419,8 @@ describe('SentryBrowser initialization', () => { const sdkData = getClient()?.getOptions()._metadata?.sdk || {}; expect(sdkData.name).toBe('sentry.javascript.angular'); - expect(sdkData.packages?.[0].name).toBe('npm:@sentry/angular'); - expect(sdkData.packages?.[0].version).toBe(SDK_VERSION); + expect(sdkData.packages?.[0]?.name).toBe('npm:@sentry/angular'); + expect(sdkData.packages?.[0]?.version).toBe(SDK_VERSION); expect(sdkData.version).toBe(SDK_VERSION); }); }); diff --git a/packages/browser/test/unit/integrations/breadcrumbs.test.ts b/packages/browser/test/unit/integrations/breadcrumbs.test.ts index 3c9713a872d2..15ad5f3e4261 100644 --- a/packages/browser/test/unit/integrations/breadcrumbs.test.ts +++ b/packages/browser/test/unit/integrations/breadcrumbs.test.ts @@ -19,7 +19,7 @@ describe('Breadcrumbs', () => { client.captureMessage('test'); await flush(2000); - expect(addBreadcrumbSpy.mock.calls[0][0].category).toEqual('sentry.event'); - expect(addBreadcrumbSpy.mock.calls[0][0].message).toEqual('test'); + expect(addBreadcrumbSpy.mock.calls[0]?.[0]?.category).toEqual('sentry.event'); + expect(addBreadcrumbSpy.mock.calls[0]?.[0]?.message).toEqual('test'); }); }); diff --git a/packages/browser/test/unit/profiling/utils.test.ts b/packages/browser/test/unit/profiling/utils.test.ts index 42a747f1aaa9..573d9dbb335e 100644 --- a/packages/browser/test/unit/profiling/utils.test.ts +++ b/packages/browser/test/unit/profiling/utils.test.ts @@ -109,11 +109,11 @@ describe('convertJSSelfProfileToSampledFormat', () => { }), ); - expect(profile.samples[0].stack_id).toBe(0); - expect(profile.samples[1].stack_id).toBe(1); + expect(profile.samples[0]?.stack_id).toBe(0); + expect(profile.samples[1]?.stack_id).toBe(1); - expect(profile.samples[0].elapsed_since_start_ns).toBe('0'); - expect(profile.samples[1].elapsed_since_start_ns).toBe((1 * 1e6).toFixed(0)); + expect(profile.samples[0]?.elapsed_since_start_ns).toBe('0'); + expect(profile.samples[1]?.elapsed_since_start_ns).toBe((1 * 1e6).toFixed(0)); }); it('assert frames has no holes', () => { @@ -181,7 +181,7 @@ describe('convertJSSelfProfileToSampledFormat', () => { expect(profile.stacks.length).toBe(2); expect(profile.samples.length).toBe(3); - expect(profile.samples[0].stack_id).toEqual(profile.samples[2].stack_id); - expect(profile.stacks[profile.samples[0].stack_id]).toEqual([]); + expect(profile.samples[0]?.stack_id).toEqual(profile.samples[2]?.stack_id); + expect(profile.stacks[profile.samples[0]!.stack_id]).toEqual([]); }); }); diff --git a/packages/browser/test/unit/sdk.test.ts b/packages/browser/test/unit/sdk.test.ts index b0af70fcd652..22c220e7d0f1 100644 --- a/packages/browser/test/unit/sdk.test.ts +++ b/packages/browser/test/unit/sdk.test.ts @@ -69,8 +69,8 @@ describe('init', () => { init(options); - expect(DEFAULT_INTEGRATIONS[0].setupOnce as jest.Mock).toHaveBeenCalledTimes(1); - expect(DEFAULT_INTEGRATIONS[1].setupOnce as jest.Mock).toHaveBeenCalledTimes(1); + expect(DEFAULT_INTEGRATIONS[0]!.setupOnce as jest.Mock).toHaveBeenCalledTimes(1); + expect(DEFAULT_INTEGRATIONS[1]!.setupOnce as jest.Mock).toHaveBeenCalledTimes(1); }); test("doesn't install default integrations if told not to", () => { @@ -81,8 +81,8 @@ describe('init', () => { const options = getDefaultBrowserOptions({ dsn: PUBLIC_DSN, defaultIntegrations: false }); init(options); - expect(DEFAULT_INTEGRATIONS[0].setupOnce as jest.Mock).toHaveBeenCalledTimes(0); - expect(DEFAULT_INTEGRATIONS[1].setupOnce as jest.Mock).toHaveBeenCalledTimes(0); + expect(DEFAULT_INTEGRATIONS[0]!.setupOnce as jest.Mock).toHaveBeenCalledTimes(0); + expect(DEFAULT_INTEGRATIONS[1]!.setupOnce as jest.Mock).toHaveBeenCalledTimes(0); }); it('installs merged default integrations, with overrides provided through options', () => { @@ -100,10 +100,10 @@ describe('init', () => { init(options); // 'MockIntegration 1' should be overridden by the one with the same name provided through options - expect(DEFAULT_INTEGRATIONS[0].setupOnce as jest.Mock).toHaveBeenCalledTimes(0); - expect(DEFAULT_INTEGRATIONS[1].setupOnce as jest.Mock).toHaveBeenCalledTimes(1); - expect(integrations[0].setupOnce as jest.Mock).toHaveBeenCalledTimes(1); - expect(integrations[1].setupOnce as jest.Mock).toHaveBeenCalledTimes(1); + expect(DEFAULT_INTEGRATIONS[0]!.setupOnce as jest.Mock).toHaveBeenCalledTimes(0); + expect(DEFAULT_INTEGRATIONS[1]!.setupOnce as jest.Mock).toHaveBeenCalledTimes(1); + expect(integrations[0]!.setupOnce as jest.Mock).toHaveBeenCalledTimes(1); + expect(integrations[1]!.setupOnce as jest.Mock).toHaveBeenCalledTimes(1); }); it('installs integrations returned from a callback function', () => { @@ -124,9 +124,9 @@ describe('init', () => { init(options); - expect(DEFAULT_INTEGRATIONS[0].setupOnce as jest.Mock).toHaveBeenCalledTimes(1); + expect(DEFAULT_INTEGRATIONS[0]!.setupOnce as jest.Mock).toHaveBeenCalledTimes(1); expect(newIntegration.setupOnce as jest.Mock).toHaveBeenCalledTimes(1); - expect(DEFAULT_INTEGRATIONS[1].setupOnce as jest.Mock).toHaveBeenCalledTimes(0); + expect(DEFAULT_INTEGRATIONS[1]!.setupOnce as jest.Mock).toHaveBeenCalledTimes(0); }); describe('initialization error in browser extension', () => { diff --git a/packages/browser/test/unit/tracing/request.test.ts b/packages/browser/test/unit/tracing/request.test.ts index 521ff1327b01..47174c7d9016 100644 --- a/packages/browser/test/unit/tracing/request.test.ts +++ b/packages/browser/test/unit/tracing/request.test.ts @@ -87,7 +87,7 @@ describe('HTTPTimings', () => { const protocols = Object.keys(nextHopToNetworkVersion); for (const protocol of protocols) { - const expected: ProtocolInfo = nextHopToNetworkVersion[protocol]; + const expected = nextHopToNetworkVersion[protocol]!; expect(extractNetworkProtocol(protocol)).toMatchObject(expected); } }); diff --git a/packages/core/src/asyncContext/stackStrategy.ts b/packages/core/src/asyncContext/stackStrategy.ts index 7da6960e7801..a27afa1cae15 100644 --- a/packages/core/src/asyncContext/stackStrategy.ts +++ b/packages/core/src/asyncContext/stackStrategy.ts @@ -15,7 +15,7 @@ interface Layer { * This is an object that holds a stack of scopes. */ export class AsyncContextStack { - private readonly _stack: Layer[]; + private readonly _stack: [Layer, ...Layer[]]; private _isolationScope: ScopeInterface; public constructor(scope?: ScopeInterface, isolationScope?: ScopeInterface) { @@ -101,7 +101,7 @@ export class AsyncContextStack { * Returns the topmost scope layer in the order domain > local > process. */ public getStackTop(): Layer { - return this._stack[this._stack.length - 1]; + return this._stack[this._stack.length - 1] as Layer; } /** diff --git a/packages/core/src/baseclient.ts b/packages/core/src/baseclient.ts index d29c7f61af47..4b3796c295f3 100644 --- a/packages/core/src/baseclient.ts +++ b/packages/core/src/baseclient.ts @@ -382,8 +382,7 @@ export abstract class BaseClient implements Client { const key = `${reason}:${category}`; DEBUG_BUILD && logger.log(`Adding outcome: "${key}"`); - // The following works because undefined + 1 === NaN and NaN is falsy - this._outcomes[key] = this._outcomes[key] + 1 || 1; + this._outcomes[key] = (this._outcomes[key] || 0) + 1; } } @@ -523,8 +522,9 @@ export abstract class BaseClient implements Client { /** @inheritdoc */ public emit(hook: string, ...rest: unknown[]): void { - if (this._hooks[hook]) { - this._hooks[hook].forEach(callback => callback(...rest)); + const callbacks = this._hooks[hook]; + if (callbacks) { + callbacks.forEach(callback => callback(...rest)); } } @@ -837,12 +837,12 @@ export abstract class BaseClient implements Client { protected _clearOutcomes(): Outcome[] { const outcomes = this._outcomes; this._outcomes = {}; - return Object.keys(outcomes).map(key => { + return Object.entries(outcomes).map(([key, quantity]) => { const [reason, category] = key.split(':') as [EventDropReason, DataCategory]; return { reason, category, - quantity: outcomes[key], + quantity, }; }); } diff --git a/packages/core/src/envelope.ts b/packages/core/src/envelope.ts index dd0b814d0b07..d8b8c03e888b 100644 --- a/packages/core/src/envelope.ts +++ b/packages/core/src/envelope.ts @@ -101,7 +101,7 @@ export function createEventEnvelope( * * Takes an optional client and runs spans through `beforeSendSpan` if available. */ -export function createSpanEnvelope(spans: SentrySpan[], client?: Client): SpanEnvelope { +export function createSpanEnvelope(spans: [SentrySpan, ...SentrySpan[]], client?: Client): SpanEnvelope { function dscHasRequiredProps(dsc: Partial): dsc is DynamicSamplingContext { return !!dsc.trace_id && !!dsc.public_key; } diff --git a/packages/core/src/integration.ts b/packages/core/src/integration.ts index f39e09dae3ff..80a539bbe3d7 100644 --- a/packages/core/src/integration.ts +++ b/packages/core/src/integration.ts @@ -40,7 +40,7 @@ function filterDuplicates(integrations: Integration[]): Integration[] { integrationsByName[name] = currentInstance; }); - return Object.keys(integrationsByName).map(k => integrationsByName[k]); + return Object.values(integrationsByName); } /** Gets integrations to install */ @@ -69,9 +69,9 @@ export function getIntegrationsToSetup(options: Pick integration.name === 'Debug'); - if (debugIndex !== -1) { - const [debugInstance] = finalIntegrations.splice(debugIndex, 1); + const debugIndex = finalIntegrations.findIndex(integration => integration.name === 'Debug'); + if (debugIndex > -1) { + const [debugInstance] = finalIntegrations.splice(debugIndex, 1) as [Integration]; finalIntegrations.push(debugInstance); } @@ -158,17 +158,6 @@ export function addIntegration(integration: Integration): void { client.addIntegration(integration); } -// Polyfill for Array.findIndex(), which is not supported in ES5 -function findIndex(arr: T[], callback: (item: T) => boolean): number { - for (let i = 0; i < arr.length; i++) { - if (callback(arr[i]) === true) { - return i; - } - } - - return -1; -} - /** * Define an integration function that can be used to create an integration instance. * Note that this by design hides the implementation details of the integration, as they are considered internal. diff --git a/packages/core/src/integrations/dedupe.ts b/packages/core/src/integrations/dedupe.ts index 94eea57e68cb..32507f3a22ee 100644 --- a/packages/core/src/integrations/dedupe.ts +++ b/packages/core/src/integrations/dedupe.ts @@ -129,8 +129,10 @@ function _isSameStacktrace(currentEvent: Event, previousEvent: Event): boolean { // Otherwise, compare the two for (let i = 0; i < previousFrames.length; i++) { - const frameA = previousFrames[i]; - const frameB = currentFrames[i]; + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion + const frameA = previousFrames[i]!; + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion + const frameB = currentFrames[i]!; if ( frameA.filename !== frameB.filename || diff --git a/packages/core/src/integrations/zoderrors.ts b/packages/core/src/integrations/zoderrors.ts index 14a7da84d384..6dc583e54235 100644 --- a/packages/core/src/integrations/zoderrors.ts +++ b/packages/core/src/integrations/zoderrors.ts @@ -63,7 +63,9 @@ function formatIssueTitle(issue: ZodIssue): SingleLevelZodIssue { function formatIssueMessage(zodError: ZodError): string { const errorKeyMap = new Set(); for (const iss of zodError.issues) { - if (iss.path) errorKeyMap.add(iss.path[0]); + if (iss.path && iss.path[0]) { + errorKeyMap.add(iss.path[0]); + } } const errorKeys = Array.from(errorKeyMap); diff --git a/packages/core/src/metrics/metric-summary.ts b/packages/core/src/metrics/metric-summary.ts index c8672bed8bc6..279bea9e0808 100644 --- a/packages/core/src/metrics/metric-summary.ts +++ b/packages/core/src/metrics/metric-summary.ts @@ -28,11 +28,8 @@ export function getMetricSummaryJsonForSpan(span: Span): Record> = {}; for (const [, [exportKey, summary]] of storage) { - if (!output[exportKey]) { - output[exportKey] = []; - } - - output[exportKey].push(dropUndefinedKeys(summary)); + const arr = output[exportKey] || (output[exportKey] = []); + arr.push(dropUndefinedKeys(summary)); } return output; diff --git a/packages/core/src/sessionflusher.ts b/packages/core/src/sessionflusher.ts index 291864333119..02c93791007e 100644 --- a/packages/core/src/sessionflusher.ts +++ b/packages/core/src/sessionflusher.ts @@ -18,7 +18,7 @@ type ReleaseHealthAttributes = { */ export class SessionFlusher implements SessionFlusherLike { public readonly flushTimeout: number; - private _pendingAggregates: Record; + private _pendingAggregates: Map; private _sessionAttrs: ReleaseHealthAttributes; // Cast to any so that it can use Node.js timeout // eslint-disable-next-line @typescript-eslint/no-explicit-any @@ -29,7 +29,7 @@ export class SessionFlusher implements SessionFlusherLike { public constructor(client: Client, attrs: ReleaseHealthAttributes) { this._client = client; this.flushTimeout = 60; - this._pendingAggregates = {}; + this._pendingAggregates = new Map(); this._isEnabled = true; // Call to setInterval, so that flush is called every 60 seconds. @@ -48,15 +48,13 @@ export class SessionFlusher implements SessionFlusherLike { if (sessionAggregates.aggregates.length === 0) { return; } - this._pendingAggregates = {}; + this._pendingAggregates = new Map(); this._client.sendSession(sessionAggregates); } /** Massages the entries in `pendingAggregates` and returns aggregated sessions */ public getSessionAggregates(): SessionAggregates { - const aggregates: AggregationCounts[] = Object.keys(this._pendingAggregates).map((key: string) => { - return this._pendingAggregates[parseInt(key)]; - }); + const aggregates: AggregationCounts[] = Array.from(this._pendingAggregates.values()); const sessionAggregates: SessionAggregates = { attrs: this._sessionAttrs, @@ -100,13 +98,13 @@ export class SessionFlusher implements SessionFlusherLike { private _incrementSessionStatusCount(status: RequestSessionStatus, date: Date): number { // Truncate minutes and seconds on Session Started attribute to have one minute bucket keys const sessionStartedTrunc = new Date(date).setSeconds(0, 0); - this._pendingAggregates[sessionStartedTrunc] = this._pendingAggregates[sessionStartedTrunc] || {}; // corresponds to aggregated sessions in one specific minute bucket // for example, {"started":"2021-03-16T08:00:00.000Z","exited":4, "errored": 1} - const aggregationCounts: AggregationCounts = this._pendingAggregates[sessionStartedTrunc]; - if (!aggregationCounts.started) { - aggregationCounts.started = new Date(sessionStartedTrunc).toISOString(); + let aggregationCounts = this._pendingAggregates.get(sessionStartedTrunc); + if (!aggregationCounts) { + aggregationCounts = { started: new Date(sessionStartedTrunc).toISOString() }; + this._pendingAggregates.set(sessionStartedTrunc, aggregationCounts); } switch (status) { diff --git a/packages/core/src/transports/multiplexed.ts b/packages/core/src/transports/multiplexed.ts index d740f1fde3ec..6b58637025dd 100644 --- a/packages/core/src/transports/multiplexed.ts +++ b/packages/core/src/transports/multiplexed.ts @@ -135,14 +135,12 @@ export function makeMultiplexedTransport( .filter((t): t is [string, Transport] => !!t); // If we have no transports to send to, use the fallback transport - if (transports.length === 0) { - // Don't override the DSN in the header for the fallback transport. '' is falsy - transports.push(['', fallbackTransport]); - } + // Don't override the DSN in the header for the fallback transport. '' is falsy + const transportsWithFallback: [string, Transport][] = transports.length ? transports : [['', fallbackTransport]]; - const results = await Promise.all( - transports.map(([dsn, transport]) => transport.send(overrideDsn(envelope, dsn))), - ); + const results = (await Promise.all( + transportsWithFallback.map(([dsn, transport]) => transport.send(overrideDsn(envelope, dsn))), + )) as [TransportMakeRequestResponse, ...TransportMakeRequestResponse[]]; return results[0]; } diff --git a/packages/core/src/utils/prepareEvent.ts b/packages/core/src/utils/prepareEvent.ts index c60c6d21331b..71fef195dd31 100644 --- a/packages/core/src/utils/prepareEvent.ts +++ b/packages/core/src/utils/prepareEvent.ts @@ -179,25 +179,29 @@ export function applyDebugIds(event: Event, stackParser: StackParser): void { } // Build a map of filename -> debug_id - const filenameDebugIdMap = Object.keys(debugIdMap).reduce>((acc, debugIdStackTrace) => { - let parsedStack: StackFrame[]; - const cachedParsedStack = debugIdStackFramesCache.get(debugIdStackTrace); - if (cachedParsedStack) { - parsedStack = cachedParsedStack; - } else { - parsedStack = stackParser(debugIdStackTrace); - debugIdStackFramesCache.set(debugIdStackTrace, parsedStack); - } + const filenameDebugIdMap = Object.entries(debugIdMap).reduce>( + (acc, [debugIdStackTrace, debugIdValue]) => { + let parsedStack: StackFrame[]; + const cachedParsedStack = debugIdStackFramesCache.get(debugIdStackTrace); + if (cachedParsedStack) { + parsedStack = cachedParsedStack; + } else { + parsedStack = stackParser(debugIdStackTrace); + debugIdStackFramesCache.set(debugIdStackTrace, parsedStack); + } - for (let i = parsedStack.length - 1; i >= 0; i--) { - const stackFrame = parsedStack[i]; - if (stackFrame.filename) { - acc[stackFrame.filename] = debugIdMap[debugIdStackTrace]; - break; + for (let i = parsedStack.length - 1; i >= 0; i--) { + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion + const stackFrame = parsedStack[i]!; + if (stackFrame.filename) { + acc[stackFrame.filename] = debugIdValue; + break; + } } - } - return acc; - }, {}); + return acc; + }, + {}, + ); try { // eslint-disable-next-line @typescript-eslint/no-non-null-assertion @@ -247,11 +251,11 @@ export function applyDebugMeta(event: Event): void { event.debug_meta = event.debug_meta || {}; event.debug_meta.images = event.debug_meta.images || []; const images = event.debug_meta.images; - Object.keys(filenameDebugIdMap).forEach(filename => { + Object.entries(filenameDebugIdMap).forEach(([filename, debug_id]) => { images.push({ type: 'sourcemap', code_file: filename, - debug_id: filenameDebugIdMap[filename], + debug_id, }); }); } diff --git a/packages/core/test/lib/base.test.ts b/packages/core/test/lib/base.test.ts index 1ee834e0d8ae..d24dd42cb1c9 100644 --- a/packages/core/test/lib/base.test.ts +++ b/packages/core/test/lib/base.test.ts @@ -644,7 +644,7 @@ describe('BaseClient', () => { client.captureEvent({ message: 'message' }, undefined, scope); expect(TestClient.instance!.event!.breadcrumbs).toHaveLength(1); - expect(TestClient.instance!.event!.breadcrumbs![0].message).toEqual('2'); + expect(TestClient.instance!.event!.breadcrumbs![0]?.message).toEqual('2'); }); test('adds context data', () => { diff --git a/packages/core/test/lib/envelope.test.ts b/packages/core/test/lib/envelope.test.ts index 5652f48a545d..74e764c1d938 100644 --- a/packages/core/test/lib/envelope.test.ts +++ b/packages/core/test/lib/envelope.test.ts @@ -109,7 +109,7 @@ describe('createSpanEnvelope', () => { const spanEnvelope = createSpanEnvelope([span]); - const spanItem = spanEnvelope[1][0][1]; + const spanItem = spanEnvelope[1]?.[0]?.[1]; expect(spanItem).toEqual({ data: { 'sentry.origin': 'manual', @@ -119,7 +119,7 @@ describe('createSpanEnvelope', () => { is_segment: true, origin: 'manual', span_id: expect.stringMatching(/^[0-9a-f]{16}$/), - segment_id: spanItem.segment_id, + segment_id: spanItem?.segment_id, start_timestamp: 1, timestamp: 2, trace_id: expect.stringMatching(/^[0-9a-f]{32}$/), @@ -200,7 +200,7 @@ describe('createSpanEnvelope', () => { expect(beforeSendSpan).toHaveBeenCalled(); - const spanItem = spanEnvelope[1][0][1]; + const spanItem = spanEnvelope[1]?.[0]?.[1]; expect(spanItem).toEqual({ data: { 'sentry.origin': 'manual', @@ -209,7 +209,7 @@ describe('createSpanEnvelope', () => { is_segment: true, origin: 'manual', span_id: expect.stringMatching(/^[0-9a-f]{16}$/), - segment_id: spanItem.segment_id, + segment_id: spanItem?.segment_id, start_timestamp: 1, timestamp: 2, trace_id: expect.stringMatching(/^[0-9a-f]{32}$/), @@ -235,7 +235,7 @@ describe('createSpanEnvelope', () => { expect(beforeSendSpan).toHaveBeenCalled(); - const spanItem = spanEnvelope[1][0][1]; + const spanItem = spanEnvelope[1]?.[0]?.[1]; expect(spanItem).toEqual({ data: { 'sentry.origin': 'manual', @@ -244,7 +244,7 @@ describe('createSpanEnvelope', () => { is_segment: true, origin: 'manual', span_id: expect.stringMatching(/^[0-9a-f]{16}$/), - segment_id: spanItem.segment_id, + segment_id: spanItem?.segment_id, start_timestamp: 1, timestamp: 2, trace_id: expect.stringMatching(/^[0-9a-f]{32}$/), diff --git a/packages/core/test/lib/feedback.test.ts b/packages/core/test/lib/feedback.test.ts index e67521bfa2f8..cd9e99e43bdb 100644 --- a/packages/core/test/lib/feedback.test.ts +++ b/packages/core/test/lib/feedback.test.ts @@ -187,7 +187,7 @@ describe('captureFeedback', () => { const [feedbackEnvelope] = mockTransport.mock.calls; expect(feedbackEnvelope).toHaveLength(1); - expect(feedbackEnvelope[0]).toEqual([ + expect(feedbackEnvelope![0]).toEqual([ { event_id: eventId, sent_at: expect.any(String), diff --git a/packages/core/test/lib/hint.test.ts b/packages/core/test/lib/hint.test.ts index 5c7cf950a44e..c5a31a944663 100644 --- a/packages/core/test/lib/hint.test.ts +++ b/packages/core/test/lib/hint.test.ts @@ -35,7 +35,7 @@ describe('Hint', () => { const client = new TestClient(options); client.captureEvent({}); - const [, hint] = sendEvent.mock.calls[0]; + const [, hint] = sendEvent.mock.calls[0]!; expect(hint).toMatchObject({ attachments: [{ filename: 'another.file', data: 'more text' }] }); }); @@ -53,7 +53,7 @@ describe('Hint', () => { const client = new TestClient(options); client.captureEvent({}, { attachments: [{ filename: 'some-file.txt', data: 'Hello' }] }); - const [, hint] = sendEvent.mock.calls[0]; + const [, hint] = sendEvent.mock.calls[0]!; expect(hint).toMatchObject({ attachments: [ { filename: 'some-file.txt', data: 'Hello' }, @@ -79,7 +79,7 @@ describe('Hint', () => { { attachments: [{ filename: 'some-file.txt', data: 'Hello' }] }, ); - const [, hint] = sendEvent.mock.calls[0]; + const [, hint] = sendEvent.mock.calls[0]!; expect(hint).toMatchObject({ attachments: [ { filename: 'some-file.txt', data: 'Hello' }, @@ -99,7 +99,7 @@ describe('Hint', () => { initAndBind(TestClient, options); captureEvent({}); - const [, hint] = sendEvent.mock.calls[0]; + const [, hint] = sendEvent.mock.calls[0]!; expect(hint?.attachments).toEqual([{ filename: 'integration.file', data: 'great content!' }]); }); @@ -113,7 +113,7 @@ describe('Hint', () => { captureEvent({}, { attachments: [{ filename: 'some-file.txt', data: 'Hello' }] }); - const [, hint] = sendEvent.mock.calls[0]; + const [, hint] = sendEvent.mock.calls[0]!; expect(hint?.attachments).toEqual([ { filename: 'some-file.txt', data: 'Hello' }, { filename: 'scope.file', data: 'great content!' }, diff --git a/packages/core/test/lib/integrations/captureconsole.test.ts b/packages/core/test/lib/integrations/captureconsole.test.ts index 66dd16c514b5..ce913642b123 100644 --- a/packages/core/test/lib/integrations/captureconsole.test.ts +++ b/packages/core/test/lib/integrations/captureconsole.test.ts @@ -328,7 +328,7 @@ describe('CaptureConsole setup', () => { expect(captureException).toHaveBeenCalledTimes(1); expect(mockScope.addEventProcessor).toHaveBeenCalledTimes(1); - expect(someEvent.exception?.values?.[0].mechanism).toEqual({ + expect(someEvent.exception?.values?.[0]?.mechanism).toEqual({ handled: false, type: 'console', }); diff --git a/packages/core/test/lib/integrations/debug.test.ts b/packages/core/test/lib/integrations/debug.test.ts index 49ed0d2d341a..c391d3057f0e 100644 --- a/packages/core/test/lib/integrations/debug.test.ts +++ b/packages/core/test/lib/integrations/debug.test.ts @@ -21,7 +21,7 @@ function testEventLogged( expect(callbacks.length).toEqual(1); if (testEvent) { - callbacks[0](testEvent, testEventHint); + callbacks[0]?.(testEvent, testEventHint); } } diff --git a/packages/core/test/lib/integrations/dedupe.test.ts b/packages/core/test/lib/integrations/dedupe.test.ts index 540b07c53b79..9478f41a6492 100644 --- a/packages/core/test/lib/integrations/dedupe.test.ts +++ b/packages/core/test/lib/integrations/dedupe.test.ts @@ -80,14 +80,14 @@ describe('Dedupe', () => { const eventA = clone(messageEvent); const eventB = clone(messageEvent); eventB.message = 'EvilMorty'; - eventB.exception.values[0].value = 'EvilMorty'; + eventB.exception.values[0]!.value = 'EvilMorty'; expect(_shouldDropEvent(eventA, eventB)).toBe(false); }); it('should not drop if events have same messages, but different stacktraces', () => { const eventA = clone(messageEvent); const eventB = clone(messageEvent); - eventB.exception.values[0].stacktrace.frames[0].colno = 1337; + eventB.exception.values[0]!.stacktrace.frames[0]!.colno = 1337; expect(_shouldDropEvent(eventA, eventB)).toBe(false); }); @@ -131,21 +131,21 @@ describe('Dedupe', () => { it('should not drop if types are different', () => { const eventA = clone(exceptionEvent); const eventB = clone(exceptionEvent); - eventB.exception.values[0].type = 'TypeError'; + eventB.exception.values[0]!.type = 'TypeError'; expect(_shouldDropEvent(eventA, eventB)).toBe(false); }); it('should not drop if values are different', () => { const eventA = clone(exceptionEvent); const eventB = clone(exceptionEvent); - eventB.exception.values[0].value = 'Expected number, got string'; + eventB.exception.values[0]!.value = 'Expected number, got string'; expect(_shouldDropEvent(eventA, eventB)).toBe(false); }); it('should not drop if stacktraces are different', () => { const eventA = clone(exceptionEvent); const eventB = clone(exceptionEvent); - eventB.exception.values[0].stacktrace.frames[0].colno = 1337; + eventB.exception.values[0]!.stacktrace.frames[0]!.colno = 1337; expect(_shouldDropEvent(eventA, eventB)).toBe(false); }); diff --git a/packages/core/test/lib/integrations/extraerrordata.test.ts b/packages/core/test/lib/integrations/extraerrordata.test.ts index c5ffd68c1405..c1f59e984aae 100644 --- a/packages/core/test/lib/integrations/extraerrordata.test.ts +++ b/packages/core/test/lib/integrations/extraerrordata.test.ts @@ -217,7 +217,7 @@ describe('ExtraErrorData()', () => { it('captures Error causes when captureErrorCause = true (default)', () => { // Error.cause is only available from node 16 upwards - const nodeMajorVersion = parseInt(process.versions.node.split('.')[0]); + const nodeMajorVersion = parseInt(process.versions.node.split('.')[0]!); if (nodeMajorVersion < 16) { return; } @@ -246,7 +246,7 @@ describe('ExtraErrorData()', () => { it("doesn't capture Error causes when captureErrorCause != true", () => { // Error.cause is only available from node 16 upwards - const nodeMajorVersion = parseInt(process.versions.node.split('.')[0]); + const nodeMajorVersion = parseInt(process.versions.node.split('.')[0]!); if (nodeMajorVersion < 16) { return; } diff --git a/packages/core/test/lib/integrations/metadata.test.ts b/packages/core/test/lib/integrations/metadata.test.ts index 7f53ce090275..2fb06bd6081d 100644 --- a/packages/core/test/lib/integrations/metadata.test.ts +++ b/packages/core/test/lib/integrations/metadata.test.ts @@ -29,7 +29,7 @@ describe('ModuleMetadata integration', () => { integrations: [moduleMetadataIntegration()], beforeSend: (event, _hint) => { // copy the frames since reverse in in-place - const lastFrame = [...(event.exception?.values?.[0].stacktrace?.frames || [])].reverse()[0]; + const lastFrame = [...(event.exception?.values?.[0]?.stacktrace?.frames || [])].reverse()[0]; // Ensure module_metadata is populated in beforeSend callback expect(lastFrame?.module_metadata).toEqual({ team: 'frontend' }); return event; diff --git a/packages/core/test/lib/integrations/requestdata.test.ts b/packages/core/test/lib/integrations/requestdata.test.ts index 19659333b5b1..648d47e4faf5 100644 --- a/packages/core/test/lib/integrations/requestdata.test.ts +++ b/packages/core/test/lib/integrations/requestdata.test.ts @@ -62,7 +62,7 @@ describe('`RequestData` integration', () => { void requestDataEventProcessor(event, {}); - const passedOptions = addRequestDataToEventSpy.mock.calls[0][2]; + const passedOptions = addRequestDataToEventSpy.mock.calls[0]?.[2]; expect(passedOptions?.include).toEqual(expect.objectContaining({ ip: false, user: true })); }); @@ -72,7 +72,7 @@ describe('`RequestData` integration', () => { void requestDataEventProcessor(event, {}); - const passedOptions = addRequestDataToEventSpy.mock.calls[0][2]; + const passedOptions = addRequestDataToEventSpy.mock.calls[0]?.[2]; expect(passedOptions?.include).toEqual(expect.objectContaining({ transaction: 'path' })); }); @@ -84,7 +84,7 @@ describe('`RequestData` integration', () => { void requestDataEventProcessor(event, {}); - const passedOptions = addRequestDataToEventSpy.mock.calls[0][2]; + const passedOptions = addRequestDataToEventSpy.mock.calls[0]?.[2]; expect(passedOptions?.include?.request).toEqual(expect.arrayContaining(['data'])); expect(passedOptions?.include?.request).not.toEqual(expect.arrayContaining(['cookies'])); @@ -97,7 +97,7 @@ describe('`RequestData` integration', () => { void requestDataEventProcessor(event, {}); - const passedOptions = addRequestDataToEventSpy.mock.calls[0][2]; + const passedOptions = addRequestDataToEventSpy.mock.calls[0]?.[2]; expect(passedOptions?.include?.user).toEqual(expect.arrayContaining(['id'])); expect(passedOptions?.include?.user).not.toEqual(expect.arrayContaining(['email'])); diff --git a/packages/core/test/lib/integrations/rewriteframes.test.ts b/packages/core/test/lib/integrations/rewriteframes.test.ts index 4c30de3a42a6..f3ea865b459d 100644 --- a/packages/core/test/lib/integrations/rewriteframes.test.ts +++ b/packages/core/test/lib/integrations/rewriteframes.test.ts @@ -114,15 +114,15 @@ describe('RewriteFrames', () => { it('transforms exceptionEvent frames', () => { const event = rewriteFrames.processEvent?.(exceptionEvent, {}, {} as any) as Event; - expect(event.exception!.values![0].stacktrace!.frames![0].filename).toEqual('app:///file1.js'); - expect(event.exception!.values![0].stacktrace!.frames![1].filename).toEqual('app:///file2.js'); + expect(event.exception!.values![0]?.stacktrace!.frames![0]?.filename).toEqual('app:///file1.js'); + expect(event.exception!.values![0]?.stacktrace!.frames![1]?.filename).toEqual('app:///file2.js'); }); it('ignore exception without StackTrace', () => { // @ts-expect-error Validates that the Stacktrace does not exist before validating the test. - expect(exceptionWithoutStackTrace.exception?.values[0].stacktrace).toEqual(undefined); + expect(exceptionWithoutStackTrace.exception?.values[0]?.stacktrace).toEqual(undefined); const event = rewriteFrames.processEvent?.(exceptionWithoutStackTrace, {}, {} as any) as Event; - expect(event.exception!.values![0].stacktrace).toEqual(undefined); + expect(event.exception!.values![0]?.stacktrace).toEqual(undefined); }); }); @@ -135,8 +135,8 @@ describe('RewriteFrames', () => { it('transforms exceptionEvent frames', () => { const event = rewriteFrames.processEvent?.(exceptionEvent, {}, {} as any) as Event; - expect(event.exception!.values![0].stacktrace!.frames![0].filename).toEqual('foobar/file1.js'); - expect(event.exception!.values![0].stacktrace!.frames![1].filename).toEqual('foobar/file2.js'); + expect(event.exception!.values![0]?.stacktrace!.frames![0]?.filename).toEqual('foobar/file1.js'); + expect(event.exception!.values![0]?.stacktrace!.frames![1]?.filename).toEqual('foobar/file2.js'); }); }); @@ -147,26 +147,26 @@ describe('RewriteFrames', () => { it('transforms windowsExceptionEvent frames (C:\\)', () => { const event = rewriteFrames.processEvent?.(windowsExceptionEvent, {}, {} as any) as Event; - expect(event.exception!.values![0].stacktrace!.frames![0].filename).toEqual('app:///file1.js'); - expect(event.exception!.values![0].stacktrace!.frames![1].filename).toEqual('app:///file2.js'); + expect(event.exception!.values![0]?.stacktrace!.frames![0]?.filename).toEqual('app:///file1.js'); + expect(event.exception!.values![0]?.stacktrace!.frames![1]?.filename).toEqual('app:///file2.js'); }); it('transforms windowsExceptionEvent frames with lower-case prefix (c:\\)', () => { const event = rewriteFrames.processEvent?.(windowsLowerCaseExceptionEvent, {}, {} as any) as Event; - expect(event.exception!.values![0].stacktrace!.frames![0].filename).toEqual('app:///file1.js'); - expect(event.exception!.values![0].stacktrace!.frames![1].filename).toEqual('app:///file2.js'); + expect(event.exception!.values![0]?.stacktrace!.frames![0]?.filename).toEqual('app:///file1.js'); + expect(event.exception!.values![0]?.stacktrace!.frames![1]?.filename).toEqual('app:///file2.js'); }); it('transforms windowsExceptionEvent frames with no prefix', () => { const event = rewriteFrames.processEvent?.(windowsExceptionEventWithoutPrefix, {}, {} as any) as Event; - expect(event.exception!.values![0].stacktrace!.frames![0].filename).toEqual('app:///file1.js'); - expect(event.exception!.values![0].stacktrace!.frames![1].filename).toEqual('app:///file2.js'); + expect(event.exception!.values![0]?.stacktrace!.frames![0]?.filename).toEqual('app:///file1.js'); + expect(event.exception!.values![0]?.stacktrace!.frames![1]?.filename).toEqual('app:///file2.js'); }); it('transforms windowsExceptionEvent frames with backslash prefix', () => { const event = rewriteFrames.processEvent?.(windowsExceptionEventWithBackslashPrefix, {}, {} as any) as Event; - expect(event.exception!.values![0].stacktrace!.frames![0].filename).toEqual('app:///file1.js'); - expect(event.exception!.values![0].stacktrace!.frames![1].filename).toEqual('app:///file2.js'); + expect(event.exception!.values![0]?.stacktrace!.frames![0]?.filename).toEqual('app:///file1.js'); + expect(event.exception!.values![0]?.stacktrace!.frames![1]?.filename).toEqual('app:///file2.js'); }); }); @@ -179,32 +179,32 @@ describe('RewriteFrames', () => { it('transforms exceptionEvent frames', () => { const event = rewriteFrames.processEvent?.(exceptionEvent, {}, {} as any) as Event; - expect(event.exception!.values![0].stacktrace!.frames![0].filename).toEqual('app:///src/app/file1.js'); - expect(event.exception!.values![0].stacktrace!.frames![1].filename).toEqual('app:///src/app/mo\\dule/file2.js'); + expect(event.exception!.values![0]?.stacktrace!.frames![0]?.filename).toEqual('app:///src/app/file1.js'); + expect(event.exception!.values![0]?.stacktrace!.frames![1]?.filename).toEqual('app:///src/app/mo\\dule/file2.js'); }); it('transforms windowsExceptionEvent frames', () => { const event = rewriteFrames.processEvent?.(windowsExceptionEvent, {}, {} as any) as Event; - expect(event.exception!.values![0].stacktrace!.frames![0].filename).toEqual('app:///src/app/file1.js'); - expect(event.exception!.values![0].stacktrace!.frames![1].filename).toEqual('app:///src/app/file2.js'); + expect(event.exception!.values![0]?.stacktrace!.frames![0]?.filename).toEqual('app:///src/app/file1.js'); + expect(event.exception!.values![0]?.stacktrace!.frames![1]?.filename).toEqual('app:///src/app/file2.js'); }); it('transforms windowsExceptionEvent lower-case prefix frames', () => { const event = rewriteFrames.processEvent?.(windowsLowerCaseExceptionEvent, {}, {} as any) as Event; - expect(event.exception!.values![0].stacktrace!.frames![0].filename).toEqual('app:///src/app/file1.js'); - expect(event.exception!.values![0].stacktrace!.frames![1].filename).toEqual('app:///src/app/file2.js'); + expect(event.exception!.values![0]?.stacktrace!.frames![0]?.filename).toEqual('app:///src/app/file1.js'); + expect(event.exception!.values![0]?.stacktrace!.frames![1]?.filename).toEqual('app:///src/app/file2.js'); }); it('transforms windowsExceptionEvent frames with no prefix', () => { const event = rewriteFrames.processEvent?.(windowsExceptionEventWithoutPrefix, {}, {} as any) as Event; - expect(event.exception!.values![0].stacktrace!.frames![0].filename).toEqual('app:///src/app/file1.js'); - expect(event.exception!.values![0].stacktrace!.frames![1].filename).toEqual('app:///src/app/file2.js'); + expect(event.exception!.values![0]?.stacktrace!.frames![0]?.filename).toEqual('app:///src/app/file1.js'); + expect(event.exception!.values![0]?.stacktrace!.frames![1]?.filename).toEqual('app:///src/app/file2.js'); }); it('transforms windowsExceptionEvent frames with backslash prefix', () => { const event = rewriteFrames.processEvent?.(windowsExceptionEventWithBackslashPrefix, {}, {} as any) as Event; - expect(event.exception!.values![0].stacktrace!.frames![0].filename).toEqual('app:///src/app/file1.js'); - expect(event.exception!.values![0].stacktrace!.frames![1].filename).toEqual('app:///src/app/file2.js'); + expect(event.exception!.values![0]?.stacktrace!.frames![0]?.filename).toEqual('app:///src/app/file1.js'); + expect(event.exception!.values![0]?.stacktrace!.frames![1]?.filename).toEqual('app:///src/app/file2.js'); }); }); @@ -220,10 +220,10 @@ describe('RewriteFrames', () => { it('transforms exceptionEvent frames', () => { const event = rewriteFrames.processEvent?.(exceptionEvent, {}, {} as any) as Event; - expect(event.exception!.values![0].stacktrace!.frames![0].filename).toEqual('/www/src/app/file1.js'); - expect(event.exception!.values![0].stacktrace!.frames![0].function).toEqual('whoops'); - expect(event.exception!.values![0].stacktrace!.frames![1].filename).toEqual('/www/src/app/mo\\dule/file2.js'); - expect(event.exception!.values![0].stacktrace!.frames![1].function).toEqual('whoops'); + expect(event.exception!.values![0]?.stacktrace!.frames![0]?.filename).toEqual('/www/src/app/file1.js'); + expect(event.exception!.values![0]?.stacktrace!.frames![0]?.function).toEqual('whoops'); + expect(event.exception!.values![0]?.stacktrace!.frames![1]?.filename).toEqual('/www/src/app/mo\\dule/file2.js'); + expect(event.exception!.values![0]?.stacktrace!.frames![1]?.function).toEqual('whoops'); }); }); @@ -232,14 +232,14 @@ describe('RewriteFrames', () => { rewriteFrames = rewriteFramesIntegration(); const event = rewriteFrames.processEvent?.(multipleStacktracesEvent, {}, {} as any) as Event; // first stacktrace - expect(event.exception!.values![0].stacktrace!.frames![0].filename).toEqual('app:///file1.js'); - expect(event.exception!.values![0].stacktrace!.frames![1].filename).toEqual('app:///file2.js'); + expect(event.exception!.values![0]?.stacktrace!.frames![0]?.filename).toEqual('app:///file1.js'); + expect(event.exception!.values![0]?.stacktrace!.frames![1]?.filename).toEqual('app:///file2.js'); // second stacktrace - expect(event.exception!.values![1].stacktrace!.frames![0].filename).toEqual('app:///file3.js'); - expect(event.exception!.values![1].stacktrace!.frames![1].filename).toEqual('app:///file4.js'); + expect(event.exception!.values![1]?.stacktrace!.frames![0]?.filename).toEqual('app:///file3.js'); + expect(event.exception!.values![1]?.stacktrace!.frames![1]?.filename).toEqual('app:///file4.js'); // third stacktrace - expect(event.exception!.values![2].stacktrace!.frames![0].filename).toEqual('app:///file5.js'); - expect(event.exception!.values![2].stacktrace!.frames![1].filename).toEqual('app:///file6.js'); + expect(event.exception!.values![2]?.stacktrace!.frames![0]?.filename).toEqual('app:///file5.js'); + expect(event.exception!.values![2]?.stacktrace!.frames![1]?.filename).toEqual('app:///file6.js'); }); it('with custom root', () => { @@ -248,14 +248,14 @@ describe('RewriteFrames', () => { }); const event = rewriteFrames.processEvent?.(multipleStacktracesEvent, {}, {} as any) as Event; // first stacktrace - expect(event.exception!.values![0].stacktrace!.frames![0].filename).toEqual('app:///src/app/file1.js'); - expect(event.exception!.values![0].stacktrace!.frames![1].filename).toEqual('app:///src/app/mo\\dule/file2.js'); + expect(event.exception!.values![0]?.stacktrace!.frames![0]?.filename).toEqual('app:///src/app/file1.js'); + expect(event.exception!.values![0]?.stacktrace!.frames![1]?.filename).toEqual('app:///src/app/mo\\dule/file2.js'); // second stacktrace - expect(event.exception!.values![1].stacktrace!.frames![0].filename).toEqual('app:///src/app/file3.js'); - expect(event.exception!.values![1].stacktrace!.frames![1].filename).toEqual('app:///src/app/mo\\dule/file4.js'); + expect(event.exception!.values![1]?.stacktrace!.frames![0]?.filename).toEqual('app:///src/app/file3.js'); + expect(event.exception!.values![1]?.stacktrace!.frames![1]?.filename).toEqual('app:///src/app/mo\\dule/file4.js'); // third stacktrace - expect(event.exception!.values![2].stacktrace!.frames![0].filename).toEqual('app:///src/app/file5.js'); - expect(event.exception!.values![2].stacktrace!.frames![1].filename).toEqual('app:///src/app/mo\\dule/file6.js'); + expect(event.exception!.values![2]?.stacktrace!.frames![0]?.filename).toEqual('app:///src/app/file5.js'); + expect(event.exception!.values![2]?.stacktrace!.frames![1]?.filename).toEqual('app:///src/app/mo\\dule/file6.js'); }); it('with custom iteratee', () => { @@ -267,20 +267,20 @@ describe('RewriteFrames', () => { }); const event = rewriteFrames.processEvent?.(multipleStacktracesEvent, {}, {} as any) as Event; // first stacktrace - expect(event.exception!.values![0].stacktrace!.frames![0].filename).toEqual('/www/src/app/file1.js'); - expect(event.exception!.values![0].stacktrace!.frames![0].function).toEqual('whoops'); - expect(event.exception!.values![0].stacktrace!.frames![1].filename).toEqual('/www/src/app/mo\\dule/file2.js'); - expect(event.exception!.values![0].stacktrace!.frames![1].function).toEqual('whoops'); + expect(event.exception!.values![0]?.stacktrace!.frames![0]?.filename).toEqual('/www/src/app/file1.js'); + expect(event.exception!.values![0]?.stacktrace!.frames![0]?.function).toEqual('whoops'); + expect(event.exception!.values![0]?.stacktrace!.frames![1]?.filename).toEqual('/www/src/app/mo\\dule/file2.js'); + expect(event.exception!.values![0]?.stacktrace!.frames![1]?.function).toEqual('whoops'); // second stacktrace - expect(event.exception!.values![1].stacktrace!.frames![0].filename).toEqual('/www/src/app/file3.js'); - expect(event.exception!.values![1].stacktrace!.frames![0].function).toEqual('whoops'); - expect(event.exception!.values![1].stacktrace!.frames![1].filename).toEqual('/www/src/app/mo\\dule/file4.js'); - expect(event.exception!.values![1].stacktrace!.frames![1].function).toEqual('whoops'); + expect(event.exception!.values![1]?.stacktrace!.frames![0]?.filename).toEqual('/www/src/app/file3.js'); + expect(event.exception!.values![1]?.stacktrace!.frames![0]?.function).toEqual('whoops'); + expect(event.exception!.values![1]?.stacktrace!.frames![1]?.filename).toEqual('/www/src/app/mo\\dule/file4.js'); + expect(event.exception!.values![1]?.stacktrace!.frames![1]?.function).toEqual('whoops'); // third stacktrace - expect(event.exception!.values![2].stacktrace!.frames![0].filename).toEqual('/www/src/app/file5.js'); - expect(event.exception!.values![2].stacktrace!.frames![0].function).toEqual('whoops'); - expect(event.exception!.values![2].stacktrace!.frames![1].filename).toEqual('/www/src/app/mo\\dule/file6.js'); - expect(event.exception!.values![2].stacktrace!.frames![1].function).toEqual('whoops'); + expect(event.exception!.values![2]?.stacktrace!.frames![0]?.filename).toEqual('/www/src/app/file5.js'); + expect(event.exception!.values![2]?.stacktrace!.frames![0]?.function).toEqual('whoops'); + expect(event.exception!.values![2]?.stacktrace!.frames![1]?.filename).toEqual('/www/src/app/mo\\dule/file6.js'); + expect(event.exception!.values![2]?.stacktrace!.frames![1]?.function).toEqual('whoops'); }); }); diff --git a/packages/core/test/lib/integrations/zoderrrors.test.ts b/packages/core/test/lib/integrations/zoderrrors.test.ts index 2eca38d5d2ab..33dc2609ac09 100644 --- a/packages/core/test/lib/integrations/zoderrrors.test.ts +++ b/packages/core/test/lib/integrations/zoderrrors.test.ts @@ -90,8 +90,8 @@ describe('applyZodErrorsToEvent()', () => { 'zoderror.issues': [ { ...issues[0], - path: issues[0].path.join('.'), - keys: JSON.stringify(issues[0].keys), + path: issues[0]?.path.join('.'), + keys: JSON.stringify(issues[0]?.keys), unionErrors: undefined, }, ], diff --git a/packages/core/test/lib/metadata.test.ts b/packages/core/test/lib/metadata.test.ts index 14d64efdf2c9..378bb51e6562 100644 --- a/packages/core/test/lib/metadata.test.ts +++ b/packages/core/test/lib/metadata.test.ts @@ -51,7 +51,7 @@ describe('Metadata', () => { it('is added and stripped from stack frames', () => { addMetadataToStackFrames(parser, event); - expect(event.exception?.values?.[0].stacktrace?.frames).toEqual([ + expect(event.exception?.values?.[0]?.stacktrace?.frames).toEqual([ { filename: '', function: 'new Promise', @@ -75,7 +75,7 @@ describe('Metadata', () => { stripMetadataFromStackFrames(event); - expect(event.exception?.values?.[0].stacktrace?.frames).toEqual([ + expect(event.exception?.values?.[0]?.stacktrace?.frames).toEqual([ { filename: '', function: 'new Promise', diff --git a/packages/core/test/lib/prepareEvent.test.ts b/packages/core/test/lib/prepareEvent.test.ts index 49dcc349f2ed..11c1ceca991c 100644 --- a/packages/core/test/lib/prepareEvent.test.ts +++ b/packages/core/test/lib/prepareEvent.test.ts @@ -53,18 +53,18 @@ describe('applyDebugIds', () => { applyDebugIds(event, stackParser); - expect(event.exception?.values?.[0].stacktrace?.frames).toContainEqual({ + expect(event.exception?.values?.[0]?.stacktrace?.frames).toContainEqual({ filename: 'filename1.js', debug_id: 'aaaaaaaa-aaaa-4aaa-aaaa-aaaaaaaaaa', }); - expect(event.exception?.values?.[0].stacktrace?.frames).toContainEqual({ + expect(event.exception?.values?.[0]?.stacktrace?.frames).toContainEqual({ filename: 'filename2.js', debug_id: 'bbbbbbbb-bbbb-4bbb-bbbb-bbbbbbbbbb', }); // expect not to contain an image for the stack frame that doesn't have a corresponding debug id - expect(event.exception?.values?.[0].stacktrace?.frames).not.toContainEqual( + expect(event.exception?.values?.[0]?.stacktrace?.frames).not.toContainEqual( expect.objectContaining({ filename3: 'filename3.js', debug_id: expect.any(String), @@ -72,7 +72,7 @@ describe('applyDebugIds', () => { ); // expect not to contain an image for the debug id mapping that isn't contained in the stack trace - expect(event.exception?.values?.[0].stacktrace?.frames).not.toContainEqual( + expect(event.exception?.values?.[0]?.stacktrace?.frames).not.toContainEqual( expect.objectContaining({ filename3: 'filename4.js', debug_id: 'cccccccc-cccc-4ccc-cccc-cccccccccc', @@ -102,7 +102,7 @@ describe('applyDebugMeta', () => { applyDebugMeta(event); - expect(event.exception?.values?.[0].stacktrace?.frames).toEqual([ + expect(event.exception?.values?.[0]?.stacktrace?.frames).toEqual([ { filename: 'filename1.js' }, { filename: 'filename2.js' }, { filename: 'filename1.js' }, diff --git a/packages/core/test/lib/scope.test.ts b/packages/core/test/lib/scope.test.ts index a58370a1b632..33c89a2e9eb5 100644 --- a/packages/core/test/lib/scope.test.ts +++ b/packages/core/test/lib/scope.test.ts @@ -827,7 +827,7 @@ describe('Scope', () => { scope.addBreadcrumb({ message: 'hello world' }, 100); - expect((scope as any)._breadcrumbs[0].message).toEqual('hello world'); + expect((scope as any)._breadcrumbs[0]?.message).toEqual('hello world'); }); test('adds a timestamp to new breadcrumbs', () => { @@ -835,7 +835,7 @@ describe('Scope', () => { scope.addBreadcrumb({ message: 'hello world' }, 100); - expect((scope as any)._breadcrumbs[0].timestamp).toEqual(expect.any(Number)); + expect((scope as any)._breadcrumbs[0]?.timestamp).toEqual(expect.any(Number)); }); test('overrides the `maxBreadcrumbs` defined in client options', () => { diff --git a/packages/core/test/lib/sdk.test.ts b/packages/core/test/lib/sdk.test.ts index ecc7d18483e5..3d0604c436de 100644 --- a/packages/core/test/lib/sdk.test.ts +++ b/packages/core/test/lib/sdk.test.ts @@ -32,8 +32,8 @@ describe('SDK', () => { ]; const options = getDefaultTestClientOptions({ dsn: PUBLIC_DSN, integrations }); initAndBind(TestClient, options); - expect((integrations[0].setupOnce as jest.Mock).mock.calls.length).toBe(1); - expect((integrations[1].setupOnce as jest.Mock).mock.calls.length).toBe(1); + expect((integrations[0]?.setupOnce as jest.Mock).mock.calls.length).toBe(1); + expect((integrations[1]?.setupOnce as jest.Mock).mock.calls.length).toBe(1); }); test('calls hooks in the correct order', () => { diff --git a/packages/core/test/lib/tracing/idleSpan.test.ts b/packages/core/test/lib/tracing/idleSpan.test.ts index 86d032488229..21f461dccbe1 100644 --- a/packages/core/test/lib/tracing/idleSpan.test.ts +++ b/packages/core/test/lib/tracing/idleSpan.test.ts @@ -139,7 +139,7 @@ describe('startIdleSpan', () => { ); expect(beforeSendTransaction).toHaveBeenCalledTimes(1); - const transaction = transactions[0]; + const transaction = transactions[0]!; expect(transaction.start_timestamp).toBe(baseTimeInSeconds); // It considers the end time of the span we added in beforeSpanEnd @@ -206,7 +206,7 @@ describe('startIdleSpan', () => { expect(cancelledSpan.isRecording()).toBe(false); expect(beforeSendTransaction).toHaveBeenCalledTimes(1); - const transaction = transactions[0]; + const transaction = transactions[0]!; // End time is based on idle time etc. const idleSpanEndTime = transaction.timestamp!; @@ -281,7 +281,7 @@ describe('startIdleSpan', () => { expect(cancelledSpan.isRecording()).toBe(false); expect(beforeSendTransaction).toHaveBeenCalledTimes(1); - const transaction = transactions[0]; + const transaction = transactions[0]!; // End time is based on idle time etc. const idleSpanEndTime = transaction.timestamp!; diff --git a/packages/core/test/lib/tracing/trace.test.ts b/packages/core/test/lib/tracing/trace.test.ts index 9399aa6b5a57..84b49034a229 100644 --- a/packages/core/test/lib/tracing/trace.test.ts +++ b/packages/core/test/lib/tracing/trace.test.ts @@ -137,9 +137,9 @@ describe('startSpan', () => { const spans = getSpanDescendants(_span!); expect(spans).toHaveLength(2); - expect(spanToJSON(spans[1]).description).toEqual('SELECT * from users'); - expect(spanToJSON(spans[1]).parent_span_id).toEqual(_span!.spanContext().spanId); - expect(spanToJSON(spans[1]).status).toEqual(isError ? 'internal_error' : undefined); + expect(spanToJSON(spans[1]!).description).toEqual('SELECT * from users'); + expect(spanToJSON(spans[1]!).parent_span_id).toEqual(_span!.spanContext().spanId); + expect(spanToJSON(spans[1]!).status).toEqual(isError ? 'internal_error' : undefined); }); it('allows for span to be mutated', async () => { @@ -166,7 +166,7 @@ describe('startSpan', () => { const spans = getSpanDescendants(_span!); expect(spans).toHaveLength(2); - expect(spanToJSON(spans[1]).op).toEqual('db.query'); + expect(spanToJSON(spans[1]!).op).toEqual('db.query'); }); it('correctly sets the span origin', async () => { @@ -315,7 +315,7 @@ describe('startSpan', () => { const outerTraceId = outerTransaction?.contexts?.trace?.trace_id; // The inner transaction should be a child of the last span of the outer transaction - const innerParentSpanId = outerTransaction?.spans?.[0].id; + const innerParentSpanId = outerTransaction?.spans?.[0]?.id; const innerSpanId = innerTransaction?.contexts?.trace?.span_id; expect(outerTraceId).toBeDefined(); @@ -717,7 +717,7 @@ describe('startSpanManual', () => { const outerTraceId = outerTransaction?.contexts?.trace?.trace_id; // The inner transaction should be a child of the last span of the outer transaction - const innerParentSpanId = outerTransaction?.spans?.[0].id; + const innerParentSpanId = outerTransaction?.spans?.[0]?.id; const innerSpanId = innerTransaction?.contexts?.trace?.span_id; expect(outerTraceId).toBeDefined(); @@ -1018,7 +1018,7 @@ describe('startInactiveSpan', () => { const outerTraceId = outerTransaction?.contexts?.trace?.trace_id; // The inner transaction should be a child of the last span of the outer transaction - const innerParentSpanId = outerTransaction?.spans?.[0].id; + const innerParentSpanId = outerTransaction?.spans?.[0]?.id; const innerSpanId = innerTransaction?.contexts?.trace?.span_id; expect(outerTraceId).toBeDefined(); diff --git a/packages/core/test/lib/transports/multiplexed.test.ts b/packages/core/test/lib/transports/multiplexed.test.ts index 59e71e31304f..4ddde08437f2 100644 --- a/packages/core/test/lib/transports/multiplexed.test.ts +++ b/packages/core/test/lib/transports/multiplexed.test.ts @@ -111,7 +111,7 @@ describe('makeMultiplexedTransport', () => { const makeTransport = makeMultiplexedTransport( createTestTransport((url, _, env) => { expect(url).toBe(DSN2_URL); - expect(env[0].dsn).toBe(DSN2); + expect(env[0]?.dsn).toBe(DSN2); }), () => [DSN2], ); @@ -127,7 +127,7 @@ describe('makeMultiplexedTransport', () => { createTestTransport((url, release, env) => { expect(url).toBe(DSN2_URL); expect(release).toBe('something@1.0.0'); - expect(env[0].dsn).toBe(DSN2); + expect(env[0]?.dsn).toBe(DSN2); }), () => [{ dsn: DSN2, release: 'something@1.0.0' }], ); @@ -143,7 +143,7 @@ describe('makeMultiplexedTransport', () => { createTestTransport((url, release, env) => { expect(url).toBe('http://google.com'); expect(release).toBe('something@1.0.0'); - expect(env[0].dsn).toBe(DSN2); + expect(env[0]?.dsn).toBe(DSN2); }), () => [{ dsn: DSN2, release: 'something@1.0.0' }], ); diff --git a/packages/core/test/lib/transports/offline.test.ts b/packages/core/test/lib/transports/offline.test.ts index 2368f61d5892..ea3fa13ffd69 100644 --- a/packages/core/test/lib/transports/offline.test.ts +++ b/packages/core/test/lib/transports/offline.test.ts @@ -311,7 +311,7 @@ describe('makeOfflineTransport', () => { // Create an envelope with a sent_at header very far in the past const env: EventEnvelope = [...ERROR_ENVELOPE]; - env[0].sent_at = new Date(2020, 1, 1).toISOString(); + env[0]!.sent_at = new Date(2020, 1, 1).toISOString(); const { getCalls, store } = createTestStore(ERROR_ENVELOPE); const { getSentEnvelopes, baseTransport } = createTestTransport({ statusCode: 200 }); @@ -327,8 +327,8 @@ describe('makeOfflineTransport', () => { // When it gets shifted out of the store, the sent_at header should be updated const envelopes = getSentEnvelopes().map(parseEnvelope) as EventEnvelope[]; - expect(envelopes[0][0]).toBeDefined(); - const sent_at = new Date(envelopes[0][0].sent_at); + expect(envelopes[0]?.[0]).toBeDefined(); + const sent_at = new Date(envelopes[0]![0]?.sent_at); expect(sent_at.getTime()).toBeGreaterThan(testStartTime.getTime()); }, @@ -403,9 +403,9 @@ describe('makeOfflineTransport', () => { const envelopes = getSentEnvelopes().map(parseEnvelope); // Ensure they're still in the correct order - expect((envelopes[0][1][0][1] as ErrorEvent).message).toEqual('1'); - expect((envelopes[1][1][0][1] as ErrorEvent).message).toEqual('2'); - expect((envelopes[2][1][0][1] as ErrorEvent).message).toEqual('3'); + expect((envelopes[0]?.[1]?.[0]?.[1] as ErrorEvent).message).toEqual('1'); + expect((envelopes[1]?.[1]?.[0]?.[1] as ErrorEvent).message).toEqual('2'); + expect((envelopes[2]?.[1]?.[0]?.[1] as ErrorEvent).message).toEqual('3'); }, START_DELAY + 2_000, ); diff --git a/packages/deno/src/integrations/normalizepaths.ts b/packages/deno/src/integrations/normalizepaths.ts index 1ea564a562ec..f984d5bebc33 100644 --- a/packages/deno/src/integrations/normalizepaths.ts +++ b/packages/deno/src/integrations/normalizepaths.ts @@ -21,22 +21,24 @@ function appRootFromErrorStack(error: Error): string | undefined { .filter(seg => seg !== ''), // remove empty segments ) as string[][]; - if (paths.length == 0) { + const firstPath = paths[0]; + + if (!firstPath) { return undefined; } if (paths.length == 1) { // Assume the single file is in the root - return dirname(paths[0].join('/')); + return dirname(firstPath.join('/')); } // Iterate over the paths and bail out when they no longer have a common root let i = 0; - while (paths[0][i] && paths.every(w => w[i] === paths[0][i])) { + while (firstPath[i] && paths.every(w => w[i] === firstPath[i])) { i++; } - return paths[0].slice(0, i).join('/'); + return firstPath.slice(0, i).join('/'); } function getCwd(): string | undefined { diff --git a/packages/deno/test/normalize.ts b/packages/deno/test/normalize.ts index 93f4250c00a8..2946ea8de153 100644 --- a/packages/deno/test/normalize.ts +++ b/packages/deno/test/normalize.ts @@ -151,9 +151,9 @@ function normalizeEvent(event: sentryTypes.Event): sentryTypes.Event { event.start_timestamp = 0; } - if (event.exception?.values?.[0].stacktrace?.frames) { + if (event.exception?.values?.[0]?.stacktrace?.frames) { // Exclude Deno frames since these may change between versions - event.exception.values[0].stacktrace.frames = event.exception.values[0].stacktrace.frames.filter( + event.exception.values[0]!.stacktrace.frames = event.exception.values[0]?.stacktrace.frames.filter( frame => !frame.filename?.includes('deno:') && !frame.filename?.startsWith('ext:'), ); } diff --git a/packages/ember/tests/helpers/setup-sentry.ts b/packages/ember/tests/helpers/setup-sentry.ts index db439d226dc2..a194d67fc662 100644 --- a/packages/ember/tests/helpers/setup-sentry.ts +++ b/packages/ember/tests/helpers/setup-sentry.ts @@ -33,7 +33,7 @@ export function setupSentryTest(hooks: NestedHooks): void { // @ts-expect-error this is fine QUnit.onError = function ({ message }: { message: string }) { - errorMessages.push(message.split('Error: ')[1]); + errorMessages.push(message.split('Error: ')[1]!); return true; }; @@ -48,7 +48,7 @@ export function setupSentryTest(hooks: NestedHooks): void { * Will collect errors when run via testem in cli */ window.onerror = error => { - errorMessages.push(error.toString().split('Error: ')[1]); + errorMessages.push(error.toString().split('Error: ')[1]!); }; }); diff --git a/packages/ember/tests/helpers/utils.ts b/packages/ember/tests/helpers/utils.ts index 77ac48ebb516..fb2736a43e13 100644 --- a/packages/ember/tests/helpers/utils.ts +++ b/packages/ember/tests/helpers/utils.ts @@ -55,7 +55,7 @@ export function assertSentryTransactions( }, ): void { const sentryTestEvents = getTestSentryTransactions(); - const event = sentryTestEvents[callNumber]; + const event = sentryTestEvents[callNumber]!; assert.ok(event, 'event exists'); assert.ok(event.spans, 'event has spans'); diff --git a/packages/feedback/src/core/sendFeedback.test.ts b/packages/feedback/src/core/sendFeedback.test.ts index e87573533952..195c208b448a 100644 --- a/packages/feedback/src/core/sendFeedback.test.ts +++ b/packages/feedback/src/core/sendFeedback.test.ts @@ -367,7 +367,7 @@ describe('sendFeedback', () => { const [feedbackEnvelope] = mockTransport.mock.calls; - expect(feedbackEnvelope[0]).toEqual([ + expect(feedbackEnvelope?.[0]).toEqual([ { event_id: eventId, sent_at: expect.any(String), diff --git a/packages/gatsby/test/sdk.test.ts b/packages/gatsby/test/sdk.test.ts index bbb77abf5af8..dd267ad5c2e1 100644 --- a/packages/gatsby/test/sdk.test.ts +++ b/packages/gatsby/test/sdk.test.ts @@ -22,9 +22,9 @@ describe('Initialize React SDK', () => { expect(sdkMetadata.name).toStrictEqual('sentry.javascript.gatsby'); expect(sdkMetadata.version).toBe(SDK_VERSION); expect(sdkMetadata.packages).toHaveLength(1); // Only Gatsby SDK - expect(sdkMetadata.packages[0].name).toStrictEqual('npm:@sentry/gatsby'); + expect(sdkMetadata.packages[0]?.name).toStrictEqual('npm:@sentry/gatsby'); // Explicit tests on the version fail when making new releases - expect(sdkMetadata.packages[0].version).toBeDefined(); + expect(sdkMetadata.packages[0]?.version).toBeDefined(); }); describe('Environment', () => { diff --git a/packages/google-cloud-serverless/src/integrations/google-cloud-grpc.ts b/packages/google-cloud-serverless/src/integrations/google-cloud-grpc.ts index 0b1aa11a0a9d..3b55fe99eeb8 100644 --- a/packages/google-cloud-serverless/src/integrations/google-cloud-grpc.ts +++ b/packages/google-cloud-serverless/src/integrations/google-cloud-grpc.ts @@ -125,5 +125,5 @@ function fillGrpcFunction(stub: Stub, serviceIdentifier: string, methodName: str /** Identifies service by its address */ function identifyService(servicePath: string): string { const match = servicePath.match(SERVICE_PATH_REGEX); - return match ? match[1] : servicePath; + return match && match[1] ? match[1] : servicePath; } diff --git a/packages/google-cloud-serverless/src/integrations/google-cloud-http.ts b/packages/google-cloud-serverless/src/integrations/google-cloud-http.ts index e33f016b204f..9ffad40fd7b6 100644 --- a/packages/google-cloud-serverless/src/integrations/google-cloud-http.ts +++ b/packages/google-cloud-serverless/src/integrations/google-cloud-http.ts @@ -65,5 +65,5 @@ function wrapRequestFunction(orig: RequestFunction): RequestFunction { /** Identifies service by its base url */ function identifyService(apiEndpoint: string): string { const match = apiEndpoint.match(/^https:\/\/(\w+)\.googleapis.com$/); - return match ? match[1] : apiEndpoint.replace(/^(http|https)?:\/\//, ''); + return match && match[1] ? match[1] : apiEndpoint.replace(/^(http|https)?:\/\//, ''); } diff --git a/packages/google-cloud-serverless/test/gcpfunction/events.test.ts b/packages/google-cloud-serverless/test/gcpfunction/events.test.ts index 9a3bf0d189dd..b149bd1764f1 100644 --- a/packages/google-cloud-serverless/test/gcpfunction/events.test.ts +++ b/packages/google-cloud-serverless/test/gcpfunction/events.test.ts @@ -247,7 +247,7 @@ describe('wrapEventFunction', () => { expect(evtProcessor).toBeInstanceOf(Function); // @ts-expect-error just mocking around... - expect(evtProcessor(event).exception.values[0].mechanism).toEqual({ + expect(evtProcessor(event).exception.values[0]?.mechanism).toEqual({ handled: false, type: 'generic', }); diff --git a/packages/google-cloud-serverless/test/gcpfunction/http.test.ts b/packages/google-cloud-serverless/test/gcpfunction/http.test.ts index e0793fb5691d..8b0168e61949 100644 --- a/packages/google-cloud-serverless/test/gcpfunction/http.test.ts +++ b/packages/google-cloud-serverless/test/gcpfunction/http.test.ts @@ -171,7 +171,7 @@ describe('GCPFunction', () => { await handleHttp(wrappedHandler); const initOptions = (mockInit as unknown as jest.SpyInstance).mock.calls[0]; - const defaultIntegrations = initOptions[0].defaultIntegrations.map((i: Integration) => i.name); + const defaultIntegrations = initOptions[0]?.defaultIntegrations.map((i: Integration) => i.name); expect(defaultIntegrations).toContain('RequestData'); diff --git a/packages/nextjs/src/client/routing/pagesRouterRoutingInstrumentation.ts b/packages/nextjs/src/client/routing/pagesRouterRoutingInstrumentation.ts index 3685f9590fbb..f6906a566050 100644 --- a/packages/nextjs/src/client/routing/pagesRouterRoutingInstrumentation.ts +++ b/packages/nextjs/src/client/routing/pagesRouterRoutingInstrumentation.ts @@ -193,7 +193,7 @@ function convertNextRouteToRegExp(route: string): RegExp { const routeParts = route.split('/'); let optionalCatchallWildcardRegex = ''; - if (routeParts[routeParts.length - 1].match(/^\[\[\.\.\..+\]\]$/)) { + if (routeParts[routeParts.length - 1]?.match(/^\[\[\.\.\..+\]\]$/)) { // If last route part has pattern "[[...xyz]]" we pop the latest route part to get rid of the required trailing // slash that would come before it if we didn't pop it. routeParts.pop(); diff --git a/packages/nextjs/src/common/devErrorSymbolicationEventProcessor.ts b/packages/nextjs/src/common/devErrorSymbolicationEventProcessor.ts index 0af455e8b2f7..6c37859a851d 100644 --- a/packages/nextjs/src/common/devErrorSymbolicationEventProcessor.ts +++ b/packages/nextjs/src/common/devErrorSymbolicationEventProcessor.ts @@ -146,7 +146,7 @@ export async function devErrorSymbolicationEventProcessor(event: Event, hint: Ev frames.map(frame => resolveStackFrame(frame, hint.originalException as Error)), ); - if (event.exception?.values?.[0].stacktrace?.frames) { + if (event.exception?.values?.[0]?.stacktrace?.frames) { event.exception.values[0].stacktrace.frames = event.exception.values[0].stacktrace.frames.map( (frame, i, frames) => { const resolvedFrame = resolvedFrames[frames.length - 1 - i]; diff --git a/packages/nextjs/src/config/webpack.ts b/packages/nextjs/src/config/webpack.ts index e529a2b47f57..eece7ff30305 100644 --- a/packages/nextjs/src/config/webpack.ts +++ b/packages/nextjs/src/config/webpack.ts @@ -529,7 +529,9 @@ function addFilesToWebpackEntryPoint( ); } - entryProperty[entryPointName] = newEntryPoint; + if (newEntryPoint) { + entryProperty[entryPointName] = newEntryPoint; + } } /** diff --git a/packages/nextjs/test/clientSdk.test.ts b/packages/nextjs/test/clientSdk.test.ts index ec090d631efd..67e6804f1a1b 100644 --- a/packages/nextjs/test/clientSdk.test.ts +++ b/packages/nextjs/test/clientSdk.test.ts @@ -105,7 +105,7 @@ describe('Client init()', () => { it('supports passing unrelated integrations through options', () => { init({ integrations: [breadcrumbsIntegration({ console: false })] }); - const reactInitOptions = reactInit.mock.calls[0][0] as ModifiedInitOptionsIntegrationArray; + const reactInitOptions = reactInit.mock.calls[0]![0] as ModifiedInitOptionsIntegrationArray; const installedBreadcrumbsIntegration = findIntegrationByName(reactInitOptions.integrations, 'Breadcrumbs'); expect(installedBreadcrumbsIntegration).toBeDefined(); diff --git a/packages/nextjs/test/config/mocks.ts b/packages/nextjs/test/config/mocks.ts index fbc3705486c2..5c27c743c9f9 100644 --- a/packages/nextjs/test/config/mocks.ts +++ b/packages/nextjs/test/config/mocks.ts @@ -66,7 +66,7 @@ global.console.warn = (...args: unknown[]) => { // `hideSourceMaps` because that would mean we couldn't test it easily and would muddy the waters of other tests. Note // that doing this here, as a side effect, only works because the tests which trigger this warning are the same tests // which need other mocks from this file. - if (typeof args[0] === 'string' && args[0].includes('your original code may be visible in browser devtools')) { + if (typeof args[0] === 'string' && args[0]?.includes('your original code may be visible in browser devtools')) { return; } diff --git a/packages/nextjs/test/integration/test/client/errorClick.test.ts b/packages/nextjs/test/integration/test/client/errorClick.test.ts index 85369b19e355..ad1b94463ed7 100644 --- a/packages/nextjs/test/integration/test/client/errorClick.test.ts +++ b/packages/nextjs/test/integration/test/client/errorClick.test.ts @@ -10,7 +10,7 @@ test('should capture error triggered on click', async ({ page }) => { getMultipleSentryEnvelopeRequests(page, 1, { envelopeType: 'event' }), ]); - expect(events[0].exception?.values?.[0]).toMatchObject({ + expect(events[0]?.exception?.values?.[0]).toMatchObject({ type: 'Error', value: 'Sentry Frontend Error', }); @@ -24,7 +24,7 @@ test('should have a non-url-encoded top frame in route with parameter', async ({ getMultipleSentryEnvelopeRequests(page, 1, { envelopeType: 'event' }), ]); - const frames = events[0]?.exception?.values?.[0].stacktrace?.frames; + const frames = events[0]?.exception?.values?.[0]?.stacktrace?.frames; expect(frames?.[frames.length - 1].filename).toMatch(/\/\[id\]\/errorClick-[a-f0-9]+\.js$/); }); @@ -37,7 +37,7 @@ test('should mark nextjs internal frames as `in_app`: false', async ({ page }) = getMultipleSentryEnvelopeRequests(page, 1, { envelopeType: 'event' }), ]); - const frames = events[0]?.exception?.values?.[0].stacktrace?.frames; + const frames = events[0]?.exception?.values?.[0]?.stacktrace?.frames; expect(frames).toContainEqual( expect.objectContaining({ diff --git a/packages/nextjs/test/integration/test/client/errorGlobal.test.ts b/packages/nextjs/test/integration/test/client/errorGlobal.test.ts index 09191094f49f..e31daac08be1 100644 --- a/packages/nextjs/test/integration/test/client/errorGlobal.test.ts +++ b/packages/nextjs/test/integration/test/client/errorGlobal.test.ts @@ -5,7 +5,7 @@ import { getMultipleSentryEnvelopeRequests } from './utils/helpers'; test('should capture a globally triggered event', async ({ page }) => { const event = await getMultipleSentryEnvelopeRequests(page, 1, { url: '/crashed', envelopeType: 'event' }); - expect(event[0].exception?.values?.[0]).toMatchObject({ + expect(event[0]?.exception?.values?.[0]).toMatchObject({ type: 'Error', value: 'Crashed', }); diff --git a/packages/nextjs/test/integration/test/client/tracingClientGetInitialProps.test.ts b/packages/nextjs/test/integration/test/client/tracingClientGetInitialProps.test.ts index 3f6cad97fe00..4f44d6762f46 100644 --- a/packages/nextjs/test/integration/test/client/tracingClientGetInitialProps.test.ts +++ b/packages/nextjs/test/integration/test/client/tracingClientGetInitialProps.test.ts @@ -19,7 +19,7 @@ test('should instrument `getInitialProps` for performance tracing', async ({ pag const nextDataTag = await page.waitForSelector('#__NEXT_DATA__', { state: 'attached' }); const nextDataTagValue = JSON.parse(await nextDataTag.evaluate(tag => (tag as HTMLElement).innerText)); - const traceId = transaction[0].contexts?.trace?.trace_id; + const traceId = transaction[0]?.contexts?.trace?.trace_id; expect(traceId).toBeDefined(); diff --git a/packages/nextjs/test/integration/test/client/tracingClientGetServerSideProps.test.ts b/packages/nextjs/test/integration/test/client/tracingClientGetServerSideProps.test.ts index 4da9bc181469..ee8990462d47 100644 --- a/packages/nextjs/test/integration/test/client/tracingClientGetServerSideProps.test.ts +++ b/packages/nextjs/test/integration/test/client/tracingClientGetServerSideProps.test.ts @@ -20,7 +20,7 @@ test('should instrument `getServerSideProps` for performance tracing', async ({ const nextDataTagValue = JSON.parse(await nextDataTag.evaluate(tag => (tag as HTMLElement).innerText)); // @ts-expect-error - We know `contexts` is defined in the Transaction envelope - const traceId = transaction[0].contexts.trace.trace_id; + const traceId = transaction[0]?.contexts.trace.trace_id; expect(traceId).toBeDefined(); diff --git a/packages/nextjs/test/integration/test/client/tracingFetch.test.ts b/packages/nextjs/test/integration/test/client/tracingFetch.test.ts index 4d7b416a17dd..1ad8d3859518 100644 --- a/packages/nextjs/test/integration/test/client/tracingFetch.test.ts +++ b/packages/nextjs/test/integration/test/client/tracingFetch.test.ts @@ -27,7 +27,7 @@ test('should correctly instrument `fetch` for performance tracing', async ({ pag }, }); - expect(transaction[0].spans).toEqual( + expect(transaction[0]?.spans).toEqual( expect.arrayContaining([ expect.objectContaining({ data: { diff --git a/packages/nextjs/test/integration/test/server/errorApiEndpoint.test.ts b/packages/nextjs/test/integration/test/server/errorApiEndpoint.test.ts index ad838873871e..b99058ecfe64 100644 --- a/packages/nextjs/test/integration/test/server/errorApiEndpoint.test.ts +++ b/packages/nextjs/test/integration/test/server/errorApiEndpoint.test.ts @@ -38,7 +38,7 @@ describe('Error API Endpoints', () => { }); const sentryTransactionEnvelope = envelopes.find(envelope => { - const envelopeItem = envelope[2]; + const envelopeItem = envelope[2]!; return envelopeItem.transaction === 'GET /api/error'; }); diff --git a/packages/nextjs/test/integration/test/server/errorServerSideProps.test.ts b/packages/nextjs/test/integration/test/server/errorServerSideProps.test.ts index 9148fe90c3ac..b777653af526 100644 --- a/packages/nextjs/test/integration/test/server/errorServerSideProps.test.ts +++ b/packages/nextjs/test/integration/test/server/errorServerSideProps.test.ts @@ -38,7 +38,7 @@ describe('Error Server-side Props', () => { }); const sentryTransactionEnvelope = envelopes.find(envelope => { - const envelopeItem = envelope[2]; + const envelopeItem = envelope[2]!; return envelopeItem.transaction === '/withErrorServerSideProps'; }); diff --git a/packages/nextjs/test/integration/test/server/tracing200.test.ts b/packages/nextjs/test/integration/test/server/tracing200.test.ts index c56da4d11631..dec2af2ef086 100644 --- a/packages/nextjs/test/integration/test/server/tracing200.test.ts +++ b/packages/nextjs/test/integration/test/server/tracing200.test.ts @@ -12,7 +12,7 @@ describe('Tracing 200', () => { }); const sentryTransactionEnvelope = envelopes.find(envelope => { - const envelopeItem = envelope[2]; + const envelopeItem = envelope[2]!; return envelopeItem.transaction === 'GET /api/users'; }); diff --git a/packages/nextjs/test/integration/test/server/tracing500.test.ts b/packages/nextjs/test/integration/test/server/tracing500.test.ts index 635cacf11889..d98c36d61db3 100644 --- a/packages/nextjs/test/integration/test/server/tracing500.test.ts +++ b/packages/nextjs/test/integration/test/server/tracing500.test.ts @@ -12,7 +12,7 @@ describe('Tracing 500', () => { }); const sentryTransactionEnvelope = envelopes.find(envelope => { - const envelopeItem = envelope[2]; + const envelopeItem = envelope[2]!; return envelopeItem.transaction === 'GET /api/broken'; }); diff --git a/packages/nextjs/test/integration/test/server/tracingHttp.test.ts b/packages/nextjs/test/integration/test/server/tracingHttp.test.ts index 79afda4fa3f0..1d84c2309d53 100644 --- a/packages/nextjs/test/integration/test/server/tracingHttp.test.ts +++ b/packages/nextjs/test/integration/test/server/tracingHttp.test.ts @@ -12,13 +12,13 @@ describe('Tracing HTTP', () => { }); const sentryTransactionEnvelope = envelopes.find(envelope => { - const envelopeItem = envelope[2]; + const envelopeItem = envelope[2]!; return envelopeItem.transaction === 'GET /api/http'; }); expect(sentryTransactionEnvelope).toBeDefined(); - const envelopeItem = sentryTransactionEnvelope![2]; + const envelopeItem = sentryTransactionEnvelope![2]!; expect(envelopeItem).toMatchObject({ contexts: { diff --git a/packages/nextjs/test/integration/test/server/tracingServerGetInitialProps.test.ts b/packages/nextjs/test/integration/test/server/tracingServerGetInitialProps.test.ts index d809654c9d44..4e8c1a2975c4 100644 --- a/packages/nextjs/test/integration/test/server/tracingServerGetInitialProps.test.ts +++ b/packages/nextjs/test/integration/test/server/tracingServerGetInitialProps.test.ts @@ -12,7 +12,7 @@ describe('getInitialProps', () => { }); const sentryTransactionEnvelope = envelopes.find(envelope => { - const envelopeItem = envelope[2]; + const envelopeItem = envelope[2]!; return envelopeItem.transaction === `/[id]/withInitialProps`; }); diff --git a/packages/nextjs/test/integration/test/server/tracingServerGetServerSideProps.test.ts b/packages/nextjs/test/integration/test/server/tracingServerGetServerSideProps.test.ts index c40c3b52da08..88e0bcb2b550 100644 --- a/packages/nextjs/test/integration/test/server/tracingServerGetServerSideProps.test.ts +++ b/packages/nextjs/test/integration/test/server/tracingServerGetServerSideProps.test.ts @@ -12,7 +12,7 @@ describe('getServerSideProps', () => { }); const sentryTransactionEnvelope = envelopes.find(envelope => { - const envelopeItem = envelope[2]; + const envelopeItem = envelope[2]!; return envelopeItem.transaction === '/[id]/withServerSideProps'; }); diff --git a/packages/nextjs/test/integration/test/server/tracingServerGetServerSidePropsCustomPageExtension.test.ts b/packages/nextjs/test/integration/test/server/tracingServerGetServerSidePropsCustomPageExtension.test.ts index 96fee1458fa6..54dec6ae5153 100644 --- a/packages/nextjs/test/integration/test/server/tracingServerGetServerSidePropsCustomPageExtension.test.ts +++ b/packages/nextjs/test/integration/test/server/tracingServerGetServerSidePropsCustomPageExtension.test.ts @@ -12,7 +12,7 @@ describe('tracingServerGetServerSidePropsCustomPageExtension', () => { }); const sentryTransactionEnvelope = envelopes.find(envelope => { - const envelopeItem = envelope[2]; + const envelopeItem = envelope[2]!; return envelopeItem.transaction === '/customPageExtension'; }); diff --git a/packages/nextjs/test/integration/test/tsconfig.json b/packages/nextjs/test/integration/test/tsconfig.json new file mode 100644 index 000000000000..e48928457b7c --- /dev/null +++ b/packages/nextjs/test/integration/test/tsconfig.json @@ -0,0 +1,4 @@ +{ + "extends": "../../../tsconfig.test.json", + +} diff --git a/packages/nextjs/test/performance/pagesRouterInstrumentation.test.ts b/packages/nextjs/test/performance/pagesRouterInstrumentation.test.ts index 3091fea80cec..947b3c9f3f69 100644 --- a/packages/nextjs/test/performance/pagesRouterInstrumentation.test.ts +++ b/packages/nextjs/test/performance/pagesRouterInstrumentation.test.ts @@ -29,16 +29,16 @@ jest.mock('next/router', () => { eventHandlers[type] = new Set(); } - eventHandlers[type].add(handler); + eventHandlers[type]!.add(handler); }, off: jest.fn((type: string, handler: (...args: any[]) => void) => { if (eventHandlers[type]) { - eventHandlers[type].delete(handler); + eventHandlers[type]!.delete(handler); } }), emit(type: string, ...eventArgs: any[]) { if (eventHandlers[type]) { - eventHandlers[type].forEach(eventHandler => { + eventHandlers[type]!.forEach(eventHandler => { eventHandler(...eventArgs); }); } diff --git a/packages/nextjs/test/serverSdk.test.ts b/packages/nextjs/test/serverSdk.test.ts index 7354c11643f4..fa17327b22f5 100644 --- a/packages/nextjs/test/serverSdk.test.ts +++ b/packages/nextjs/test/serverSdk.test.ts @@ -92,7 +92,7 @@ describe('Server init()', () => { it('adds default integrations', () => { init({}); - const nodeInitOptions = nodeInit.mock.calls[0][0] as ModifiedInitOptions; + const nodeInitOptions = nodeInit.mock.calls[0]?.[0] as ModifiedInitOptions; const integrationNames = nodeInitOptions.defaultIntegrations.map(integration => integration.name); const onUncaughtExceptionIntegration = findIntegrationByName( nodeInitOptions.defaultIntegrations, @@ -106,7 +106,7 @@ describe('Server init()', () => { it('supports passing unrelated integrations through options', () => { init({ integrations: [SentryNode.consoleIntegration()] }); - const nodeInitOptions = nodeInit.mock.calls[0][0] as ModifiedInitOptions; + const nodeInitOptions = nodeInit.mock.calls[0]?.[0] as ModifiedInitOptions; const consoleIntegration = findIntegrationByName(nodeInitOptions.integrations, 'Console'); expect(consoleIntegration).toBeDefined(); diff --git a/packages/node/src/integrations/context.ts b/packages/node/src/integrations/context.ts index 379af4710794..e8ab2c7f84f1 100644 --- a/packages/node/src/integrations/context.ts +++ b/packages/node/src/integrations/context.ts @@ -247,9 +247,8 @@ export function getDeviceContext(deviceOpt: DeviceContextOptions | true): Device if (deviceOpt === true || deviceOpt.cpu) { const cpuInfo: os.CpuInfo[] | undefined = os.cpus(); - if (cpuInfo && cpuInfo.length) { - const firstCpu = cpuInfo[0]; - + const firstCpu = cpuInfo && cpuInfo[0]; + if (firstCpu) { device.processor_count = cpuInfo.length; device.cpu_description = firstCpu.model; device.processor_frequency = firstCpu.speed; @@ -273,7 +272,7 @@ interface DistroFile { /** The file name, located in `/etc`. */ name: string; /** Potential distributions to check. */ - distros: string[]; + distros: [string, ...string[]]; } /** Mapping of linux release files located in /etc to distributions. */ @@ -356,7 +355,7 @@ async function getDarwinInfo(): Promise { /** Returns a distribution identifier to look up version callbacks. */ function getLinuxDistroId(name: string): string { - return name.split(' ')[0].toLowerCase(); + return (name.split(' ') as [string])[0].toLowerCase(); } /** Loads the Linux operating system context. */ @@ -401,7 +400,7 @@ async function getLinuxInfo(): Promise { // number. This is different for every distribution, so several strategies // are computed in `LINUX_VERSIONS`. const id = getLinuxDistroId(linuxInfo.name); - linuxInfo.version = LINUX_VERSIONS[id](contents); + linuxInfo.version = LINUX_VERSIONS[id]?.(contents); } catch (e) { // ignore } diff --git a/packages/node/src/integrations/contextlines.ts b/packages/node/src/integrations/contextlines.ts index 7f4b78653b6a..204385144075 100644 --- a/packages/node/src/integrations/contextlines.ts +++ b/packages/node/src/integrations/contextlines.ts @@ -59,7 +59,8 @@ async function addSourceContext(event: Event, contextLines: number): Promise= 0; i--) { - const frame = exception.stacktrace.frames[i]; + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion + const frame = exception.stacktrace.frames[i]!; // Call cache.get to bump the file to the top of the cache and ensure we have not already // enqueued a read operation for this filename if (frame.filename && !enqueuedReadSourceFileTasks[frame.filename] && !FILE_CONTENT_CACHE.get(frame.filename)) { diff --git a/packages/node/src/integrations/local-variables/local-variables-async.ts b/packages/node/src/integrations/local-variables/local-variables-async.ts index 06056aae6a39..86ce9359a95e 100644 --- a/packages/node/src/integrations/local-variables/local-variables-async.ts +++ b/packages/node/src/integrations/local-variables/local-variables-async.ts @@ -45,23 +45,26 @@ export const localVariablesAsyncIntegration = defineIntegration((( // Sentry frames are in reverse order const frameIndex = frames.length - i - 1; - // Drop out if we run out of frames to match up - if (!frames[frameIndex] || !cachedFrame[i]) { + const cachedFrameVariable = cachedFrame[i]; + const frameVariable = frames[frameIndex]; + + if (!frameVariable || !cachedFrameVariable) { + // Drop out if we run out of frames to match up break; } if ( // We need to have vars to add - cachedFrame[i].vars === undefined || + cachedFrameVariable.vars === undefined || // We're not interested in frames that are not in_app because the vars are not relevant - frames[frameIndex].in_app === false || + frameVariable.in_app === false || // The function names need to match - !functionNamesMatch(frames[frameIndex].function, cachedFrame[i].function) + !functionNamesMatch(frameVariable.function, cachedFrameVariable.function) ) { continue; } - frames[frameIndex].vars = cachedFrame[i].vars; + frameVariable.vars = cachedFrameVariable.vars; } } diff --git a/packages/node/src/integrations/local-variables/local-variables-sync.ts b/packages/node/src/integrations/local-variables/local-variables-sync.ts index 9616e534625a..bf7aaa26cbf3 100644 --- a/packages/node/src/integrations/local-variables/local-variables-sync.ts +++ b/packages/node/src/integrations/local-variables/local-variables-sync.ts @@ -236,23 +236,26 @@ const _localVariablesSyncIntegration = (( // Sentry frames are in reverse order const frameIndex = frames.length - i - 1; + const cachedFrameVariable = cachedFrame[i]; + const frameVariable = frames[frameIndex]; + // Drop out if we run out of frames to match up - if (!frames[frameIndex] || !cachedFrame[i]) { + if (!frameVariable || !cachedFrameVariable) { break; } if ( // We need to have vars to add - cachedFrame[i].vars === undefined || + cachedFrameVariable.vars === undefined || // We're not interested in frames that are not in_app because the vars are not relevant - frames[frameIndex].in_app === false || + frameVariable.in_app === false || // The function names need to match - !functionNamesMatch(frames[frameIndex].function, cachedFrame[i].function) + !functionNamesMatch(frameVariable.function, cachedFrameVariable.function) ) { continue; } - frames[frameIndex].vars = cachedFrame[i].vars; + frameVariable.vars = cachedFrameVariable.vars; } } @@ -313,7 +316,8 @@ const _localVariablesSyncIntegration = (( // Because we're queuing up and making all these calls synchronously, we can potentially overflow the stack // For this reason we only attempt to get local variables for the first 5 frames for (let i = 0; i < Math.min(callFrames.length, 5); i++) { - const { scopeChain, functionName, this: obj } = callFrames[i]; + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion + const { scopeChain, functionName, this: obj } = callFrames[i]!; const localScope = scopeChain.find(scope => scope.type === 'local'); diff --git a/packages/node/src/integrations/local-variables/worker.ts b/packages/node/src/integrations/local-variables/worker.ts index fee92b10106c..5bee22a84c29 100644 --- a/packages/node/src/integrations/local-variables/worker.ts +++ b/packages/node/src/integrations/local-variables/worker.ts @@ -107,7 +107,8 @@ async function handlePaused( const frames = []; for (let i = 0; i < callFrames.length; i++) { - const { scopeChain, functionName, this: obj } = callFrames[i]; + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion + const { scopeChain, functionName, this: obj } = callFrames[i]!; const localScope = scopeChain.find(scope => scope.type === 'local'); diff --git a/packages/node/src/otel/instrument.ts b/packages/node/src/otel/instrument.ts index 71cc28a24915..5db7960e4019 100644 --- a/packages/node/src/otel/instrument.ts +++ b/packages/node/src/otel/instrument.ts @@ -13,10 +13,11 @@ export function generateInstrumentOnce( ): ((options?: Options) => void) & { id: string } { return Object.assign( (options?: Options) => { - if (INSTRUMENTED[name]) { + const instrumented = INSTRUMENTED[name]; + if (instrumented) { // If options are provided, ensure we update them if (options) { - INSTRUMENTED[name].setConfig(options); + instrumented.setConfig(options); } return; } diff --git a/packages/node/src/proxy/parse-proxy-response.ts b/packages/node/src/proxy/parse-proxy-response.ts index a01e6675b765..da29fa52c747 100644 --- a/packages/node/src/proxy/parse-proxy-response.ts +++ b/packages/node/src/proxy/parse-proxy-response.ts @@ -96,7 +96,7 @@ export function parseProxyResponse(socket: Readable): Promise<{ connect: Connect return reject(new Error('No header received from proxy CONNECT response')); } const firstLineParts = firstLine.split(' '); - const statusCode = +firstLineParts[1]; + const statusCode = +(firstLineParts[1] || 0); const statusText = firstLineParts.slice(2).join(' '); const headers: IncomingHttpHeaders = {}; for (const header of headerParts) { diff --git a/packages/node/src/sdk/initOtel.ts b/packages/node/src/sdk/initOtel.ts index 26a2f34e0901..652c3826fa04 100644 --- a/packages/node/src/sdk/initOtel.ts +++ b/packages/node/src/sdk/initOtel.ts @@ -32,7 +32,7 @@ export function initOpenTelemetry(client: NodeClient): void { /** Initialize the ESM loader. */ export function maybeInitializeEsmLoader(): void { - const [nodeMajor, nodeMinor] = process.versions.node.split('.').map(Number); + const [nodeMajor = 0, nodeMinor = 0] = process.versions.node.split('.').map(Number); // Register hook was added in v20.6.0 and v18.19.0 if (nodeMajor >= 22 || (nodeMajor === 20 && nodeMinor >= 6) || (nodeMajor === 18 && nodeMinor >= 19)) { diff --git a/packages/node/src/transports/http.ts b/packages/node/src/transports/http.ts index 16f42de36d66..751e4f3b3f4d 100644 --- a/packages/node/src/transports/http.ts +++ b/packages/node/src/transports/http.ts @@ -162,7 +162,7 @@ function createRequestExecutor( statusCode: res.statusCode, headers: { 'retry-after': retryAfterHeader, - 'x-sentry-rate-limits': Array.isArray(rateLimitsHeader) ? rateLimitsHeader[0] : rateLimitsHeader, + 'x-sentry-rate-limits': Array.isArray(rateLimitsHeader) ? rateLimitsHeader[0] || null : rateLimitsHeader, }, }); }, diff --git a/packages/node/src/utils/redisCache.ts b/packages/node/src/utils/redisCache.ts index 9f0e7e6f1ac8..7c9cff30f9d9 100644 --- a/packages/node/src/utils/redisCache.ts +++ b/packages/node/src/utils/redisCache.ts @@ -48,8 +48,9 @@ export function getCacheKeySafely(redisCommand: string, cmdArgs: IORedisCommandA } }; - if (isInCommands(SINGLE_ARG_COMMANDS, redisCommand) && cmdArgs.length > 0) { - return processArg(cmdArgs[0]); + const firstArg = cmdArgs[0]; + if (isInCommands(SINGLE_ARG_COMMANDS, redisCommand) && firstArg != null) { + return processArg(firstArg); } return flatten(cmdArgs.map(arg => processArg(arg))); diff --git a/packages/node/test/integration/transactions.test.ts b/packages/node/test/integration/transactions.test.ts index a690f1187669..75f5ef85a519 100644 --- a/packages/node/test/integration/transactions.test.ts +++ b/packages/node/test/integration/transactions.test.ts @@ -66,7 +66,7 @@ describe('Integration | Transactions', () => { await client.flush(); expect(transactions).toHaveLength(1); - const transaction = transactions[0]; + const transaction = transactions[0]!; expect(transaction.breadcrumbs).toEqual([ { message: 'test breadcrumb 1', timestamp: 123456 }, @@ -525,7 +525,7 @@ describe('Integration | Transactions', () => { // Checking the spans here, as they are circular to the transaction... const runArgs = beforeSendTransaction.mock.calls[0] as unknown as [TransactionEvent, unknown]; - const spans = runArgs[0].spans || []; + const spans = runArgs[0]?.spans || []; // note: Currently, spans do not have any context/span added to them // This is the same behavior as for the "regular" SDKs diff --git a/packages/node/test/sdk/init.test.ts b/packages/node/test/sdk/init.test.ts index 5592acfaa897..50a0fec7c2d6 100644 --- a/packages/node/test/sdk/init.test.ts +++ b/packages/node/test/sdk/init.test.ts @@ -61,10 +61,10 @@ describe('init()', () => { init({ dsn: PUBLIC_DSN, integrations: mockIntegrations, defaultIntegrations: mockDefaultIntegrations }); - expect(mockDefaultIntegrations[0].setupOnce as jest.Mock).toHaveBeenCalledTimes(0); - expect(mockDefaultIntegrations[1].setupOnce as jest.Mock).toHaveBeenCalledTimes(1); - expect(mockIntegrations[0].setupOnce as jest.Mock).toHaveBeenCalledTimes(1); - expect(mockIntegrations[1].setupOnce as jest.Mock).toHaveBeenCalledTimes(1); + expect(mockDefaultIntegrations[0]?.setupOnce as jest.Mock).toHaveBeenCalledTimes(0); + expect(mockDefaultIntegrations[1]?.setupOnce as jest.Mock).toHaveBeenCalledTimes(1); + expect(mockIntegrations[0]?.setupOnce as jest.Mock).toHaveBeenCalledTimes(1); + expect(mockIntegrations[1]?.setupOnce as jest.Mock).toHaveBeenCalledTimes(1); expect(mockAutoPerformanceIntegrations).toHaveBeenCalledTimes(0); }); @@ -86,8 +86,8 @@ describe('init()', () => { }, }); - expect(mockDefaultIntegrations[0].setupOnce as jest.Mock).toHaveBeenCalledTimes(1); - expect(mockDefaultIntegrations[1].setupOnce as jest.Mock).toHaveBeenCalledTimes(0); + expect(mockDefaultIntegrations[0]?.setupOnce as jest.Mock).toHaveBeenCalledTimes(1); + expect(mockDefaultIntegrations[1]?.setupOnce as jest.Mock).toHaveBeenCalledTimes(0); expect(newIntegration.setupOnce as jest.Mock).toHaveBeenCalledTimes(1); expect(mockAutoPerformanceIntegrations).toHaveBeenCalledTimes(0); }); @@ -108,8 +108,8 @@ describe('init()', () => { enableTracing: true, }); - expect(mockIntegrations[0].setupOnce as jest.Mock).toHaveBeenCalledTimes(1); - expect(mockIntegrations[1].setupOnce as jest.Mock).toHaveBeenCalledTimes(1); + expect(mockIntegrations[0]?.setupOnce as jest.Mock).toHaveBeenCalledTimes(1); + expect(mockIntegrations[1]?.setupOnce as jest.Mock).toHaveBeenCalledTimes(1); expect(autoPerformanceIntegration.setupOnce as jest.Mock).toHaveBeenCalledTimes(1); expect(mockAutoPerformanceIntegrations).toHaveBeenCalledTimes(1); diff --git a/packages/opentelemetry/test/integration/transactions.test.ts b/packages/opentelemetry/test/integration/transactions.test.ts index cf4a145775ec..13f8c91af7b7 100644 --- a/packages/opentelemetry/test/integration/transactions.test.ts +++ b/packages/opentelemetry/test/integration/transactions.test.ts @@ -81,7 +81,7 @@ describe('Integration | Transactions', () => { await client.flush(); expect(transactions).toHaveLength(1); - const transaction = transactions[0]; + const transaction = transactions[0]!; expect(transaction.breadcrumbs).toEqual([ { message: 'test breadcrumb 1', timestamp: 123456 }, @@ -406,7 +406,7 @@ describe('Integration | Transactions', () => { // Checking the spans here, as they are circular to the transaction... const runArgs = beforeSendTransaction.mock.calls[0] as unknown as [TransactionEvent, unknown]; - const spans = runArgs[0].spans || []; + const spans = runArgs[0]?.spans || []; // note: Currently, spans do not have any context/span added to them // This is the same behavior as for the "regular" SDKs @@ -558,7 +558,7 @@ describe('Integration | Transactions', () => { jest.advanceTimersByTime(1); expect(transactions).toHaveLength(1); - expect(transactions[0].spans).toHaveLength(2); + expect(transactions[0]?.spans).toHaveLength(2); // No spans are pending expect(exporter['_finishedSpans'].length).toBe(0); @@ -612,11 +612,11 @@ describe('Integration | Transactions', () => { jest.advanceTimersByTime(2); expect(transactions).toHaveLength(1); - expect(transactions[0].spans).toHaveLength(1); + expect(transactions[0]?.spans).toHaveLength(1); // subSpan2 is pending (and will eventually be cleaned up) expect(exporter['_finishedSpans'].length).toBe(1); - expect(exporter['_finishedSpans'][0].name).toBe('inner span 2'); + expect(exporter['_finishedSpans'][0]?.name).toBe('inner span 2'); }); it('uses & inherits DSC on span trace state', async () => { diff --git a/packages/opentelemetry/test/trace.test.ts b/packages/opentelemetry/test/trace.test.ts index e46ef43f385b..e352dc9ca9bd 100644 --- a/packages/opentelemetry/test/trace.test.ts +++ b/packages/opentelemetry/test/trace.test.ts @@ -68,7 +68,7 @@ describe('trace', () => { expect(getActiveSpan()).toEqual(undefined); expect(spans).toHaveLength(2); - const [outerSpan, innerSpan] = spans; + const [outerSpan, innerSpan] = spans as [Span, Span]; expect(getSpanName(outerSpan)).toEqual('outer'); expect(getSpanName(innerSpan)).toEqual('inner'); @@ -108,7 +108,7 @@ describe('trace', () => { expect(getActiveSpan()).toEqual(undefined); expect(spans).toHaveLength(2); - const [outerSpan, innerSpan] = spans; + const [outerSpan, innerSpan] = spans as [Span, Span]; expect(getSpanName(outerSpan)).toEqual('outer'); expect(getSpanName(innerSpan)).toEqual('inner'); @@ -350,7 +350,7 @@ describe('trace', () => { const outerTraceId = outerTransaction?.contexts?.trace?.trace_id; // The inner transaction should be a child of the last span of the outer transaction - const innerParentSpanId = outerTransaction?.spans?.[0].id; + const innerParentSpanId = outerTransaction?.spans?.[0]?.id; const innerSpanId = innerTransaction?.contexts?.trace?.span_id; expect(outerTraceId).toBeDefined(); @@ -586,7 +586,7 @@ describe('trace', () => { const outerTraceId = outerTransaction?.contexts?.trace?.trace_id; // The inner transaction should be a child of the last span of the outer transaction - const innerParentSpanId = outerTransaction?.spans?.[0].id; + const innerParentSpanId = outerTransaction?.spans?.[0]?.id; const innerSpanId = innerTransaction?.contexts?.trace?.span_id; expect(outerTraceId).toBeDefined(); @@ -857,7 +857,7 @@ describe('trace', () => { const outerTraceId = outerTransaction?.contexts?.trace?.trace_id; // The inner transaction should be a child of the last span of the outer transaction - const innerParentSpanId = outerTransaction?.spans?.[0].id; + const innerParentSpanId = outerTransaction?.spans?.[0]?.id; const innerSpanId = innerTransaction?.contexts?.trace?.span_id; expect(outerTraceId).toBeDefined(); diff --git a/packages/profiling-node/test/spanProfileUtils.test.ts b/packages/profiling-node/test/spanProfileUtils.test.ts index f2ca1b932e12..766a0059d02e 100644 --- a/packages/profiling-node/test/spanProfileUtils.test.ts +++ b/packages/profiling-node/test/spanProfileUtils.test.ts @@ -133,7 +133,7 @@ describe('automated span instrumentation', () => { expect(logSpy).toHaveBeenCalledWith('[Profiling] Discarding profile because it contains less than 2 samples'); - expect((transport.send as any).mock.calls[0][0][1][0][0].type).toBe('transaction'); + expect((transport.send as any).mock.calls[0][0][1][0][0]?.type).toBe('transaction'); // eslint-disable-next-line @typescript-eslint/unbound-method expect(transport.send).toHaveBeenCalledTimes(1); }); @@ -569,7 +569,7 @@ describe('continuous profiling', () => { integration._profiler.stop(); jest.advanceTimersByTime(1000); - expect(transportSpy.mock.calls?.[0]?.[0]?.[1]?.[0]?.[0].type).toBe('profile_chunk'); + expect(transportSpy.mock.calls?.[0]?.[0]?.[1]?.[0]?.[0]?.type).toBe('profile_chunk'); }); it('sets global profile context', async () => { diff --git a/packages/react/src/reactrouter.tsx b/packages/react/src/reactrouter.tsx index 3adf59326012..c15fb9d0b191 100644 --- a/packages/react/src/reactrouter.tsx +++ b/packages/react/src/reactrouter.tsx @@ -143,10 +143,9 @@ function instrumentReactRouter( } const branches = matchRoutes(allRoutes, pathname, matchPath); - // eslint-disable-next-line @typescript-eslint/prefer-for-of - for (let x = 0; x < branches.length; x++) { - if (branches[x].match.isExact) { - return [branches[x].match.path, 'route']; + for (const branch of branches) { + if (branch.match.isExact) { + return [branch.match.path, 'route']; } } @@ -199,7 +198,8 @@ function matchRoutes( const match = route.path ? matchPath(pathname, route) : branch.length - ? branch[branch.length - 1].match // use parent match + ? // eslint-disable-next-line @typescript-eslint/no-non-null-assertion + branch[branch.length - 1]!.match // use parent match : computeRootMatch(pathname); // use default "root" match if (match) { diff --git a/packages/react/src/reactrouterv3.ts b/packages/react/src/reactrouterv3.ts index 4c5c084db791..df495de3e711 100644 --- a/packages/react/src/reactrouterv3.ts +++ b/packages/react/src/reactrouterv3.ts @@ -143,7 +143,8 @@ function getRouteStringFromRoutes(routes: Route[]): string { let index = -1; for (let x = routesWithPaths.length - 1; x >= 0; x--) { - const route = routesWithPaths[x]; + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion + const route = routesWithPaths[x]!; if (route.path && route.path.startsWith('/')) { index = x; break; diff --git a/packages/react/src/reactrouterv6.tsx b/packages/react/src/reactrouterv6.tsx index 7ac981f90d4c..400028a28789 100644 --- a/packages/react/src/reactrouterv6.tsx +++ b/packages/react/src/reactrouterv6.tsx @@ -153,9 +153,7 @@ function getNormalizedName( let pathBuilder = ''; if (branches) { - // eslint-disable-next-line @typescript-eslint/prefer-for-of - for (let x = 0; x < branches.length; x++) { - const branch = branches[x]; + for (const branch of branches) { const route = branch.route; if (route) { // Early return if index route diff --git a/packages/react/src/tanstackrouter.ts b/packages/react/src/tanstackrouter.ts index 52b0148d3042..2efefba49e31 100644 --- a/packages/react/src/tanstackrouter.ts +++ b/packages/react/src/tanstackrouter.ts @@ -115,9 +115,9 @@ function routeMatchToParamSpanAttributes(match: VendoredTanstackRouterRouteMatch } const paramAttributes: Record = {}; - for (const key of Object.keys(match.params)) { - paramAttributes[`url.path.params.${key}`] = match.params[key]; - } + Object.entries(match.params).forEach(([key, value]) => { + paramAttributes[`url.path.params.${key}`] = value; + }); return paramAttributes; } diff --git a/packages/react/test/errorboundary.test.tsx b/packages/react/test/errorboundary.test.tsx index d185dc8a9647..b4d907ef0873 100644 --- a/packages/react/test/errorboundary.test.tsx +++ b/packages/react/test/errorboundary.test.tsx @@ -272,7 +272,7 @@ describe('ErrorBoundary', () => { // Check if error.cause -> react component stack const error = mockCaptureException.mock.calls[0][0]; const cause = error.cause; - expect(cause.stack).toEqual(mockCaptureException.mock.calls[0][1].captureContext.contexts.react.componentStack); + expect(cause.stack).toEqual(mockCaptureException.mock.calls[0][1]?.captureContext.contexts.react.componentStack); expect(cause.name).toContain('React ErrorBoundary'); expect(cause.message).toEqual(error.message); }); @@ -370,7 +370,7 @@ describe('ErrorBoundary', () => { const secondError = thirdError.cause; const firstError = secondError.cause; const cause = firstError.cause; - expect(cause.stack).toEqual(mockCaptureException.mock.calls[0][1].captureContext.contexts.react.componentStack); + expect(cause.stack).toEqual(mockCaptureException.mock.calls[0][1]?.captureContext.contexts.react.componentStack); expect(cause.name).toContain('React ErrorBoundary'); expect(cause.message).toEqual(thirdError.message); }); @@ -414,7 +414,7 @@ describe('ErrorBoundary', () => { const cause = error.cause; // We need to make sure that recursive error.cause does not cause infinite loop expect(cause.stack).not.toEqual( - mockCaptureException.mock.calls[0][1].captureContext.contexts.react.componentStack, + mockCaptureException.mock.calls[0][1]?.captureContext.contexts.react.componentStack, ); expect(cause.name).not.toContain('React ErrorBoundary'); }); diff --git a/packages/remix/src/client/performance.tsx b/packages/remix/src/client/performance.tsx index d92ad323beb1..f8432aa0b613 100644 --- a/packages/remix/src/client/performance.tsx +++ b/packages/remix/src/client/performance.tsx @@ -93,8 +93,16 @@ export function startPageloadSpan(): void { } function startNavigationSpan(matches: RouteMatch[]): void { + const lastMatch = matches[matches.length - 1]; + + const client = getClient(); + + if (!client || !lastMatch) { + return; + } + const spanContext: StartSpanOptions = { - name: matches[matches.length - 1].id, + name: lastMatch.id, op: 'navigation', attributes: { [SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: 'auto.navigation.remix', @@ -102,12 +110,6 @@ function startNavigationSpan(matches: RouteMatch[]): void { }, }; - const client = getClient(); - - if (!client) { - return; - } - startBrowserTracingNavigationSpan(client, spanContext); } @@ -148,8 +150,9 @@ export function withSentry

, R extends React.Co const matches = _useMatches(); _useEffect(() => { - if (matches && matches.length) { - const routeName = matches[matches.length - 1].id; + const lastMatch = matches && matches[matches.length - 1]; + if (lastMatch) { + const routeName = lastMatch.id; getCurrentScope().setTransactionName(routeName); const activeRootSpan = getActiveSpan(); diff --git a/packages/remix/src/utils/vendor/response.ts b/packages/remix/src/utils/vendor/response.ts index 8a8421ab06fd..c79b253adc7e 100644 --- a/packages/remix/src/utils/vendor/response.ts +++ b/packages/remix/src/utils/vendor/response.ts @@ -118,10 +118,10 @@ export function getRequestMatch( url: URL, matches: AgnosticRouteMatch[], ): AgnosticRouteMatch { - const match = matches.slice(-1)[0]; + const match = matches.slice(-1)[0] as AgnosticRouteMatch; if (!isIndexRequestUrl(url) && match.route.id?.endsWith('/index')) { - return matches.slice(-2)[0]; + return matches.slice(-2)[0] as AgnosticRouteMatch; } return match; diff --git a/packages/remix/src/utils/web-fetch.ts b/packages/remix/src/utils/web-fetch.ts index 0a28357e4dea..ba34e4320973 100644 --- a/packages/remix/src/utils/web-fetch.ts +++ b/packages/remix/src/utils/web-fetch.ts @@ -75,8 +75,10 @@ export const normalizeRemixRequest = (request: RemixRequest): Record { method: 'post', envelopeType: 'transaction', }); - const transaction = envelope[2]; + const transaction = envelope[2]!; assertSentryTransaction(transaction, { transaction: `routes/action-json-response${useV2 ? '.' : '/'}$id`, @@ -57,10 +57,10 @@ describe('Remix API Actions', () => { envelopeType: ['transaction', 'event'], }); - const [transaction] = envelopes.filter(envelope => envelope[1].type === 'transaction'); - const [event] = envelopes.filter(envelope => envelope[1].type === 'event'); + const [transaction] = envelopes.filter(envelope => envelope[1]?.type === 'transaction'); + const [event] = envelopes.filter(envelope => envelope[1]?.type === 'event'); - assertSentryTransaction(transaction[2], { + assertSentryTransaction(transaction![2]!, { contexts: { trace: { status: 'internal_error', @@ -71,7 +71,7 @@ describe('Remix API Actions', () => { }, }); - assertSentryEvent(event[2], { + assertSentryEvent(event![2]!, { transaction: expect.stringMatching(/routes\/action-json-response(\/|\.)\$id/), exception: { values: [ @@ -103,10 +103,10 @@ describe('Remix API Actions', () => { envelopeType: ['transaction', 'event'], }); - const [transaction] = envelopes.filter(envelope => envelope[1].type === 'transaction'); - const [event] = envelopes.filter(envelope => envelope[1].type === 'event'); + const [transaction] = envelopes.filter(envelope => envelope[1]?.type === 'transaction'); + const [event] = envelopes.filter(envelope => envelope[1]?.type === 'event'); - assertSentryTransaction(transaction[2], { + assertSentryTransaction(transaction![2]!, { transaction: `routes/action-json-response${useV2 ? '.' : '/'}$id`, request: { method: 'POST', @@ -119,7 +119,7 @@ describe('Remix API Actions', () => { }, }); - assertSentryEvent(event[2], { + assertSentryEvent(event![2]!, { transaction: expect.stringMatching(/routes\/action-json-response(\/|\.)\$id/), exception: { values: [ @@ -152,9 +152,9 @@ describe('Remix API Actions', () => { envelopeType: ['transaction', 'event'], }); - const [transaction_1, transaction_2] = envelopes.filter(envelope => envelope[1].type === 'transaction'); - const [event] = envelopes.filter(envelope => envelope[1].type === 'event'); - assertSentryTransaction(transaction_1[2], { + const [transaction_1, transaction_2] = envelopes.filter(envelope => envelope[1]?.type === 'transaction'); + const [event] = envelopes.filter(envelope => envelope[1]?.type === 'event'); + assertSentryTransaction(transaction_1![2]!, { contexts: { trace: { op: 'http.server', @@ -168,7 +168,7 @@ describe('Remix API Actions', () => { transaction: `routes/action-json-response${useV2 ? '.' : '/'}$id`, }); - assertSentryTransaction(transaction_2[2], { + assertSentryTransaction(transaction_2![2]!, { contexts: { trace: { op: 'http.server', @@ -182,7 +182,7 @@ describe('Remix API Actions', () => { transaction: `routes/action-json-response${useV2 ? '.' : '/'}$id`, }); - assertSentryEvent(event[2], { + assertSentryEvent(event![2]!, { transaction: expect.stringMatching(/routes\/action-json-response(\/|\.)\$id/), exception: { values: [ @@ -214,10 +214,10 @@ describe('Remix API Actions', () => { envelopeType: ['transaction', 'event'], }); - const [transaction] = envelopes.filter(envelope => envelope[1].type === 'transaction'); - const [event] = envelopes.filter(envelope => envelope[1].type === 'event'); + const [transaction] = envelopes.filter(envelope => envelope[1]?.type === 'transaction'); + const [event] = envelopes.filter(envelope => envelope[1]?.type === 'event'); - assertSentryTransaction(transaction[2], { + assertSentryTransaction(transaction![2]!, { contexts: { trace: { op: 'http.server', @@ -231,7 +231,7 @@ describe('Remix API Actions', () => { transaction: `routes/action-json-response${useV2 ? '.' : '/'}$id`, }); - assertSentryEvent(event[2], { + assertSentryEvent(event![2]!, { transaction: expect.stringMatching(/routes\/action-json-response(\/|\.)\$id/), exception: { values: [ @@ -263,10 +263,10 @@ describe('Remix API Actions', () => { envelopeType: ['transaction', 'event'], }); - const [transaction] = envelopes.filter(envelope => envelope[1].type === 'transaction'); - const [event] = envelopes.filter(envelope => envelope[1].type === 'event'); + const [transaction] = envelopes.filter(envelope => envelope[1]?.type === 'transaction'); + const [event] = envelopes.filter(envelope => envelope[1]?.type === 'event'); - assertSentryTransaction(transaction[2], { + assertSentryTransaction(transaction![2]!, { contexts: { trace: { op: 'http.server', @@ -280,7 +280,7 @@ describe('Remix API Actions', () => { transaction: `routes/action-json-response${useV2 ? '.' : '/'}$id`, }); - assertSentryEvent(event[2], { + assertSentryEvent(event![2]!, { transaction: expect.stringMatching(/routes\/action-json-response(\/|\.)\$id/), exception: { values: [ @@ -312,10 +312,10 @@ describe('Remix API Actions', () => { envelopeType: ['transaction', 'event'], }); - const [transaction] = envelopes.filter(envelope => envelope[1].type === 'transaction'); - const [event] = envelopes.filter(envelope => envelope[1].type === 'event'); + const [transaction] = envelopes.filter(envelope => envelope[1]?.type === 'transaction'); + const [event] = envelopes.filter(envelope => envelope[1]?.type === 'event'); - assertSentryTransaction(transaction[2], { + assertSentryTransaction(transaction![2]!, { contexts: { trace: { op: 'http.server', @@ -329,7 +329,7 @@ describe('Remix API Actions', () => { transaction: `routes/action-json-response${useV2 ? '.' : '/'}$id`, }); - assertSentryEvent(event[2], { + assertSentryEvent(event![2]!, { transaction: expect.stringMatching(/routes\/action-json-response(\/|\.)\$id/), exception: { values: [ @@ -361,10 +361,10 @@ describe('Remix API Actions', () => { envelopeType: ['transaction', 'event'], }); - const [transaction] = envelopes.filter(envelope => envelope[1].type === 'transaction'); - const [event] = envelopes.filter(envelope => envelope[1].type === 'event'); + const [transaction] = envelopes.filter(envelope => envelope[1]?.type === 'transaction'); + const [event] = envelopes.filter(envelope => envelope[1]?.type === 'event'); - assertSentryTransaction(transaction[2], { + assertSentryTransaction(transaction![2]!, { contexts: { trace: { op: 'http.server', @@ -378,7 +378,7 @@ describe('Remix API Actions', () => { transaction: `routes/action-json-response${useV2 ? '.' : '/'}$id`, }); - assertSentryEvent(event[2], { + assertSentryEvent(event![2]!, { transaction: expect.stringMatching(/routes\/action-json-response(\/|\.)\$id/), exception: { values: [ @@ -410,10 +410,10 @@ describe('Remix API Actions', () => { envelopeType: ['event', 'transaction'], }); - const [transaction] = envelopes.filter(envelope => envelope[1].type === 'transaction'); - const [event] = envelopes.filter(envelope => envelope[1].type === 'event'); + const [transaction] = envelopes.filter(envelope => envelope[1]?.type === 'transaction'); + const [event] = envelopes.filter(envelope => envelope[1]?.type === 'event'); - assertSentryTransaction(transaction[2], { + assertSentryTransaction(transaction![2]!, { contexts: { trace: { op: 'http.server', @@ -427,7 +427,7 @@ describe('Remix API Actions', () => { transaction: `routes/server-side-unexpected-errors${useV2 ? '.' : '/'}$id`, }); - assertSentryEvent(event[2], { + assertSentryEvent(event![2]!, { transaction: expect.stringMatching(/routes\/server-side-unexpected-errors(\/|\.)\$id/), exception: { values: [ @@ -459,10 +459,10 @@ describe('Remix API Actions', () => { envelopeType: ['event', 'transaction'], }); - const [transaction] = envelopes.filter(envelope => envelope[1].type === 'transaction'); - const [event] = envelopes.filter(envelope => envelope[1].type === 'event'); + const [transaction] = envelopes.filter(envelope => envelope[1]?.type === 'transaction'); + const [event] = envelopes.filter(envelope => envelope[1]?.type === 'event'); - assertSentryTransaction(transaction[2], { + assertSentryTransaction(transaction![2]!, { contexts: { trace: { op: 'http.server', @@ -476,7 +476,7 @@ describe('Remix API Actions', () => { transaction: `routes/server-side-unexpected-errors${useV2 ? '.' : '/'}$id`, }); - assertSentryEvent(event[2], { + assertSentryEvent(event![2]!, { transaction: expect.stringMatching(/routes\/server-side-unexpected-errors(\/|\.)\$id/), exception: { values: [ diff --git a/packages/remix/test/integration/test/server/instrumentation-legacy/loader.test.ts b/packages/remix/test/integration/test/server/instrumentation-legacy/loader.test.ts index a90dda8b60bd..d97427d4f67a 100644 --- a/packages/remix/test/integration/test/server/instrumentation-legacy/loader.test.ts +++ b/packages/remix/test/integration/test/server/instrumentation-legacy/loader.test.ts @@ -11,10 +11,10 @@ describe('Remix API Loaders', () => { const envelopes = await env.getMultipleEnvelopeRequest({ url, count: 2, envelopeType: ['transaction', 'event'] }); - const event = envelopes[0][2].type === 'transaction' ? envelopes[1][2] : envelopes[0][2]; - const transaction = envelopes[0][2].type === 'transaction' ? envelopes[0][2] : envelopes[1][2]; + const event = envelopes[0]?.[2]?.type === 'transaction' ? envelopes[1]?.[2] : envelopes[0]?.[2]; + const transaction = envelopes[0]?.[2]?.type === 'transaction' ? envelopes[0]?.[2] : envelopes[1]?.[2]; - assertSentryTransaction(transaction, { + assertSentryTransaction(transaction!, { contexts: { trace: { status: 'internal_error', @@ -25,7 +25,7 @@ describe('Remix API Loaders', () => { }, }); - assertSentryEvent(event, { + assertSentryEvent(event!, { transaction: expect.stringMatching(/routes\/loader-json-response(\/|\.)\$id/), exception: { values: [ @@ -54,10 +54,10 @@ describe('Remix API Loaders', () => { // but otherwise this may leak into another test const envelopes = await env.getMultipleEnvelopeRequest({ url, count: 2, envelopeType: ['event', 'transaction'] }); - const event = envelopes[0][2].type === 'transaction' ? envelopes[1][2] : envelopes[0][2]; - const transaction = envelopes[0][2].type === 'transaction' ? envelopes[0][2] : envelopes[1][2]; + const event = envelopes[0]?.[2]?.type === 'transaction' ? envelopes[1]?.[2] : envelopes[0]?.[2]; + const transaction = envelopes[0]?.[2]?.type === 'transaction' ? envelopes[0]?.[2] : envelopes[1]?.[2]; - assertSentryTransaction(transaction, { + assertSentryTransaction(transaction!, { contexts: { trace: { status: 'internal_error', @@ -68,7 +68,7 @@ describe('Remix API Loaders', () => { }, }); - assertSentryEvent(event, { + assertSentryEvent(event!, { transaction: expect.stringMatching(/routes\/loader-throw-response(\/|\.)\$id/), exception: { values: [ @@ -93,7 +93,7 @@ describe('Remix API Loaders', () => { const env = await RemixTestEnv.init(); const url = `${env.url}/loader-json-response/123123`; const envelope = await env.getEnvelopeRequest({ url, envelopeType: 'transaction' }); - const transaction = envelope[2]; + const transaction = envelope[2]!; assertSentryTransaction(transaction, { transaction: `routes/loader-json-response${useV2 ? '.' : '/'}$id`, @@ -127,10 +127,10 @@ describe('Remix API Loaders', () => { envelopeType: ['transaction', 'event'], }); - const [transaction_1, transaction_2] = envelopes.filter(envelope => envelope[1].type === 'transaction'); - const [event] = envelopes.filter(envelope => envelope[1].type === 'event'); + const [transaction_1, transaction_2] = envelopes.filter(envelope => envelope[1]?.type === 'transaction'); + const [event] = envelopes.filter(envelope => envelope[1]?.type === 'event'); - assertSentryTransaction(transaction_1[2], { + assertSentryTransaction(transaction_1![2]!, { contexts: { trace: { op: 'http.server', @@ -144,7 +144,7 @@ describe('Remix API Loaders', () => { transaction: `routes/loader-json-response${useV2 ? '.' : '/'}$id`, }); - assertSentryTransaction(transaction_2[2], { + assertSentryTransaction(transaction_2![2]!, { contexts: { trace: { op: 'http.server', @@ -158,7 +158,7 @@ describe('Remix API Loaders', () => { transaction: `routes/loader-json-response${useV2 ? '.' : '/'}$id`, }); - assertSentryEvent(event[2], { + assertSentryEvent(event![2]!, { transaction: expect.stringMatching(/routes\/loader-json-response(\/|\.)\$id/), exception: { values: [ @@ -192,11 +192,11 @@ describe('Remix API Loaders', () => { await new Promise(resolve => env.server.close(resolve)); envelopes.forEach(envelope => { - const tags = envelope[2].tags as NonNullable; + const tags = envelope[2]?.tags as NonNullable; const customTagArr = Object.keys(tags).filter(t => t.startsWith('tag')); expect(customTagArr).toHaveLength(1); - const key = customTagArr[0]; + const key = customTagArr[0]!; const val = key[key.length - 1]; expect(tags[key]).toEqual(val); }); @@ -215,11 +215,11 @@ describe('Remix API Loaders', () => { }); const envelope = await env.getEnvelopeRequest({ url, envelopeType: 'transaction' }); - expect(envelope[0].trace).toMatchObject({ + expect(envelope[0]?.trace).toMatchObject({ trace_id: '12312012123120121231201212312012', }); - assertSentryTransaction(envelope[2], { + assertSentryTransaction(envelope![2]!, { contexts: { trace: { trace_id: '12312012123120121231201212312012', @@ -233,7 +233,7 @@ describe('Remix API Loaders', () => { const env = await RemixTestEnv.init(); const url = `${env.url}/loader-defer-response/123123`; const envelope = await env.getEnvelopeRequest({ url, envelopeType: 'transaction' }); - const transaction = envelope[2]; + const transaction = envelope[2]!; assertSentryTransaction(transaction, { transaction: useV2 ? 'routes/loader-defer-response.$id' : 'routes/loader-defer-response/$id', diff --git a/packages/remix/test/integration/test/server/instrumentation-legacy/ssr.test.ts b/packages/remix/test/integration/test/server/instrumentation-legacy/ssr.test.ts index 8400b9f9cff2..e67258b9e14d 100644 --- a/packages/remix/test/integration/test/server/instrumentation-legacy/ssr.test.ts +++ b/packages/remix/test/integration/test/server/instrumentation-legacy/ssr.test.ts @@ -8,9 +8,9 @@ describe('Server Side Rendering', () => { const env = await RemixTestEnv.init(); const url = `${env.url}/ssr-error`; const envelopes = await env.getMultipleEnvelopeRequest({ url, count: 2, envelopeType: ['transaction', 'event'] }); - const [transaction] = envelopes.filter(envelope => envelope[1].type === 'transaction'); - const [event] = envelopes.filter(envelope => envelope[1].type === 'event'); - assertSentryTransaction(transaction[2], { + const [transaction] = envelopes.filter(envelope => envelope[1]?.type === 'transaction'); + const [event] = envelopes.filter(envelope => envelope[1]?.type === 'event'); + assertSentryTransaction(transaction![2]!, { contexts: { trace: { status: 'internal_error', @@ -27,7 +27,7 @@ describe('Server Side Rendering', () => { : {}, }); - assertSentryEvent(event[2], { + assertSentryEvent(event![2]!, { transaction: 'routes/ssr-error', exception: { values: [ diff --git a/packages/remix/test/integration/test/tsconfig.json b/packages/remix/test/integration/test/tsconfig.json new file mode 100644 index 000000000000..105334e2253a --- /dev/null +++ b/packages/remix/test/integration/test/tsconfig.json @@ -0,0 +1,3 @@ +{ + "extends": "../../../tsconfig.test.json" +} diff --git a/packages/replay-internal/src/coreHandlers/util/networkUtils.ts b/packages/replay-internal/src/coreHandlers/util/networkUtils.ts index b6ae6ff5b13e..06e96b7ab7df 100644 --- a/packages/replay-internal/src/coreHandlers/util/networkUtils.ts +++ b/packages/replay-internal/src/coreHandlers/util/networkUtils.ts @@ -189,11 +189,11 @@ export function buildNetworkRequestOrResponse( /** Filter a set of headers */ export function getAllowedHeaders(headers: Record, allowedHeaders: string[]): Record { - return Object.keys(headers).reduce((filteredHeaders: Record, key: string) => { + return Object.entries(headers).reduce((filteredHeaders: Record, [key, value]) => { const normalizedKey = key.toLowerCase(); // Avoid putting empty strings into the headers if (allowedHeaders.includes(normalizedKey) && headers[key]) { - filteredHeaders[normalizedKey] = headers[key]; + filteredHeaders[normalizedKey] = value; } return filteredHeaders; }, {}); diff --git a/packages/replay-internal/src/coreHandlers/util/xhrUtils.ts b/packages/replay-internal/src/coreHandlers/util/xhrUtils.ts index fa504dcdeec2..b86e2d2991a9 100644 --- a/packages/replay-internal/src/coreHandlers/util/xhrUtils.ts +++ b/packages/replay-internal/src/coreHandlers/util/xhrUtils.ts @@ -136,8 +136,10 @@ function getResponseHeaders(xhr: XMLHttpRequest): Record { } return headers.split('\r\n').reduce((acc: Record, line: string) => { - const [key, value] = line.split(': '); - acc[key.toLowerCase()] = value; + const [key, value] = line.split(': ') as [string, string | undefined]; + if (value) { + acc[key.toLowerCase()] = value; + } return acc; }, {}); } diff --git a/packages/replay-internal/src/util/createPerformanceEntries.ts b/packages/replay-internal/src/util/createPerformanceEntries.ts index b7cca6b05ddf..28ccf60280e8 100644 --- a/packages/replay-internal/src/util/createPerformanceEntries.ts +++ b/packages/replay-internal/src/util/createPerformanceEntries.ts @@ -72,11 +72,12 @@ export function createPerformanceEntries( } function createPerformanceEntry(entry: AllPerformanceEntry): ReplayPerformanceEntry | null { - if (!ENTRY_TYPES[entry.entryType]) { + const entryType = ENTRY_TYPES[entry.entryType]; + if (!entryType) { return null; } - return ENTRY_TYPES[entry.entryType](entry); + return entryType(entry); } function getAbsoluteTime(time: number): number { @@ -192,7 +193,11 @@ export function getLargestContentfulPaint(metric: Metric): ReplayPerformanceEntr export function getCumulativeLayoutShift(metric: Metric): ReplayPerformanceEntry { // get first node that shifts const firstEntry = metric.entries[0] as (PerformanceEntry & { sources?: LayoutShiftAttribution[] }) | undefined; - const node = firstEntry ? (firstEntry.sources ? firstEntry.sources[0].node : undefined) : undefined; + const node = firstEntry + ? firstEntry.sources && firstEntry.sources[0] + ? firstEntry.sources[0].node + : undefined + : undefined; return getWebVital(metric, 'cumulative-layout-shift', node); } diff --git a/packages/replay-internal/test/integration/coreHandlers/handleBeforeSendEvent.test.ts b/packages/replay-internal/test/integration/coreHandlers/handleBeforeSendEvent.test.ts index 8cd9fda46247..1e6067f0c0cb 100644 --- a/packages/replay-internal/test/integration/coreHandlers/handleBeforeSendEvent.test.ts +++ b/packages/replay-internal/test/integration/coreHandlers/handleBeforeSendEvent.test.ts @@ -29,7 +29,7 @@ describe('Integration | coreHandlers | handleBeforeSendEvent', () => { const addBreadcrumbSpy = vi.spyOn(replay, 'throttledAddEvent'); const error = Error(); - error.exception.values[0].value = + error.exception.values[0]!.value = 'Text content does not match server-rendered HTML. Warning: Text content did not match.'; handler(error); @@ -63,7 +63,7 @@ describe('Integration | coreHandlers | handleBeforeSendEvent', () => { const addBreadcrumbSpy = vi.spyOn(replay, 'throttledAddEvent'); const error = Error(); - error.exception.values[0].value = 'https://reactjs.org/docs/error-decoder.html?invariant=423'; + error.exception.values[0]!.value = 'https://reactjs.org/docs/error-decoder.html?invariant=423'; handler(error); expect(addBreadcrumbSpy).toHaveBeenCalledTimes(1); diff --git a/packages/replay-internal/test/integration/sendReplayEvent.test.ts b/packages/replay-internal/test/integration/sendReplayEvent.test.ts index 8e99f72ff517..9b318700a6fa 100644 --- a/packages/replay-internal/test/integration/sendReplayEvent.test.ts +++ b/packages/replay-internal/test/integration/sendReplayEvent.test.ts @@ -395,7 +395,7 @@ describe('Integration | sendReplayEvent', () => { expect(spyHandleException).toHaveBeenLastCalledWith(new Error('Unable to send Replay - max retries exceeded')); const spyHandleExceptionCall = spyHandleException.mock.calls; - expect(spyHandleExceptionCall[spyHandleExceptionCall.length - 1][0].cause.message).toEqual( + expect(spyHandleExceptionCall[spyHandleExceptionCall.length - 1][0]?.cause.message).toEqual( 'Something bad happened', ); diff --git a/packages/replay-worker/src/Compressor.ts b/packages/replay-worker/src/Compressor.ts index 80a5fa1822ac..712481aa62d7 100644 --- a/packages/replay-worker/src/Compressor.ts +++ b/packages/replay-worker/src/Compressor.ts @@ -90,15 +90,16 @@ function mergeUInt8Arrays(chunks: Uint8Array[]): Uint8Array { // calculate data length let len = 0; - for (let i = 0, l = chunks.length; i < l; i++) { - len += chunks[i].length; + for (const chunk of chunks) { + len += chunk.length; } // join chunks const result = new Uint8Array(len); for (let i = 0, pos = 0, l = chunks.length; i < l; i++) { - const chunk = chunks[i]; + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion + const chunk = chunks[i]!; result.set(chunk, pos); pos += chunk.length; } diff --git a/packages/svelte/src/preprocessors.ts b/packages/svelte/src/preprocessors.ts index a258d4becff4..c966c6e00eef 100644 --- a/packages/svelte/src/preprocessors.ts +++ b/packages/svelte/src/preprocessors.ts @@ -116,7 +116,8 @@ function shouldInjectFunction( function getBaseName(filename: string): string { const segments = filename.split('/'); - return segments[segments.length - 1].replace('.svelte', ''); + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion + return segments[segments.length - 1]!.replace('.svelte', ''); } function hasScriptTag(content: string): boolean { diff --git a/packages/svelte/test/config.test.ts b/packages/svelte/test/config.test.ts index c571ef6fb74e..7d1255e120d0 100644 --- a/packages/svelte/test/config.test.ts +++ b/packages/svelte/test/config.test.ts @@ -42,7 +42,7 @@ describe('withSentryConfig', () => { expect(Array.isArray(wrappedConfig.preprocess)).toBe(true); expect(wrappedConfig).toEqual({ ...originalConfig, preprocess: expect.any(Array) }); expect(wrappedConfig.preprocess).toHaveLength(originalNumberOfPreprocs + 1); - expect((wrappedConfig.preprocess as SentryPreprocessorGroup[])[0].sentryId).toEqual( + expect((wrappedConfig.preprocess as SentryPreprocessorGroup[])[0]?.sentryId).toEqual( FIRST_PASS_COMPONENT_TRACKING_PREPROC_ID, ); }); diff --git a/packages/svelte/test/performance.test.ts b/packages/svelte/test/performance.test.ts index 99ac99698682..758207797284 100644 --- a/packages/svelte/test/performance.test.ts +++ b/packages/svelte/test/performance.test.ts @@ -46,7 +46,7 @@ describe('Sentry.trackComponent()', () => { const rootSpanId = transaction.contexts?.trace?.span_id; expect(rootSpanId).toBeDefined(); - const initSpanId = transaction.spans![0].span_id; + const initSpanId = transaction.spans![0]?.span_id; expect(transaction.spans![0]).toEqual({ data: { @@ -101,7 +101,7 @@ describe('Sentry.trackComponent()', () => { const rootSpanId = transaction.contexts?.trace?.span_id; expect(rootSpanId).toBeDefined(); - const initSpanId = transaction.spans![0].span_id; + const initSpanId = transaction.spans![0]?.span_id; expect(transaction.spans![0]).toEqual({ data: { @@ -162,7 +162,7 @@ describe('Sentry.trackComponent()', () => { const transaction = transactions[0]; expect(transaction.spans).toHaveLength(1); - expect(transaction.spans![0].op).toEqual('ui.svelte.init'); + expect(transaction.spans![0]?.op).toEqual('ui.svelte.init'); }); it('only creates update spans if trackInit is deactivated', async () => { @@ -178,7 +178,7 @@ describe('Sentry.trackComponent()', () => { const transaction = transactions[0]; expect(transaction.spans).toHaveLength(1); - expect(transaction.spans![0].op).toEqual('ui.svelte.update'); + expect(transaction.spans![0]?.op).toEqual('ui.svelte.update'); }); it('creates no spans if trackInit and trackUpdates are deactivated', async () => { @@ -210,8 +210,8 @@ describe('Sentry.trackComponent()', () => { const transaction = transactions[0]; expect(transaction.spans).toHaveLength(2); - expect(transaction.spans![0].description).toEqual(''); - expect(transaction.spans![1].description).toEqual(''); + expect(transaction.spans![0]?.description).toEqual(''); + expect(transaction.spans![1]?.description).toEqual(''); }); it("doesn't do anything, if there's no ongoing transaction", async () => { @@ -243,7 +243,7 @@ describe('Sentry.trackComponent()', () => { // One update span is triggered by the initial rendering, but the second one is not captured expect(transaction.spans).toHaveLength(2); - expect(transaction.spans![0].op).toEqual('ui.svelte.init'); - expect(transaction.spans![1].op).toEqual('ui.svelte.update'); + expect(transaction.spans![0]?.op).toEqual('ui.svelte.init'); + expect(transaction.spans![1]?.op).toEqual('ui.svelte.update'); }); }); diff --git a/packages/typescript/tsconfig.json b/packages/typescript/tsconfig.json index d2457663e802..bee7b140cf96 100644 --- a/packages/typescript/tsconfig.json +++ b/packages/typescript/tsconfig.json @@ -19,6 +19,7 @@ "sourceMap": true, "strict": true, "strictBindCallApply": false, - "target": "es2018" + "target": "es2018", + "noUncheckedIndexedAccess": true } } diff --git a/packages/utils/src/baggage.ts b/packages/utils/src/baggage.ts index b0e506b8938a..8cc2dfd68ef2 100644 --- a/packages/utils/src/baggage.ts +++ b/packages/utils/src/baggage.ts @@ -97,9 +97,9 @@ export function parseBaggageHeader( // Combine all baggage headers into one object containing the baggage values so we can later read the Sentry-DSC-values from it return baggageHeader.reduce>((acc, curr) => { const currBaggageObject = baggageHeaderToObject(curr); - for (const key of Object.keys(currBaggageObject)) { - acc[key] = currBaggageObject[key]; - } + Object.entries(currBaggageObject).forEach(([key, value]) => { + acc[key] = value; + }); return acc; }, {}); } @@ -118,7 +118,9 @@ function baggageHeaderToObject(baggageHeader: string): Record { .split(',') .map(baggageEntry => baggageEntry.split('=').map(keyOrValue => decodeURIComponent(keyOrValue.trim()))) .reduce>((acc, [key, value]) => { - acc[key] = value; + if (key && value) { + acc[key] = value; + } return acc; }, {}); } diff --git a/packages/utils/src/browser.ts b/packages/utils/src/browser.ts index 371e7e96c8c2..ce00f2556d05 100644 --- a/packages/utils/src/browser.ts +++ b/packages/utils/src/browser.ts @@ -75,11 +75,6 @@ function _htmlElementAsString(el: unknown, keyAttrs?: string[]): string { }; const out = []; - let className; - let classes; - let key; - let attr; - let i; if (!elem || !elem.tagName) { return ''; @@ -115,22 +110,22 @@ function _htmlElementAsString(el: unknown, keyAttrs?: string[]): string { out.push(`#${elem.id}`); } - className = elem.className; + const className = elem.className; if (className && isString(className)) { - classes = className.split(/\s+/); - for (i = 0; i < classes.length; i++) { - out.push(`.${classes[i]}`); + const classes = className.split(/\s+/); + for (const c of classes) { + out.push(`.${c}`); } } } const allowedAttrs = ['aria-label', 'type', 'name', 'title', 'alt']; - for (i = 0; i < allowedAttrs.length; i++) { - key = allowedAttrs[i]; - attr = elem.getAttribute(key); + for (const k of allowedAttrs) { + const attr = elem.getAttribute(k); if (attr) { - out.push(`[${key}="${attr}"]`); + out.push(`[${k}="${attr}"]`); } } + return out.join(''); } diff --git a/packages/utils/src/dsn.ts b/packages/utils/src/dsn.ts index 7bf735c10780..5ca4aa96c180 100644 --- a/packages/utils/src/dsn.ts +++ b/packages/utils/src/dsn.ts @@ -45,7 +45,7 @@ export function dsnFromString(str: string): DsnComponents | undefined { return undefined; } - const [protocol, publicKey, pass = '', host, port = '', lastPath] = match.slice(1); + const [protocol, publicKey, pass = '', host = '', port = '', lastPath = ''] = match.slice(1); let path = ''; let projectId = lastPath; diff --git a/packages/utils/src/misc.ts b/packages/utils/src/misc.ts index c8afc0818909..ee48a2d60c2d 100644 --- a/packages/utils/src/misc.ts +++ b/packages/utils/src/misc.ts @@ -38,7 +38,8 @@ export function uuid4(): string { // @see https://developer.mozilla.org/en-US/docs/Web/API/Crypto/getRandomValues#typedarray const typedArray = new Uint8Array(1); crypto.getRandomValues(typedArray); - return typedArray[0]; + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion + return typedArray[0]!; }; } } catch (_) { @@ -135,15 +136,19 @@ interface SemVer { buildmetadata?: string; } +function _parseInt(input: string | undefined): number { + return parseInt(input || '', 10); +} + /** * Parses input into a SemVer interface * @param input string representation of a semver version */ export function parseSemver(input: string): SemVer { const match = input.match(SEMVER_REGEXP) || []; - const major = parseInt(match[1], 10); - const minor = parseInt(match[2], 10); - const patch = parseInt(match[3], 10); + const major = _parseInt(match[1]); + const minor = _parseInt(match[2]); + const patch = _parseInt(match[3]); return { buildmetadata: match[5], major: isNaN(major) ? undefined : major, @@ -173,7 +178,11 @@ export function addContextToFrame(lines: string[], frame: StackFrame, linesOfCon .slice(Math.max(0, sourceLine - linesOfContext), sourceLine) .map((line: string) => snipLine(line, 0)); - frame.context_line = snipLine(lines[Math.min(maxLines - 1, sourceLine)], frame.colno || 0); + // We guard here to ensure this is not larger than the existing number of lines + const lineIndex = Math.min(maxLines - 1, sourceLine); + + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion + frame.context_line = snipLine(lines[lineIndex]!, frame.colno || 0); frame.post_context = lines .slice(Math.min(sourceLine + 1, maxLines), sourceLine + 1 + linesOfContext) diff --git a/packages/utils/src/node-stack-trace.ts b/packages/utils/src/node-stack-trace.ts index 6bb1877c870d..7748046528a4 100644 --- a/packages/utils/src/node-stack-trace.ts +++ b/packages/utils/src/node-stack-trace.ts @@ -116,9 +116,9 @@ export function node(getModule?: GetModuleFn): StackLineParserFn { filename, module: getModule ? getModule(filename) : undefined, function: functionName, - lineno: parseInt(lineMatch[3], 10) || undefined, - colno: parseInt(lineMatch[4], 10) || undefined, - in_app: filenameIsInApp(filename, isNative), + lineno: _parseIntOrUndefined(lineMatch[3]), + colno: _parseIntOrUndefined(lineMatch[4]), + in_app: filenameIsInApp(filename || '', isNative), }; } @@ -141,3 +141,7 @@ export function node(getModule?: GetModuleFn): StackLineParserFn { export function nodeStackLineParser(getModule?: GetModuleFn): StackLineParser { return [90, node(getModule)]; } + +function _parseIntOrUndefined(input: string | undefined): number | undefined { + return parseInt(input || '', 10) || undefined; +} diff --git a/packages/utils/src/object.ts b/packages/utils/src/object.ts index 9ae3e00e05ce..95346cf1f812 100644 --- a/packages/utils/src/object.ts +++ b/packages/utils/src/object.ts @@ -182,12 +182,14 @@ export function extractExceptionKeysForMessage(exception: Record= maxLength) { - return truncate(keys[0], maxLength); + if (firstKey.length >= maxLength) { + return truncate(firstKey, maxLength); } for (let includedKeys = keys.length; includedKeys > 0; includedKeys--) { diff --git a/packages/utils/src/path.ts b/packages/utils/src/path.ts index 7a65aa57b7c8..ed14e85a3a92 100644 --- a/packages/utils/src/path.ts +++ b/packages/utils/src/path.ts @@ -183,7 +183,7 @@ export function join(...args: string[]): string { /** JSDoc */ export function dirname(path: string): string { const result = splitPath(path); - const root = result[0]; + const root = result[0] || ''; let dir = result[1]; if (!root && !dir) { @@ -201,7 +201,7 @@ export function dirname(path: string): string { /** JSDoc */ export function basename(path: string, ext?: string): string { - let f = splitPath(path)[2]; + let f = splitPath(path)[2] || ''; if (ext && f.slice(ext.length * -1) === ext) { f = f.slice(0, f.length - ext.length); } diff --git a/packages/utils/src/promisebuffer.ts b/packages/utils/src/promisebuffer.ts index 0ad4991cfb48..32b90e4d8519 100644 --- a/packages/utils/src/promisebuffer.ts +++ b/packages/utils/src/promisebuffer.ts @@ -26,8 +26,8 @@ export function makePromiseBuffer(limit?: number): PromiseBuffer { * @param task Can be any PromiseLike * @returns Removed promise. */ - function remove(task: PromiseLike): PromiseLike { - return buffer.splice(buffer.indexOf(task), 1)[0]; + function remove(task: PromiseLike): PromiseLike { + return buffer.splice(buffer.indexOf(task), 1)[0] || Promise.resolve(undefined); } /** diff --git a/packages/utils/src/ratelimit.ts b/packages/utils/src/ratelimit.ts index 77be8cf7fa9e..6131cff2bb2c 100644 --- a/packages/utils/src/ratelimit.ts +++ b/packages/utils/src/ratelimit.ts @@ -78,7 +78,7 @@ export function updateRateLimits( * Only present if rate limit applies to the metric_bucket data category. */ for (const limit of rateLimitHeader.trim().split(',')) { - const [retryAfter, categories, , , namespaces] = limit.split(':', 5); + const [retryAfter, categories, , , namespaces] = limit.split(':', 5) as [string, ...string[]]; const headerDelay = parseInt(retryAfter, 10); const delay = (!isNaN(headerDelay) ? headerDelay : 60) * 1000; // 60sec default if (!categories) { diff --git a/packages/utils/src/stacktrace.ts b/packages/utils/src/stacktrace.ts index dfb2a6e6269f..7fddda6db158 100644 --- a/packages/utils/src/stacktrace.ts +++ b/packages/utils/src/stacktrace.ts @@ -21,7 +21,7 @@ export function createStackParser(...parsers: StackLineParser[]): StackParser { const lines = stack.split('\n'); for (let i = skipFirstLines; i < lines.length; i++) { - const line = lines[i]; + const line = lines[i] as string; // Ignore lines over 1kb as they are unlikely to be stack frames. // Many of the regular expressions use backtracking which results in run time that increases exponentially with // input size. Huge strings can result in hangs/Denial of Service: @@ -85,7 +85,7 @@ export function stripSentryFramesAndReverse(stack: ReadonlyArray): S const localStack = Array.from(stack); // If stack starts with one of our API calls, remove it (starts, meaning it's the top of the stack - aka last call) - if (/sentryWrapped/.test(localStack[localStack.length - 1].function || '')) { + if (/sentryWrapped/.test(getLastStackFrame(localStack).function || '')) { localStack.pop(); } @@ -93,7 +93,7 @@ export function stripSentryFramesAndReverse(stack: ReadonlyArray): S localStack.reverse(); // If stack ends with one of our internal API calls, remove it (ends, meaning it's the bottom of the stack - aka top-most call) - if (STRIP_FRAME_REGEXP.test(localStack[localStack.length - 1].function || '')) { + if (STRIP_FRAME_REGEXP.test(getLastStackFrame(localStack).function || '')) { localStack.pop(); // When using synthetic events, we will have a 2 levels deep stack, as `new Error('Sentry syntheticException')` @@ -104,18 +104,22 @@ export function stripSentryFramesAndReverse(stack: ReadonlyArray): S // // instead of just the top `Sentry` call itself. // This forces us to possibly strip an additional frame in the exact same was as above. - if (STRIP_FRAME_REGEXP.test(localStack[localStack.length - 1].function || '')) { + if (STRIP_FRAME_REGEXP.test(getLastStackFrame(localStack).function || '')) { localStack.pop(); } } return localStack.slice(0, STACKTRACE_FRAME_LIMIT).map(frame => ({ ...frame, - filename: frame.filename || localStack[localStack.length - 1].filename, + filename: frame.filename || getLastStackFrame(localStack).filename, function: frame.function || UNKNOWN_FUNCTION, })); } +function getLastStackFrame(arr: StackFrame[]): StackFrame { + return arr[arr.length - 1] || {}; +} + const defaultFunctionName = ''; /** diff --git a/packages/utils/src/url.ts b/packages/utils/src/url.ts index b7b11138d3c7..e324f41f82a3 100644 --- a/packages/utils/src/url.ts +++ b/packages/utils/src/url.ts @@ -45,8 +45,7 @@ export function parseUrl(url: string): PartialURL { * @returns URL or path without query string or fragment */ export function stripUrlQueryAndFragment(urlPath: string): string { - // eslint-disable-next-line no-useless-escape - return urlPath.split(/[\?#]/, 1)[0]; + return (urlPath.split(/[?#]/, 1) as [string, ...string[]])[0]; } /** diff --git a/packages/utils/src/worldwide.ts b/packages/utils/src/worldwide.ts index 0263fc5ec719..7e428444e21d 100644 --- a/packages/utils/src/worldwide.ts +++ b/packages/utils/src/worldwide.ts @@ -24,7 +24,7 @@ interface SentryCarrier { globalScope?: Scope; defaultIsolationScope?: Scope; defaultCurrentScope?: Scope; - globalMetricsAggregators: WeakMap | undefined; + globalMetricsAggregators?: WeakMap | undefined; /** Overwrites TextEncoder used in `@sentry/utils`, need for `react-native@0.73` and older */ encodePolyfill?: (input: string) => Uint8Array; diff --git a/packages/utils/test/aggregate-errors.test.ts b/packages/utils/test/aggregate-errors.test.ts index 8d5fb3be6ded..cebea7f441a4 100644 --- a/packages/utils/test/aggregate-errors.test.ts +++ b/packages/utils/test/aggregate-errors.test.ts @@ -153,7 +153,7 @@ describe('applyAggregateErrorsToEvent()', () => { const eventHint: EventHint = { originalException: fakeAggregateError }; applyAggregateErrorsToEvent(exceptionFromError, stackParser, undefined, 'cause', 100, event, eventHint); - expect(event.exception?.values?.[event.exception.values.length - 1].mechanism?.type).toBe('instrument'); + expect(event.exception?.values?.[event.exception.values.length - 1]?.mechanism?.type).toBe('instrument'); }); test('should recursively walk mixed errors (Aggregate errors and based on `key`)', () => { diff --git a/packages/utils/test/clientreport.test.ts b/packages/utils/test/clientreport.test.ts index d54ec311d839..04a2a4fe7334 100644 --- a/packages/utils/test/clientreport.test.ts +++ b/packages/utils/test/clientreport.test.ts @@ -33,7 +33,7 @@ describe('createClientReportEnvelope', () => { const items = env[1]; expect(items).toHaveLength(1); - const clientReportItem = items[0]; + const clientReportItem = items[0]!; expect(clientReportItem[0]).toEqual({ type: 'client_report' }); expect(clientReportItem[1]).toEqual({ timestamp: expect.any(Number), discarded_events: discardedEvents }); diff --git a/packages/utils/test/envelope.test.ts b/packages/utils/test/envelope.test.ts index 6f3a9ea3a6d6..fa2df9cc159a 100644 --- a/packages/utils/test/envelope.test.ts +++ b/packages/utils/test/envelope.test.ts @@ -71,7 +71,7 @@ describe('envelope', () => { measurements: { inp: { value: expect.any(Number), unit: expect.any(String) } }, }; - expect(spanEnvelopeItem[0].type).toBe('span'); + expect(spanEnvelopeItem[0]?.type).toBe('span'); expect(spanEnvelopeItem[1]).toMatchObject(expectedObj); }); }); @@ -209,7 +209,7 @@ describe('envelope', () => { let iteration = 0; forEachEnvelopeItem(env, (item, type) => { expect(item).toBe(items[iteration]); - expect(type).toBe(items[iteration][0].type); + expect(type).toBe(items[iteration]?.[0]?.type); iteration = iteration + 1; }); }); diff --git a/packages/utils/test/eventbuilder.test.ts b/packages/utils/test/eventbuilder.test.ts index c09697366b6f..7c67571e753e 100644 --- a/packages/utils/test/eventbuilder.test.ts +++ b/packages/utils/test/eventbuilder.test.ts @@ -30,7 +30,7 @@ describe('eventFromUnknownInput', () => { test('object with name prop', () => { const event = eventFromUnknownInput(fakeClient, stackParser, { foo: { bar: 'baz' }, name: 'BadType' }); - expect(event.exception?.values?.[0].value).toBe("'BadType' captured as exception"); + expect(event.exception?.values?.[0]?.value).toBe("'BadType' captured as exception"); expect(event.exception?.values?.[0]).toEqual( expect.objectContaining({ @@ -46,7 +46,7 @@ describe('eventFromUnknownInput', () => { test('object with name and message props', () => { const event = eventFromUnknownInput(fakeClient, stackParser, { message: 'went wrong', name: 'BadType' }); - expect(event.exception?.values?.[0].value).toBe("'BadType' captured as exception with message 'went wrong'"); + expect(event.exception?.values?.[0]?.value).toBe("'BadType' captured as exception with message 'went wrong'"); expect(event.exception?.values?.[0]).toEqual( expect.objectContaining({ @@ -150,6 +150,6 @@ describe('eventFromUnknownInput', () => { test('passing client directly', () => { const event = eventFromUnknownInput(fakeClient, stackParser, { foo: { bar: 'baz' }, prop: 1 }); - expect(event.exception?.values?.[0].value).toBe('Object captured as exception with keys: foo, prop'); + expect(event.exception?.values?.[0]?.value).toBe('Object captured as exception with keys: foo, prop'); }); }); diff --git a/packages/utils/test/misc.test.ts b/packages/utils/test/misc.test.ts index c1eb978dcdbe..14f3e88c0f0b 100644 --- a/packages/utils/test/misc.test.ts +++ b/packages/utils/test/misc.test.ts @@ -215,18 +215,18 @@ describe('addExceptionMechanism', () => { addExceptionMechanism(event); - expect(event.exception.values[0].mechanism).toEqual(defaultMechanism); + expect(event.exception.values[0]?.mechanism).toEqual(defaultMechanism); }); it('prefers current values to defaults', () => { const event = { ...baseEvent }; const nonDefaultMechanism = { type: 'instrument', handled: false }; - event.exception.values[0].mechanism = nonDefaultMechanism; + event.exception.values[0]!.mechanism = nonDefaultMechanism; addExceptionMechanism(event); - expect(event.exception.values[0].mechanism).toEqual(nonDefaultMechanism); + expect(event.exception.values[0]?.mechanism).toEqual(nonDefaultMechanism); }); it('prefers incoming values to current values', () => { @@ -234,12 +234,12 @@ describe('addExceptionMechanism', () => { const currentMechanism = { type: 'instrument', handled: false }; const newMechanism = { handled: true, synthetic: true }; - event.exception.values[0].mechanism = currentMechanism; + event.exception.values[0]!.mechanism = currentMechanism; addExceptionMechanism(event, newMechanism); // the new `handled` value took precedence - expect(event.exception.values[0].mechanism).toEqual({ type: 'instrument', handled: true, synthetic: true }); + expect(event.exception.values[0]?.mechanism).toEqual({ type: 'instrument', handled: true, synthetic: true }); }); it('merges data values', () => { @@ -247,11 +247,11 @@ describe('addExceptionMechanism', () => { const currentMechanism = { ...defaultMechanism, data: { function: 'addEventListener' } }; const newMechanism = { data: { handler: 'organizeShoes', target: 'closet' } }; - event.exception.values[0].mechanism = currentMechanism; + event.exception.values[0]!.mechanism = currentMechanism; addExceptionMechanism(event, newMechanism); - expect(event.exception.values[0].mechanism.data).toEqual({ + expect(event.exception.values[0]?.mechanism.data).toEqual({ function: 'addEventListener', handler: 'organizeShoes', target: 'closet', diff --git a/packages/utils/test/normalize.test.ts b/packages/utils/test/normalize.test.ts index c0bbe0298ba2..5a2414d52e43 100644 --- a/packages/utils/test/normalize.test.ts +++ b/packages/utils/test/normalize.test.ts @@ -178,8 +178,8 @@ describe('normalize()', () => { name: 'Alice', children: [{ name: 'Bob' }, { name: 'Eve' }], } as any; - obj.children[0].self = obj.children[0]; - obj.children[1].self = obj.children[1]; + obj.children[0]!.self = obj.children[0]; + obj.children[1]!.self = obj.children[1]; expect(normalize(obj)).toEqual({ name: 'Alice', children: [ @@ -269,7 +269,7 @@ describe('normalize()', () => { }, circular, ]; - circular.qux = circular.bar[0].baz; + circular.qux = circular.bar[0]?.baz; const normalized = normalize(circular); expect(normalized).toEqual({ @@ -283,9 +283,9 @@ describe('normalize()', () => { qux: '[Circular ~]', }); - expect(circular.bar[0].baz).toBe(circular); + expect(circular.bar[0]?.baz).toBe(circular); expect(circular.bar[1]).toBe(circular); - expect(circular.qux).toBe(circular.bar[0].baz); + expect(circular.qux).toBe(circular.bar[0]?.baz); expect(normalized).not.toBe(circular); }); diff --git a/packages/utils/test/object.test.ts b/packages/utils/test/object.test.ts index 6fbb69f0e2b4..2ba5a6c58fa3 100644 --- a/packages/utils/test/object.test.ts +++ b/packages/utils/test/object.test.ts @@ -286,7 +286,7 @@ describe('dropUndefinedKeys()', () => { // Returns new references within objects expect(chicken === droppedChicken.lays[0]).toBe(false); - expect(egg === droppedChicken.lays[0].lays).toBe(false); + expect(egg === droppedChicken.lays[0]?.lays).toBe(false); // Keeps circular reference expect(droppedChicken.lays[0] === droppedChicken).toBe(true); diff --git a/packages/utils/test/stacktrace.test.ts b/packages/utils/test/stacktrace.test.ts index 7e5251d0dd9c..f1ef0454a71a 100644 --- a/packages/utils/test/stacktrace.test.ts +++ b/packages/utils/test/stacktrace.test.ts @@ -14,8 +14,8 @@ describe('Stacktrace', () => { const frames = stripSentryFramesAndReverse(stack); expect(frames.length).toBe(2); - expect(frames[0].function).toBe('bar'); - expect(frames[1].function).toBe('foo'); + expect(frames[0]?.function).toBe('bar'); + expect(frames[1]?.function).toBe('foo'); }); it('reserved captureMessage', () => { @@ -29,8 +29,8 @@ describe('Stacktrace', () => { const frames = stripSentryFramesAndReverse(stack); expect(frames.length).toBe(2); - expect(frames[0].function).toBe('bar'); - expect(frames[1].function).toBe('foo'); + expect(frames[0]?.function).toBe('bar'); + expect(frames[1]?.function).toBe('foo'); }); it('remove two occurences if they are present', () => { @@ -44,8 +44,8 @@ describe('Stacktrace', () => { const exceptionFrames = stripSentryFramesAndReverse(exceptionStack); expect(exceptionFrames.length).toBe(2); - expect(exceptionFrames[0].function).toBe('bar'); - expect(exceptionFrames[1].function).toBe('foo'); + expect(exceptionFrames[0]?.function).toBe('bar'); + expect(exceptionFrames[1]?.function).toBe('foo'); const messageStack = [ { colno: 1, lineno: 4, filename: 'anything.js', function: 'captureMessage' }, @@ -57,8 +57,8 @@ describe('Stacktrace', () => { const messageFrames = stripSentryFramesAndReverse(messageStack); expect(messageFrames.length).toBe(2); - expect(messageFrames[0].function).toBe('bar'); - expect(messageFrames[1].function).toBe('foo'); + expect(messageFrames[0]?.function).toBe('bar'); + expect(messageFrames[1]?.function).toBe('foo'); }); }); @@ -74,8 +74,8 @@ describe('Stacktrace', () => { const frames = stripSentryFramesAndReverse(stack); expect(frames.length).toBe(2); - expect(frames[0].function).toBe('bar'); - expect(frames[1].function).toBe('foo'); + expect(frames[0]?.function).toBe('bar'); + expect(frames[1]?.function).toBe('foo'); }); }); @@ -92,8 +92,8 @@ describe('Stacktrace', () => { const frames = stripSentryFramesAndReverse(stack); expect(frames.length).toBe(2); - expect(frames[0].function).toBe('bar'); - expect(frames[1].function).toBe('foo'); + expect(frames[0]?.function).toBe('bar'); + expect(frames[1]?.function).toBe('foo'); }); it('applies frames limit after the stripping, not before', () => { @@ -111,8 +111,8 @@ describe('Stacktrace', () => { expect(frames.length).toBe(50); // Frames are named 0-54, thus after reversal and trimming, we should have frames 54-5, 50 in total. - expect(frames[0].function).toBe('54'); - expect(frames[49].function).toBe('5'); + expect(frames[0]?.function).toBe('54'); + expect(frames[49]?.function).toBe('5'); }); }); }); diff --git a/packages/vercel-edge/test/wintercg-fetch.test.ts b/packages/vercel-edge/test/wintercg-fetch.test.ts index 79678046d02d..ae830ed9c1e8 100644 --- a/packages/vercel-edge/test/wintercg-fetch.test.ts +++ b/packages/vercel-edge/test/wintercg-fetch.test.ts @@ -45,7 +45,7 @@ describe('WinterCGFetch instrumentation', () => { integration.setupOnce!(); integration.setup!(client); - const [fetchInstrumentationHandlerCallback] = addFetchInstrumentationHandlerSpy.mock.calls[0]; + const [fetchInstrumentationHandlerCallback] = addFetchInstrumentationHandlerSpy.mock.calls[0]!; expect(fetchInstrumentationHandlerCallback).toBeDefined(); const startHandlerData: HandlerDataFetch = { @@ -63,7 +63,7 @@ describe('WinterCGFetch instrumentation', () => { 'auto.http.wintercg_fetch', ); - const [, shouldCreateSpan, shouldAttachTraceData] = instrumentFetchRequestSpy.mock.calls[0]; + const [, shouldCreateSpan, shouldAttachTraceData] = instrumentFetchRequestSpy.mock.calls[0]!; expect(shouldAttachTraceData('http://my-website.com/')).toBe(true); expect(shouldAttachTraceData('https://www.3rd-party-website.at/')).toBe(false); @@ -79,7 +79,7 @@ describe('WinterCGFetch instrumentation', () => { integration.setupOnce!(); // integration.setup!(client) is not called! - const [fetchInstrumentationHandlerCallback] = addFetchInstrumentationHandlerSpy.mock.calls[0]; + const [fetchInstrumentationHandlerCallback] = addFetchInstrumentationHandlerSpy.mock.calls[0]!; expect(fetchInstrumentationHandlerCallback).toBeDefined(); const startHandlerData: HandlerDataFetch = { @@ -99,7 +99,7 @@ describe('WinterCGFetch instrumentation', () => { integration.setupOnce!(); integration.setup!(client); - const [fetchInstrumentationHandlerCallback] = addFetchInstrumentationHandlerSpy.mock.calls[0]; + const [fetchInstrumentationHandlerCallback] = addFetchInstrumentationHandlerSpy.mock.calls[0]!; expect(fetchInstrumentationHandlerCallback).toBeDefined(); const startHandlerData: HandlerDataFetch = { @@ -123,7 +123,7 @@ describe('WinterCGFetch instrumentation', () => { integration.setupOnce!(); integration.setup!(client); - const [fetchInstrumentationHandlerCallback] = addFetchInstrumentationHandlerSpy.mock.calls[0]; + const [fetchInstrumentationHandlerCallback] = addFetchInstrumentationHandlerSpy.mock.calls[0]!; expect(fetchInstrumentationHandlerCallback).toBeDefined(); const startHandlerData: HandlerDataFetch = { @@ -133,7 +133,7 @@ describe('WinterCGFetch instrumentation', () => { }; fetchInstrumentationHandlerCallback(startHandlerData); - const [, shouldCreateSpan] = instrumentFetchRequestSpy.mock.calls[0]; + const [, shouldCreateSpan] = instrumentFetchRequestSpy.mock.calls[0]!; expect(shouldCreateSpan('http://only-acceptable-url.com/')).toBe(true); expect(shouldCreateSpan('http://my-website.com/')).toBe(false); @@ -147,7 +147,7 @@ describe('WinterCGFetch instrumentation', () => { integration.setupOnce!(); integration.setup!(client); - const [fetchInstrumentationHandlerCallback] = addFetchInstrumentationHandlerSpy.mock.calls[0]; + const [fetchInstrumentationHandlerCallback] = addFetchInstrumentationHandlerSpy.mock.calls[0]!; expect(fetchInstrumentationHandlerCallback).toBeDefined(); const startTimestamp = Date.now(); @@ -189,7 +189,7 @@ describe('WinterCGFetch instrumentation', () => { integration.setupOnce!(); integration.setup!(client); - const [fetchInstrumentationHandlerCallback] = addFetchInstrumentationHandlerSpy.mock.calls[0]; + const [fetchInstrumentationHandlerCallback] = addFetchInstrumentationHandlerSpy.mock.calls[0]!; expect(fetchInstrumentationHandlerCallback).toBeDefined(); const startTimestamp = Date.now(); diff --git a/packages/vue/src/router.ts b/packages/vue/src/router.ts index b1d7163e48d1..e54c71eb550f 100644 --- a/packages/vue/src/router.ts +++ b/packages/vue/src/router.ts @@ -85,7 +85,8 @@ export function instrumentVueRouter( transactionSource = 'custom'; } else if (to.matched.length > 0) { const lastIndex = to.matched.length - 1; - spanName = to.matched[lastIndex].path; + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion + spanName = to.matched[lastIndex]!.path; transactionSource = 'route'; } diff --git a/packages/vue/test/errorHandler.test.ts b/packages/vue/test/errorHandler.test.ts index 3245fa80a356..e6ac911c533c 100644 --- a/packages/vue/test/errorHandler.test.ts +++ b/packages/vue/test/errorHandler.test.ts @@ -390,7 +390,7 @@ const testHarness = ({ const captureExceptionSpy = client.captureException; expect(captureExceptionSpy).toHaveBeenCalledTimes(1); const error = captureExceptionSpy.mock.calls[0][0]; - const contexts = captureExceptionSpy.mock.calls[0][1].captureContext.contexts; + const contexts = captureExceptionSpy.mock.calls[0][1]?.captureContext.contexts; expect(error).toBeInstanceOf(DummyError); diff --git a/packages/vue/test/router.test.ts b/packages/vue/test/router.test.ts index e835855ebd7a..8c7ca7c73e93 100644 --- a/packages/vue/test/router.test.ts +++ b/packages/vue/test/router.test.ts @@ -82,7 +82,7 @@ describe('instrumentVueRouter()', () => { // check expect(mockVueRouter.onError).toHaveBeenCalledTimes(1); - const onErrorCallback = mockVueRouter.onError.mock.calls[0][0]; + const onErrorCallback = mockVueRouter.onError.mock.calls[0]![0]!; const testError = new Error(); onErrorCallback(testError); @@ -108,10 +108,10 @@ describe('instrumentVueRouter()', () => { // check expect(mockVueRouter.beforeEach).toHaveBeenCalledTimes(1); - const beforeEachCallback = mockVueRouter.beforeEach.mock.calls[0][0]; + const beforeEachCallback = mockVueRouter.beforeEach.mock.calls[0]![0]!; - const from = testRoutes[fromKey]; - const to = testRoutes[toKey]; + const from = testRoutes[fromKey]!; + const to = testRoutes[toKey]!; beforeEachCallback(to, from, mockNext); expect(mockStartSpan).toHaveBeenCalledTimes(1); @@ -158,10 +158,10 @@ describe('instrumentVueRouter()', () => { // no span is started for page load expect(mockStartSpan).not.toHaveBeenCalled(); - const beforeEachCallback = mockVueRouter.beforeEach.mock.calls[0][0]; + const beforeEachCallback = mockVueRouter.beforeEach.mock.calls[0]![0]!; - const from = testRoutes[fromKey]; - const to = testRoutes[toKey]; + const from = testRoutes[fromKey]!; + const to = testRoutes[toKey]!; beforeEachCallback(to, from, mockNext); expect(mockVueRouter.beforeEach).toHaveBeenCalledTimes(1); @@ -186,10 +186,10 @@ describe('instrumentVueRouter()', () => { ); // check - const beforeEachCallback = mockVueRouter.beforeEach.mock.calls[0][0]; + const beforeEachCallback = mockVueRouter.beforeEach.mock.calls[0]![0]!; - const from = testRoutes.normalRoute1; - const to = testRoutes.namedRoute; + const from = testRoutes.normalRoute1!; + const to = testRoutes.namedRoute!; beforeEachCallback(to, from, mockNext); // first startTx call happens when the instrumentation is initialized (for pageloads) @@ -213,10 +213,10 @@ describe('instrumentVueRouter()', () => { ); // check - const beforeEachCallback = mockVueRouter.beforeEach.mock.calls[0][0]; + const beforeEachCallback = mockVueRouter.beforeEach.mock.calls[0]![0]!; - const from = testRoutes.normalRoute1; - const to = testRoutes.namedRoute; + const from = testRoutes.normalRoute1!; + const to = testRoutes.namedRoute!; beforeEachCallback(to, from, mockNext); // first startTx call happens when the instrumentation is initialized (for pageloads) @@ -268,10 +268,10 @@ describe('instrumentVueRouter()', () => { }, }); - const beforeEachCallback = mockVueRouter.beforeEach.mock.calls[0][0]; + const beforeEachCallback = mockVueRouter.beforeEach.mock.calls[0]![0]!; - const to = testRoutes['normalRoute1']; - const from = testRoutes['initialPageloadRoute']; + const to = testRoutes['normalRoute1']!; + const from = testRoutes['initialPageloadRoute']!; beforeEachCallback(to, from, mockNext); @@ -304,10 +304,10 @@ describe('instrumentVueRouter()', () => { mockStartSpan, ); - const beforeEachCallback = mockVueRouter.beforeEach.mock.calls[0][0]; + const beforeEachCallback = mockVueRouter.beforeEach.mock.calls[0]![0]!; - const from = testRoutes['initialPageloadRoute']; - const to = testRoutes['normalRoute1']; + const from = testRoutes['initialPageloadRoute']!; + const to = testRoutes['normalRoute1']!; beforeEachCallback(to, from, mockNext); @@ -346,8 +346,8 @@ describe('instrumentVueRouter()', () => { // check expect(mockVueRouter.beforeEach).toHaveBeenCalledTimes(1); - const beforeEachCallback = mockVueRouter.beforeEach.mock.calls[0][0]; - beforeEachCallback(testRoutes['normalRoute1'], testRoutes['initialPageloadRoute'], mockNext); + const beforeEachCallback = mockVueRouter.beforeEach.mock.calls[0]![0]!; + beforeEachCallback(testRoutes['normalRoute1']!, testRoutes['initialPageloadRoute']!, mockNext); expect(mockRootSpan.updateName).toHaveBeenCalledTimes(expectedCallsAmount); expect(mockStartSpan).not.toHaveBeenCalled(); @@ -370,8 +370,8 @@ describe('instrumentVueRouter()', () => { // check expect(mockVueRouter.beforeEach).toHaveBeenCalledTimes(1); - const beforeEachCallback = mockVueRouter.beforeEach.mock.calls[0][0]; - beforeEachCallback(testRoutes['normalRoute2'], testRoutes['normalRoute1'], mockNext); + const beforeEachCallback = mockVueRouter.beforeEach.mock.calls[0]![0]!; + beforeEachCallback(testRoutes['normalRoute2']!, testRoutes['normalRoute1']!, mockNext); expect(mockStartSpan).toHaveBeenCalledTimes(expectedCallsAmount); }, @@ -385,10 +385,10 @@ describe('instrumentVueRouter()', () => { mockStartSpan, ); - const beforeEachCallback = mockVueRouter.beforeEach.mock.calls[0][0]; + const beforeEachCallback = mockVueRouter.beforeEach.mock.calls[0]![0]!; - const from = testRoutes.normalRoute1; - const to = testRoutes.namedRoute; + const from = testRoutes.normalRoute1!; + const to = testRoutes.namedRoute!; beforeEachCallback(to, from, undefined); // first startTx call happens when the instrumentation is initialized (for pageloads) diff --git a/packages/wasm/src/index.ts b/packages/wasm/src/index.ts index c3cf09fbbcd8..88eb1915ce06 100644 --- a/packages/wasm/src/index.ts +++ b/packages/wasm/src/index.ts @@ -45,8 +45,10 @@ function patchFrames(frames: Array): boolean { if (!frame.filename) { return; } - const match = frame.filename.match(/^(.*?):wasm-function\[\d+\]:(0x[a-fA-F0-9]+)$/); - if (match !== null) { + const match = frame.filename.match(/^(.*?):wasm-function\[\d+\]:(0x[a-fA-F0-9]+)$/) as + | null + | [string, string, string]; + if (match) { const index = getImage(match[1]); if (index >= 0) { frame.instruction_addr = match[2]; diff --git a/packages/wasm/src/registry.ts b/packages/wasm/src/registry.ts index 2005c840b630..989022901709 100644 --- a/packages/wasm/src/registry.ts +++ b/packages/wasm/src/registry.ts @@ -18,16 +18,18 @@ export function getModuleInfo(module: WebAssembly.Module): ModuleInfo { let buildId = null; let debugFile = null; - if (buildIds.length > 0) { - const firstBuildId = new Uint8Array(buildIds[0]); + const buildId0 = buildIds[0]; + if (buildId0) { + const firstBuildId = new Uint8Array(buildId0); buildId = Array.from(firstBuildId).reduce((acc, x) => { return acc + x.toString(16).padStart(2, '0'); }, ''); } const externalDebugInfo = WebAssembly.Module.customSections(module, 'external_debug_info'); - if (externalDebugInfo.length > 0) { - const firstExternalDebugInfo = new Uint8Array(externalDebugInfo[0]); + const externalDebugInfo0 = externalDebugInfo[0]; + if (externalDebugInfo0) { + const firstExternalDebugInfo = new Uint8Array(externalDebugInfo0); const decoder = new TextDecoder('utf-8'); debugFile = decoder.decode(firstExternalDebugInfo); } diff --git a/scripts/prepack.ts b/scripts/prepack.ts index 43febdcde4ee..fd3d12e52d60 100644 --- a/scripts/prepack.ts +++ b/scripts/prepack.ts @@ -78,6 +78,10 @@ function rewriteConditionalExportEntryPoint( key: string, ): void { const exportsField = exportsObject[key]; + if (!exportsField) { + return; + } + if (typeof exportsField === 'string') { exportsObject[key] = exportsField.replace(`${buildDir}/`, ''); return;