Skip to content
This repository has been archived by the owner on Dec 12, 2024. It is now read-only.

feat: Add configureScope transformer #19

Merged
merged 4 commits into from
Dec 14, 2023
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
25 changes: 25 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,31 @@ but only a few that have been deprecated:
- `Handlers.ExpressRequest` → `PolymorphicRequest` (Type export)
- `Handlers.extractRequestData` → `extractRequestData`

### Use getCurrentScope() instead of configureScope()

Rewrites usages of `configureScope()` to use `getCurrentScope()` instead. Note that this will rewrite this to code
blocks, which may not be the preferred syntax in all cases, but it's the only way to make this work somewhat reliably
with avoiding variable clashes etc.

This will rewrite:

```js
Sentry.configureScope(scope => {
scope.setTag('ccc', 'ccc');
scope.setExtra('ddd', { ddd: 'ddd' });
});
```

to

```js
{
const scope = Sentry.getCurrentScope();
scope.setTag('ccc', 'ccc');
scope.setExtra('ddd', { ddd: 'ddd' });
}
```

### Tracing Config v7>v8

Rewrites `tracePropagationTargets` and `tracingOrigins` from Integration-level config to root config on `Sentry.init()`.
Expand Down
137 changes: 137 additions & 0 deletions src/transformers/configureScope/configureScope.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,137 @@
import { afterEach, describe, it } from 'node:test';
import * as assert from 'node:assert';
import { rmSync } from 'node:fs';

import { getDirFileContent, getFixturePath, makeTmpDir } from '../../../test-helpers/testPaths.js';
import { assertStringEquals } from '../../../test-helpers/assert.js';

import configureScopeTransformer from './index.js';

describe('transformers | configureScope', () => {
let tmpDir = '';

afterEach(() => {
if (tmpDir) {
rmSync(tmpDir, { force: true, recursive: true });
tmpDir = '';
}
});

it('has correct name', () => {
assert.equal(configureScopeTransformer.name, 'Use getCurrentScope() instead of configureScope()');
});

it('works with app without Sentry', async () => {
tmpDir = makeTmpDir(getFixturePath('noSentry'));
await configureScopeTransformer.transform([tmpDir], { filePatterns: [] });

const actual1 = getDirFileContent(tmpDir, 'app.js');
assert.equal(actual1, getDirFileContent(`${process.cwd()}/test-fixtures/noSentry`, 'app.js'));
});

it('works with example files', async () => {
tmpDir = makeTmpDir(getFixturePath('configureScope'));
await configureScopeTransformer.transform([tmpDir], { filePatterns: [], sdk: '@sentry/browser' });

const withImports = getDirFileContent(tmpDir, 'withImports.js');
const withImportsTs = getDirFileContent(tmpDir, 'withImports.ts');
const withRequire = getDirFileContent(tmpDir, 'withRequire.js');

assertStringEquals(
withImports,
`import { getCurrentScope } from '@sentry/browser';
import * as Sentry from '@sentry/browser';

function orig() {
// do something
}

function doSomething() {
{
const scope = getCurrentScope();
scope.setTag('aaa', 'aaa');
scope.setExtra('bbb', { bbb: 'bbb' });
};

{
const scope = getCurrentScope();
scope.setTag('aaa', 'aaa');
scope.setExtra('bbb', { bbb: 'bbb' });
};

configureScope(orig);

{
const scope = Sentry.getCurrentScope();
scope.setTag('ccc', 'ccc');
scope.setExtra('ddd', { ddd: 'ddd' });
};
}
`
);
assertStringEquals(
withImportsTs,
`import { getCurrentScope } from '@sentry/browser';
import * as Sentry from '@sentry/browser';

function orig(): void {
// do something
}

function doSomething(): void {
{
const scope = getCurrentScope();
scope.setTag('aaa', 'aaa');
scope.setExtra('bbb', { bbb: 'bbb' });
};

{
const scope = getCurrentScope();
scope.setTag('aaa', 'aaa');
scope.setExtra('bbb', { bbb: 'bbb' });
};

configureScope(orig);

{
const scope = Sentry.getCurrentScope();
scope.setTag('ccc', 'ccc');
scope.setExtra('ddd', { ddd: 'ddd' });
};
}
`
);

assertStringEquals(
withRequire,
`const { getCurrentScope } = require('@sentry/browser');
const Sentry = require('@sentry/browser');

function orig() {
// do something
}

function doSomething() {
{
const scope = getCurrentScope();
scope.setTag('aaa', 'aaa');
scope.setExtra('bbb', { bbb: 'bbb' });
};

{
const scope = getCurrentScope();
scope.setTag('aaa', 'aaa');
scope.setExtra('bbb', { bbb: 'bbb' });
};

configureScope(orig);

{
const scope = Sentry.getCurrentScope();
scope.setTag('ccc', 'ccc');
scope.setExtra('ddd', { ddd: 'ddd' });
};
}`
);
});
});
16 changes: 16 additions & 0 deletions src/transformers/configureScope/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import path from 'path';
import url from 'url';

import { runJscodeshift } from '../../utils/jscodeshift.js';

/**
* @type {import('types').Transformer}
*/
export default {
name: 'Use getCurrentScope() instead of configureScope()',
transform: async (files, options) => {
const transformPath = path.join(path.dirname(url.fileURLToPath(import.meta.url)), './transform.cjs');

await runJscodeshift(transformPath, files, options);
},
};
128 changes: 128 additions & 0 deletions src/transformers/configureScope/transform.cjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,128 @@
const { hasSentryImportOrRequire, replaceImported } = require('../../utils/jscodeshift.cjs');
const { wrapJscodeshift } = require('../../utils/dom.cjs');

/**
* This transform converts usages of `configureScope((scope) => {})` to use `getCurrentScope()` instead.
*
* Replaces:
*
* ```
* configureScope((scope) => {
* scope.setTag('foo', 'bar');
* scope.addEventProcessor(fn);
* });
* ```
*
* with
*
* ```
* const scope = getCurrentScope();
* scope.setTag('foo', 'bar');
* scope.addEventProcessor(fn);
*
* @param {import('jscodeshift').FileInfo} fileInfo
* @param {import('jscodeshift').API} api
* @param {import('jscodeshift').Options & { sentry: import('types').RunOptions & {sdk: string} }} options
*/
module.exports = function (fileInfo, api, options) {
const j = api.jscodeshift;
const source = fileInfo.source;
const fileName = fileInfo.path;

return wrapJscodeshift(j, source, fileName, (j, source) => {
const tree = j(source);

// If no sentry import, skip it
if (!hasSentryImportOrRequire(source)) {
return undefined;
}

if (!source.includes('configureScope')) {
return undefined;
}

// Replace `configureScope()`
tree.find(j.CallExpression).forEach(path => {
if (path.value.callee.type !== 'Identifier' || path.value.callee.name !== 'configureScope') {
return;
}

const callbackFn = path.value.arguments[0];
if (callbackFn.type !== 'ArrowFunctionExpression' && callbackFn.type !== 'FunctionExpression') {
return;
}

// The var name of the scope callback, e.g. (scope) => {} would be "scope"
const scopeVarName = callbackFn.params[0]?.type === 'Identifier' ? callbackFn.params[0].name : undefined;

if (!scopeVarName) {
return;
}

const callbackBody = callbackFn.body;

if (callbackBody.type !== 'BlockStatement') {
return;
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

what about configureScope(scope => scope.setTag(...))?


path.replace(
j.blockStatement([
j.variableDeclaration('const', [
j.variableDeclarator(j.identifier(scopeVarName), j.callExpression(j.identifier('getCurrentScope'), [])),
]),
...callbackBody.body,
])
);
});

// Replace e.g. `SentryUtuils.configureScope()`
tree.find(j.CallExpression).forEach(path => {
if (
path.value.callee.type !== 'MemberExpression' ||
path.value.callee.property.type !== 'Identifier' ||
path.value.callee.property.name !== 'configureScope'
) {
return;
}

const calleeObj = path.value.callee.object;

const callbackFn = path.value.arguments[0];
if (callbackFn.type !== 'ArrowFunctionExpression' && callbackFn.type !== 'FunctionExpression') {
return;
}

// The var name of the scope callback, e.g. (scope) => {} would be "scope"
const scopeVarName = callbackFn.params[0]?.type === 'Identifier' ? callbackFn.params[0].name : undefined;

if (!scopeVarName) {
return;
}

const callbackBody = callbackFn.body;

if (callbackBody.type !== 'BlockStatement') {
return;
}

path.replace(
j.blockStatement([
j.variableDeclaration('const', [
j.variableDeclarator(
j.identifier(scopeVarName),
j.memberExpression(calleeObj, j.callExpression(j.identifier('getCurrentScope'), []))
),
]),
...callbackBody.body,
])
);
});

const sdk = options.sentry?.sdk;
if (sdk) {
replaceImported(j, tree, source, sdk, new Map([['configureScope', 'getCurrentScope']]));
}

return tree.toSource();
});
};
25 changes: 25 additions & 0 deletions test-fixtures/configureScope/withImports.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import { configureScope } from '@sentry/browser';
import * as Sentry from '@sentry/browser';

function orig() {
// do something
}

function doSomething() {
configureScope((scope) => {
scope.setTag('aaa', 'aaa');
scope.setExtra('bbb', { bbb: 'bbb' });
});

configureScope(function (scope) {
scope.setTag('aaa', 'aaa');
scope.setExtra('bbb', { bbb: 'bbb' });
});

configureScope(orig);

Sentry.configureScope((scope) => {
scope.setTag('ccc', 'ccc');
scope.setExtra('ddd', { ddd: 'ddd' });
});
}
25 changes: 25 additions & 0 deletions test-fixtures/configureScope/withImports.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import { configureScope } from '@sentry/browser';
import * as Sentry from '@sentry/browser';

function orig(): void {
// do something
}

function doSomething(): void {
configureScope((scope) => {
scope.setTag('aaa', 'aaa');
scope.setExtra('bbb', { bbb: 'bbb' });
});

configureScope(function (scope) {
scope.setTag('aaa', 'aaa');
scope.setExtra('bbb', { bbb: 'bbb' });
});

configureScope(orig);

Sentry.configureScope((scope) => {
scope.setTag('ccc', 'ccc');
scope.setExtra('ddd', { ddd: 'ddd' });
});
}
25 changes: 25 additions & 0 deletions test-fixtures/configureScope/withRequire.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
const { configureScope } = require('@sentry/browser');
const Sentry = require('@sentry/browser');

function orig() {
// do something
}

function doSomething() {
configureScope((scope) => {
scope.setTag('aaa', 'aaa');
scope.setExtra('bbb', { bbb: 'bbb' });
});

configureScope(function (scope) {
scope.setTag('aaa', 'aaa');
scope.setExtra('bbb', { bbb: 'bbb' });
});

configureScope(orig);

Sentry.configureScope((scope) => {
scope.setTag('ccc', 'ccc');
scope.setExtra('ddd', { ddd: 'ddd' });
});
}
Loading