Skip to content

Commit cb10086

Browse files
committed
feat: Avoid class fields alltogether
We already have an eslint rule to avoid class fields, but had exceptions for static fields as well as for arrow functions. This also leads to bundle size increases, so removing the exceptions and handling the (few) exceptions we have there should save some bytes. Additionally, this has additional challenges if we want to avoid/reduce polyfills, as class fields need to be polyfilled for ES2020, sadly.
1 parent 4e6c7cb commit cb10086

File tree

15 files changed

+165
-173
lines changed

15 files changed

+165
-173
lines changed

docs/migration/v8-to-v9.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,8 @@ Sentry.init({
7878

7979
In v9, an `undefined` value will be treated the same as if the value is not defined at all. You'll need to set `tracesSampleRate: 0` if you want to enable tracing without performance.
8080

81+
- The `getCurrentHub().getIntegration(IntegrationClass)` method will always return `null` in v9. This has already stopped working mostly in v8, because we stopped exposing integration classes. In v9, the fallback behavior has been removed. Note that this does not change the type signature and is thus not technically breaking, but still worth pointing out.
82+
8183
### `@sentry/node`
8284

8385
- When `skipOpenTelemetrySetup: true` is configured, `httpIntegration({ spans: false })` will be configured by default. This means that you no longer have to specify this yourself in this scenario. With this change, no spans are emitted once `skipOpenTelemetrySetup: true` is configured, without any further configuration being needed.
@@ -207,6 +209,7 @@ This led to some duplication, where we had to keep an interface in `@sentry/type
207209
Since v9, the types have been merged into `@sentry/core`, which removed some of this duplication. This means that certain things that used to be a separate interface, will not expect an actual instance of the class/concrete implementation. This should not affect most users, unless you relied on passing things with a similar shape to internal methods. The following types are affected:
208210

209211
- `Scope` now always expects the `Scope` class
212+
- The `IntegrationClass` type is no longer exported - it was not used anymore. Instead, use `Integration` or `IntegrationFn`.
210213

211214
# No Version Support Timeline
212215

packages/angular/.eslintrc.cjs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,4 +4,8 @@ module.exports = {
44
},
55
extends: ['../../.eslintrc.js'],
66
ignorePatterns: ['setup-test.ts', 'patch-vitest.ts'],
7+
rules: {
8+
// Angular transpiles this correctly/relies on this
9+
'@sentry-internal/sdk/no-class-field-initializers': 'off',
10+
},
711
};

packages/core/src/getCurrentHubShim.ts

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ import {
1111
setUser,
1212
startSession,
1313
} from './exports';
14-
import type { Client, EventHint, Hub, Integration, IntegrationClass, SeverityLevel } from './types-hoist';
14+
import type { Client, EventHint, Hub, Integration, SeverityLevel } from './types-hoist';
1515

1616
/**
1717
* This is for legacy reasons, and returns a proxy object instead of a hub to be used.
@@ -48,9 +48,8 @@ export function getCurrentHubShim(): Hub {
4848
setExtras,
4949
setContext,
5050

51-
getIntegration<T extends Integration>(integration: IntegrationClass<T>): T | null {
52-
const client = getClient();
53-
return (client && client.getIntegrationByName<T>(integration.id)) || null;
51+
getIntegration<T extends Integration>(_integration: unknown): T | null {
52+
return null;
5453
},
5554

5655
startSession,

packages/core/src/types-hoist/hub.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import type { Breadcrumb, BreadcrumbHint } from './breadcrumb';
33
import type { Client } from './client';
44
import type { Event, EventHint } from './event';
55
import type { Extra, Extras } from './extra';
6-
import type { Integration, IntegrationClass } from './integration';
6+
import type { Integration } from './integration';
77
import type { Primitive } from './misc';
88
import type { Session } from './session';
99
import type { SeverityLevel } from './severity';
@@ -171,9 +171,9 @@ export interface Hub {
171171
/**
172172
* Returns the integration if installed on the current client.
173173
*
174-
* @deprecated Use `Sentry.getClient().getIntegration()` instead.
174+
* @deprecated Use `Sentry.getClient().getIntegrationByName()` instead.
175175
*/
176-
getIntegration<T extends Integration>(integration: IntegrationClass<T>): T | null;
176+
getIntegration<T extends Integration>(integration: unknown): T | null;
177177

178178
/**
179179
* Starts a new `Session`, sets on the current scope and returns it.

packages/core/src/types-hoist/index.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -58,7 +58,7 @@ export type { Exception } from './exception';
5858
export type { Extra, Extras } from './extra';
5959
// eslint-disable-next-line deprecation/deprecation
6060
export type { Hub } from './hub';
61-
export type { Integration, IntegrationClass, IntegrationFn } from './integration';
61+
export type { Integration, IntegrationFn } from './integration';
6262
export type { Mechanism } from './mechanism';
6363
export type { ExtractedNodeRequestData, HttpHeaderValue, Primitive, WorkerLocation } from './misc';
6464
export type { ClientOptions, Options } from './options';

packages/core/src/types-hoist/integration.ts

Lines changed: 0 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,6 @@
11
import type { Client } from './client';
22
import type { Event, EventHint } from './event';
33

4-
/** Integration Class Interface */
5-
export interface IntegrationClass<T> {
6-
/**
7-
* Property that holds the integration name
8-
*/
9-
id: string;
10-
11-
new (...args: any[]): T;
12-
}
13-
144
/** Integration interface */
155
export interface Integration {
166
/**

packages/core/src/utils-hoist/syncpromise.ts

Lines changed: 57 additions & 60 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
1-
/* eslint-disable @typescript-eslint/explicit-function-return-type */
21
/* eslint-disable @typescript-eslint/no-explicit-any */
32
import { isThenable } from './is';
43

@@ -44,25 +43,78 @@ export function rejectedSyncPromise<T = never>(reason?: any): PromiseLike<T> {
4443
* Thenable class that behaves like a Promise and follows it's interface
4544
* but is not async internally
4645
*/
47-
class SyncPromise<T> implements PromiseLike<T> {
46+
export class SyncPromise<T> implements PromiseLike<T> {
4847
private _state: States;
4948
private _handlers: Array<[boolean, (value: T) => void, (reason: any) => any]>;
5049
private _value: any;
50+
private _resolve: (value?: T | PromiseLike<T> | null) => void;
51+
private _reject: (reason?: any) => void;
52+
private _executeHandlers: () => void;
53+
private _setResult: (state: States, value?: T | PromiseLike<T> | any) => void;
5154

5255
public constructor(
5356
executor: (resolve: (value?: T | PromiseLike<T> | null) => void, reject: (reason?: any) => void) => void,
5457
) {
5558
this._state = States.PENDING;
5659
this._handlers = [];
5760

61+
this._resolve = (value?: T | PromiseLike<T> | null) => {
62+
this._setResult(States.RESOLVED, value);
63+
};
64+
65+
this._reject = (reason?: any) => {
66+
this._setResult(States.REJECTED, reason);
67+
};
68+
69+
this._executeHandlers = () => {
70+
if (this._state === States.PENDING) {
71+
return;
72+
}
73+
74+
const cachedHandlers = this._handlers.slice();
75+
this._handlers = [];
76+
77+
cachedHandlers.forEach(handler => {
78+
if (handler[0]) {
79+
return;
80+
}
81+
82+
if (this._state === States.RESOLVED) {
83+
handler[1](this._value as unknown as any);
84+
}
85+
86+
if (this._state === States.REJECTED) {
87+
handler[2](this._value);
88+
}
89+
90+
handler[0] = true;
91+
});
92+
};
93+
94+
this._setResult = (state: States, value?: T | PromiseLike<T> | any) => {
95+
if (this._state !== States.PENDING) {
96+
return;
97+
}
98+
99+
if (isThenable(value)) {
100+
void (value as PromiseLike<T>).then(this._resolve, this._reject);
101+
return;
102+
}
103+
104+
this._state = state;
105+
this._value = value;
106+
107+
this._executeHandlers();
108+
};
109+
58110
try {
59111
executor(this._resolve, this._reject);
60112
} catch (e) {
61113
this._reject(e);
62114
}
63115
}
64116

65-
/** JSDoc */
117+
/** @inheritdoc */
66118
public then<TResult1 = T, TResult2 = never>(
67119
onfulfilled?: ((value: T) => TResult1 | PromiseLike<TResult1>) | null,
68120
onrejected?: ((reason: any) => TResult2 | PromiseLike<TResult2>) | null,
@@ -99,14 +151,14 @@ class SyncPromise<T> implements PromiseLike<T> {
99151
});
100152
}
101153

102-
/** JSDoc */
154+
/** @inheritdoc */
103155
public catch<TResult = never>(
104156
onrejected?: ((reason: any) => TResult | PromiseLike<TResult>) | null,
105157
): PromiseLike<T | TResult> {
106158
return this.then(val => val, onrejected);
107159
}
108160

109-
/** JSDoc */
161+
/** @inheritdoc */
110162
public finally<TResult>(onfinally?: (() => void) | null): PromiseLike<TResult> {
111163
return new SyncPromise<TResult>((resolve, reject) => {
112164
let val: TResult | any;
@@ -137,59 +189,4 @@ class SyncPromise<T> implements PromiseLike<T> {
137189
});
138190
});
139191
}
140-
141-
/** JSDoc */
142-
private readonly _resolve = (value?: T | PromiseLike<T> | null) => {
143-
this._setResult(States.RESOLVED, value);
144-
};
145-
146-
/** JSDoc */
147-
private readonly _reject = (reason?: any) => {
148-
this._setResult(States.REJECTED, reason);
149-
};
150-
151-
/** JSDoc */
152-
private readonly _setResult = (state: States, value?: T | PromiseLike<T> | any) => {
153-
if (this._state !== States.PENDING) {
154-
return;
155-
}
156-
157-
if (isThenable(value)) {
158-
void (value as PromiseLike<T>).then(this._resolve, this._reject);
159-
return;
160-
}
161-
162-
this._state = state;
163-
this._value = value;
164-
165-
this._executeHandlers();
166-
};
167-
168-
/** JSDoc */
169-
private readonly _executeHandlers = () => {
170-
if (this._state === States.PENDING) {
171-
return;
172-
}
173-
174-
const cachedHandlers = this._handlers.slice();
175-
this._handlers = [];
176-
177-
cachedHandlers.forEach(handler => {
178-
if (handler[0]) {
179-
return;
180-
}
181-
182-
if (this._state === States.RESOLVED) {
183-
handler[1](this._value as unknown as any);
184-
}
185-
186-
if (this._state === States.REJECTED) {
187-
handler[2](this._value);
188-
}
189-
190-
handler[0] = true;
191-
});
192-
};
193192
}
194-
195-
export { SyncPromise };

packages/eslint-plugin-sdk/src/rules/no-class-field-initializers.js

Lines changed: 1 addition & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -29,14 +29,7 @@ module.exports = {
2929
create(context) {
3030
return {
3131
'ClassProperty, PropertyDefinition'(node) {
32-
// We do allow arrow functions being initialized directly
33-
if (
34-
!node.static &&
35-
node.value !== null &&
36-
node.value.type !== 'ArrowFunctionExpression' &&
37-
node.value.type !== 'FunctionExpression' &&
38-
node.value.type !== 'CallExpression'
39-
) {
32+
if (node.value !== null) {
4033
context.report({
4134
node,
4235
message: `Avoid class field initializers. Property "${node.key.name}" should be initialized in the constructor.`,

packages/nestjs/src/integrations/sentry-nest-event-instrumentation.ts

Lines changed: 9 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import {
55
InstrumentationNodeModuleDefinition,
66
InstrumentationNodeModuleFile,
77
} from '@opentelemetry/instrumentation';
8+
import type { SpanAttributes } from '@sentry/core';
89
import { SDK_VERSION, captureException, startSpan } from '@sentry/core';
910
import { getEventSpanOptions } from './helpers';
1011
import type { OnEventTarget } from './types';
@@ -17,23 +18,23 @@ const supportedVersions = ['>=2.0.0'];
1718
* This hooks into the `OnEvent` decorator, which is applied on event handlers.
1819
*/
1920
export class SentryNestEventInstrumentation extends InstrumentationBase {
20-
public static readonly COMPONENT = '@nestjs/event-emitter';
21-
public static readonly COMMON_ATTRIBUTES = {
22-
component: SentryNestEventInstrumentation.COMPONENT,
23-
};
21+
public readonly COMPONENT: string;
22+
public readonly COMMON_ATTRIBUTES: SpanAttributes;
2423

2524
public constructor(config: InstrumentationConfig = {}) {
2625
super('sentry-nestjs-event', SDK_VERSION, config);
26+
27+
this.COMPONENT = '@nestjs/event-emitter';
28+
this.COMMON_ATTRIBUTES = {
29+
component: this.COMPONENT,
30+
};
2731
}
2832

2933
/**
3034
* Initializes the instrumentation by defining the modules to be patched.
3135
*/
3236
public init(): InstrumentationNodeModuleDefinition {
33-
const moduleDef = new InstrumentationNodeModuleDefinition(
34-
SentryNestEventInstrumentation.COMPONENT,
35-
supportedVersions,
36-
);
37+
const moduleDef = new InstrumentationNodeModuleDefinition(this.COMPONENT, supportedVersions);
3738

3839
moduleDef.files.push(this._getOnEventFileInstrumentation(supportedVersions));
3940
return moduleDef;

packages/nestjs/src/integrations/sentry-nest-instrumentation.ts

Lines changed: 9 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ import {
55
InstrumentationNodeModuleDefinition,
66
InstrumentationNodeModuleFile,
77
} from '@opentelemetry/instrumentation';
8-
import type { Span } from '@sentry/core';
8+
import type { Span, SpanAttributes } from '@sentry/core';
99
import {
1010
SDK_VERSION,
1111
addNonEnumerableProperty,
@@ -29,20 +29,23 @@ const supportedVersions = ['>=8.0.0 <11'];
2929
* 2. @Catch decorator, which is applied on exception filters.
3030
*/
3131
export class SentryNestInstrumentation extends InstrumentationBase {
32-
public static readonly COMPONENT = '@nestjs/common';
33-
public static readonly COMMON_ATTRIBUTES = {
34-
component: SentryNestInstrumentation.COMPONENT,
35-
};
32+
public readonly COMPONENT: string;
33+
public readonly COMMON_ATTRIBUTES: SpanAttributes;
3634

3735
public constructor(config: InstrumentationConfig = {}) {
3836
super('sentry-nestjs', SDK_VERSION, config);
37+
38+
this.COMPONENT = '@nestjs/common';
39+
this.COMMON_ATTRIBUTES = {
40+
component: this.COMPONENT,
41+
};
3942
}
4043

4144
/**
4245
* Initializes the instrumentation by defining the modules to be patched.
4346
*/
4447
public init(): InstrumentationNodeModuleDefinition {
45-
const moduleDef = new InstrumentationNodeModuleDefinition(SentryNestInstrumentation.COMPONENT, supportedVersions);
48+
const moduleDef = new InstrumentationNodeModuleDefinition(this.COMPONENT, supportedVersions);
4649

4750
moduleDef.files.push(
4851
this._getInjectableFileInstrumentation(supportedVersions),

packages/react/src/errorboundary.tsx

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -145,14 +145,14 @@ class ErrorBoundary extends React.Component<ErrorBoundaryProps, ErrorBoundarySta
145145
}
146146
}
147147

148-
public resetErrorBoundary: () => void = () => {
148+
public resetErrorBoundary(): void {
149149
const { onReset } = this.props;
150150
const { error, componentStack, eventId } = this.state;
151151
if (onReset) {
152152
onReset(error, componentStack, eventId);
153153
}
154154
this.setState(INITIAL_STATE);
155-
};
155+
}
156156

157157
public render(): React.ReactNode {
158158
const { fallback, children } = this.props;
@@ -164,7 +164,7 @@ class ErrorBoundary extends React.Component<ErrorBoundaryProps, ErrorBoundarySta
164164
element = React.createElement(fallback, {
165165
error: state.error,
166166
componentStack: state.componentStack as string,
167-
resetError: this.resetErrorBoundary,
167+
resetError: this.resetErrorBoundary.bind(this),
168168
eventId: state.eventId as string,
169169
});
170170
} else {

0 commit comments

Comments
 (0)