Skip to content

Commit 73aabe4

Browse files
authored
feat(core): add uniq array util and consolidate call sites (#22152)
Adds a `uniq` helper to `@sentry/core` and routes the ad-hoc dedup implementations across the repo through it. All were doing the same order-preserving, keep-first-occurrence dedup, so this is behavior-neutral.
1 parent 47f8ce2 commit 73aabe4

8 files changed

Lines changed: 49 additions & 11 deletions

File tree

packages/aws-serverless/src/integration/aws/vendored/services/MessageAttributes.ts

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
*/
99

1010
import { TextMapGetter, TextMapSetter, context, propagation, diag } from '@opentelemetry/api';
11+
import { uniq } from '@sentry/core';
1112
import type { SQS, SNS } from '../aws-sdk.types';
1213

1314
// https://docs.aws.amazon.com/AWSSimpleQueueService/latest/SQSDeveloperGuide/sqs-quotas.html
@@ -72,7 +73,5 @@ export const addPropagationFieldsToAttributeNames = (
7273
messageAttributeNames: string[] = [],
7374
propagationFields: string[],
7475
) => {
75-
return messageAttributeNames.length
76-
? Array.from(new Set([...messageAttributeNames, ...propagationFields]))
77-
: propagationFields;
76+
return messageAttributeNames.length ? uniq([...messageAttributeNames, ...propagationFields]) : propagationFields;
7877
};

packages/core/src/shared-exports.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -121,6 +121,7 @@ export { getTraceData } from './utils/traceData';
121121
export { shouldPropagateTraceForUrl } from './utils/tracePropagationTargets';
122122
export { getTraceMetaTags } from './utils/meta';
123123
export { debounce } from './utils/debounce';
124+
export { uniq } from './utils/array';
124125
export { makeWeakRef, derefWeakRef } from './utils/weakRef';
125126
export type { MaybeWeakRef } from './utils/weakRef';
126127
export { shouldIgnoreSpan } from './utils/should-ignore-span';

packages/core/src/utils/array.ts

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
/**
2+
* Return a new array with duplicate values removed, preserving first-occurrence order.
3+
*
4+
* @param input the array to deduplicate
5+
* @returns a new array containing each distinct value once, in the order it first appeared
6+
*/
7+
export function uniq<T>(input: T[]): T[] {
8+
return Array.from(new Set(input));
9+
}
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
import { describe, expect, it } from 'vitest';
2+
import { uniq } from '../../../src/utils/array';
3+
4+
describe('Unit | util | uniq', () => {
5+
it('removes duplicate values', () => {
6+
expect(uniq([1, 1, 2, 3, 3, 3])).toEqual([1, 2, 3]);
7+
});
8+
9+
it('preserves first-occurrence order', () => {
10+
expect(uniq(['b', 'a', 'b', 'c', 'a'])).toEqual(['b', 'a', 'c']);
11+
});
12+
13+
it('returns an empty array for an empty input', () => {
14+
expect(uniq([])).toEqual([]);
15+
});
16+
17+
it('returns a new array and does not mutate the input', () => {
18+
const input = [1, 2, 2];
19+
const result = uniq(input);
20+
expect(result).not.toBe(input);
21+
expect(input).toEqual([1, 2, 2]);
22+
});
23+
24+
it('dedupes by identity, keeping distinct object references', () => {
25+
const a = { id: 1 };
26+
const b = { id: 1 };
27+
expect(uniq([a, a, b])).toEqual([a, b]);
28+
});
29+
});

packages/server-utils/src/orchestrion/config/index.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import type { InstrumentationConfig } from '@apm-js-collab/code-transformer';
2+
import { uniq } from '@sentry/core';
23
import { mysqlConfig } from './mysql';
34
import { lruMemoizerConfig } from './lru-memoizer';
45
import { ioredisConfig } from './ioredis';
@@ -40,7 +41,7 @@ export const SENTRY_INSTRUMENTATIONS: InstrumentationConfig[] = [
4041
* runtime and never passes through the code transform's `onLoad`, so its
4142
* diagnostics_channel calls are silently never injected.
4243
*/
43-
export const INSTRUMENTED_MODULE_NAMES: string[] = Array.from(new Set(SENTRY_INSTRUMENTATIONS.map(i => i.module.name)));
44+
export const INSTRUMENTED_MODULE_NAMES: string[] = uniq(SENTRY_INSTRUMENTATIONS.map(i => i.module.name));
4445

4546
/**
4647
* Returns `external` with any instrumented packages removed, so a bundler that

packages/tanstackstart-react/src/vite/autoInstrumentMiddleware.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import { uniq } from '@sentry/core';
12
import type { Plugin } from 'vite';
23

34
type AutoInstrumentMiddlewareOptions = {
@@ -179,7 +180,7 @@ export function arrayToObjectShorthand(contents: string): string | null {
179180
}
180181

181182
// Deduplicate to avoid invalid syntax like { foo, foo }
182-
const uniqueItems = [...new Set(items)];
183+
const uniqueItems = uniq(items);
183184

184185
return `{ ${uniqueItems.join(', ')} }`;
185186
}

packages/tanstackstart-react/src/vite/routePatterns.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import * as fs from 'node:fs';
22
import * as path from 'node:path';
3+
import { uniq } from '@sentry/core';
34
import type { Plugin } from 'vite';
45

56
/**
@@ -68,7 +69,7 @@ export function extractRoutePatterns(content: string): string[] {
6869
}
6970
}
7071

71-
return [...new Set(patterns)].sort((a, b) => {
72+
return uniq(patterns).sort((a, b) => {
7273
const aSegments = a.split('/');
7374
const bSegments = b.split('/');
7475
if (bSegments.length !== aSegments.length) {

packages/vue/src/tracing.ts

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import { getActiveSpan, SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN, startInactiveSpan } from '@sentry/browser';
22
import type { Span } from '@sentry/core';
3-
import { debug, timestampInSeconds } from '@sentry/core';
3+
import { debug, timestampInSeconds, uniq } from '@sentry/core';
44
import { DEFAULT_HOOKS } from './constants';
55
import { DEBUG_BUILD } from './debug-build';
66
import type { Hook, Operation, TracingOptions, ViewModel, Vue } from './types';
@@ -59,10 +59,7 @@ export function findTrackComponent(trackComponents: string[], formattedName: str
5959
}
6060

6161
export const createTracingMixins = (options: Partial<TracingOptions> = {}): Mixins => {
62-
const hooks = (options.hooks || [])
63-
.concat(DEFAULT_HOOKS)
64-
// Removing potential duplicates
65-
.filter((value, index, self) => self.indexOf(value) === index);
62+
const hooks = uniq((options.hooks || []).concat(DEFAULT_HOOKS));
6663

6764
const mixins: Mixins = {};
6865

0 commit comments

Comments
 (0)