Skip to content

Commit 1c4726c

Browse files
authored
ref(sveltekit): Improve auto-instrumentation applicability check (#8083)
Walk AST to check for `load` function instead of string/regex based approach
1 parent df59f66 commit 1c4726c

File tree

4 files changed

+94
-23
lines changed

4 files changed

+94
-23
lines changed

packages/sveltekit/package.json

+1
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@
2828
"@sentry/types": "7.53.0",
2929
"@sentry/utils": "7.53.0",
3030
"@sentry/vite-plugin": "^0.6.0",
31+
"magicast": "0.2.6",
3132
"sorcery": "0.11.0"
3233
},
3334
"devDependencies": {

packages/sveltekit/src/vite/autoInstrument.ts

+37-9
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
/* eslint-disable @sentry-internal/sdk/no-optional-chaining */
2+
import type { ExportNamedDeclaration } from '@babel/types';
23
import * as fs from 'fs';
4+
import { parseModule } from 'magicast';
35
import * as path from 'path';
46
import type { Plugin } from 'vite';
57

@@ -89,24 +91,50 @@ export function makeAutoInstrumentationPlugin(options: AutoInstrumentPluginOptio
8991
*/
9092
export async function canWrapLoad(id: string, debug: boolean): Promise<boolean> {
9193
const code = (await fs.promises.readFile(id, 'utf8')).toString();
94+
const mod = parseModule(code);
9295

93-
const codeWithoutComments = code.replace(/(\/\/.*| ?\/\*[^]*?\*\/)(,?)$/gm, '');
94-
95-
const hasSentryContent = codeWithoutComments.includes('@sentry/sveltekit');
96-
if (hasSentryContent) {
96+
const program = mod.$ast.type === 'Program' && mod.$ast;
97+
if (!program) {
9798
// eslint-disable-next-line no-console
98-
debug && console.log(`Skipping wrapping ${id} because it already contains Sentry code`);
99+
debug && console.log(`Skipping wrapping ${id} because it doesn't contain valid JavaScript or TypeScript`);
100+
return false;
99101
}
100102

101-
const hasLoadDeclaration = /((const|let|var|function)\s+load\s*(=|\(|:))|as\s+load\s*(,|})/gm.test(
102-
codeWithoutComments,
103-
);
103+
const hasLoadDeclaration = program.body
104+
.filter((statement): statement is ExportNamedDeclaration => statement.type === 'ExportNamedDeclaration')
105+
.find(exportDecl => {
106+
// find `export const load = ...`
107+
if (exportDecl.declaration && exportDecl.declaration.type === 'VariableDeclaration') {
108+
const variableDeclarations = exportDecl.declaration.declarations;
109+
return variableDeclarations.find(decl => decl.id.type === 'Identifier' && decl.id.name === 'load');
110+
}
111+
112+
// find `export function load = ...`
113+
if (exportDecl.declaration && exportDecl.declaration.type === 'FunctionDeclaration') {
114+
const functionId = exportDecl.declaration.id;
115+
return functionId?.name === 'load';
116+
}
117+
118+
// find `export { load, somethingElse as load, somethingElse as "load" }`
119+
if (exportDecl.specifiers) {
120+
return exportDecl.specifiers.find(specifier => {
121+
return (
122+
(specifier.exported.type === 'Identifier' && specifier.exported.name === 'load') ||
123+
(specifier.exported.type === 'StringLiteral' && specifier.exported.value === 'load')
124+
);
125+
});
126+
}
127+
128+
return false;
129+
});
130+
104131
if (!hasLoadDeclaration) {
105132
// eslint-disable-next-line no-console
106133
debug && console.log(`Skipping wrapping ${id} because it doesn't declare a \`load\` function`);
134+
return false;
107135
}
108136

109-
return !hasSentryContent && hasLoadDeclaration;
137+
return true;
110138
}
111139

112140
/**

packages/sveltekit/test/vite/autoInstrument.test.ts

+10-14
Original file line numberDiff line numberDiff line change
@@ -121,6 +121,7 @@ describe('canWrapLoad', () => {
121121
['export variable declaration - function pointer', 'export const load= loadPageData'],
122122
['export variable declaration - factory function call', 'export const load =loadPageData()'],
123123
['export variable declaration - inline function', 'export const load = () => { return { props: { msg: "hi" } } }'],
124+
['export variable declaration - inline function let', 'export let load = () => {}'],
124125
[
125126
'export variable declaration - inline async function',
126127
'export const load = async () => { return { props: { msg: "hi" } } }',
@@ -139,14 +140,14 @@ describe('canWrapLoad', () => {
139140
'variable declaration (let)',
140141
`import {something} from 'somewhere';
141142
let load = async () => {};
142-
export prerender = true;
143+
export const prerender = true;
143144
export { load}`,
144145
],
145146
[
146147
'variable declaration (var)',
147148
`import {something} from 'somewhere';
148149
var load=async () => {};
149-
export prerender = true;
150+
export const prerender = true;
150151
export { load}`,
151152
],
152153

@@ -176,13 +177,18 @@ describe('canWrapLoad', () => {
176177
async function somethingElse(){};
177178
export { somethingElse as load, foo }`,
178179
],
179-
180+
[
181+
'function declaration with different string literal name',
182+
`import { foo } from 'somewhere';
183+
async function somethingElse(){};
184+
export { somethingElse as "load", foo }`,
185+
],
180186
[
181187
'export variable declaration - inline function with assigned type',
182188
`import type { LayoutLoad } from './$types';
183189
export const load : LayoutLoad = async () => { return { props: { msg: "hi" } } }`,
184190
],
185-
])('returns `true` if a load declaration (%s) exists and no Sentry code was found', async (_, code) => {
191+
])('returns `true` if a load declaration (%s) exists', async (_, code) => {
186192
fileContent = code;
187193
expect(await canWrapLoad('+page.ts', false)).toEqual(true);
188194
});
@@ -203,14 +209,4 @@ describe('canWrapLoad', () => {
203209
fileContent = code;
204210
expect(await canWrapLoad('+page.ts', false)).toEqual(true);
205211
});
206-
207-
it('returns `false` if Sentry code was found', async () => {
208-
fileContent = 'import * as Sentry from "@sentry/sveltekit";';
209-
expect(await canWrapLoad('+page.ts', false)).toEqual(false);
210-
});
211-
212-
it('returns `false` if Sentry code was found', async () => {
213-
fileContent = 'import * as Sentry from "@sentry/sveltekit";';
214-
expect(await canWrapLoad('+page.ts', false)).toEqual(false);
215-
});
216212
});

yarn.lock

+46
Original file line numberDiff line numberDiff line change
@@ -970,6 +970,11 @@
970970
resolved "https://registry.yarnpkg.com/@babel/helper-string-parser/-/helper-string-parser-7.19.4.tgz#38d3acb654b4701a9b77fb0615a96f775c3a9e63"
971971
integrity sha512-nHtDoQcuqFmwYNYPz3Rah5ph2p8PFeFCsZk9A/48dPc/rGocJ5J3hAAZ7pb76VWX3fZKu+uEr/FhH5jLx7umrw==
972972

973+
"@babel/helper-string-parser@^7.21.5":
974+
version "7.21.5"
975+
resolved "https://registry.yarnpkg.com/@babel/helper-string-parser/-/helper-string-parser-7.21.5.tgz#2b3eea65443c6bdc31c22d037c65f6d323b6b2bd"
976+
integrity sha512-5pTUx3hAJaZIdW99sJ6ZUUgWq/Y+Hja7TowEnLNMm1VivRgZQL3vpBY3qUACVsvw+yQU6+YgfBVmcbLaZtrA1w==
977+
973978
"@babel/helper-validator-identifier@^7.18.6", "@babel/helper-validator-identifier@^7.19.1":
974979
version "7.19.1"
975980
resolved "https://registry.yarnpkg.com/@babel/helper-validator-identifier/-/helper-validator-identifier-7.19.1.tgz#7eea834cf32901ffdc1a7ee555e2f9c27e249ca2"
@@ -1027,6 +1032,11 @@
10271032
resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.20.15.tgz#eec9f36d8eaf0948bb88c87a46784b5ee9fd0c89"
10281033
integrity sha512-DI4a1oZuf8wC+oAJA9RW6ga3Zbe8RZFt7kD9i4qAspz3I/yHet1VvC3DiSy/fsUvv5pvJuNPh0LPOdCcqinDPg==
10291034

1035+
"@babel/parser@^7.21.8":
1036+
version "7.21.8"
1037+
resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.21.8.tgz#642af7d0333eab9c0ad70b14ac5e76dbde7bfdf8"
1038+
integrity sha512-6zavDGdzG3gUqAdWvlLFfk+36RilI+Pwyuuh7HItyeScCWP3k6i8vKclAQ0bM/0y/Kz/xiwvxhMv9MgTJP5gmA==
1039+
10301040
"@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression@^7.18.6":
10311041
version "7.18.6"
10321042
resolved "https://registry.yarnpkg.com/@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression/-/plugin-bugfix-safari-id-destructuring-collision-in-function-expression-7.18.6.tgz#da5b8f9a580acdfbe53494dba45ea389fb09a4d2"
@@ -2218,6 +2228,15 @@
22182228
"@babel/helper-validator-identifier" "^7.19.1"
22192229
to-fast-properties "^2.0.0"
22202230

2231+
"@babel/types@^7.21.5":
2232+
version "7.21.5"
2233+
resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.21.5.tgz#18dfbd47c39d3904d5db3d3dc2cc80bedb60e5b6"
2234+
integrity sha512-m4AfNvVF2mVC/F7fDEdH2El3HzUg9It/XsCxZiOTTA3m3qYfcSVSbTfM6Q9xG+hYDniZssYhlXKKUMD5m8tF4Q==
2235+
dependencies:
2236+
"@babel/helper-string-parser" "^7.21.5"
2237+
"@babel/helper-validator-identifier" "^7.19.1"
2238+
to-fast-properties "^2.0.0"
2239+
22212240
"@bcoe/v8-coverage@^0.2.3":
22222241
version "0.2.3"
22232242
resolved "https://registry.yarnpkg.com/@bcoe/v8-coverage/-/v8-coverage-0.2.3.tgz#75a2e8b51cb758a7553d6804a5932d7aace75c39"
@@ -6565,6 +6584,13 @@ ast-types@0.14.2:
65656584
dependencies:
65666585
tslib "^2.0.1"
65676586

6587+
ast-types@0.15.2:
6588+
version "0.15.2"
6589+
resolved "https://registry.yarnpkg.com/ast-types/-/ast-types-0.15.2.tgz#39ae4809393c4b16df751ee563411423e85fb49d"
6590+
integrity sha512-c27loCv9QkZinsa5ProX751khO9DJl/AcB5c2KNtA6NRvHKS0PgLfcftz72KVq504vB0Gku5s2kUZzDBvQWvHg==
6591+
dependencies:
6592+
tslib "^2.0.1"
6593+
65686594
astral-regex@^2.0.0:
65696595
version "2.0.0"
65706596
resolved "https://registry.yarnpkg.com/astral-regex/-/astral-regex-2.0.0.tgz#483143c567aeed4785759c0865786dc77d7d2e31"
@@ -17972,6 +17998,15 @@ magic-string@^0.30.0:
1797217998
dependencies:
1797317999
"@jridgewell/sourcemap-codec" "^1.4.13"
1797418000

18001+
magicast@0.2.6:
18002+
version "0.2.6"
18003+
resolved "https://registry.yarnpkg.com/magicast/-/magicast-0.2.6.tgz#08c9f1900177ca1896e9c07981912171d4ed8ec1"
18004+
integrity sha512-6bX0nVjGrA41o+qHSv9Duiv3VuF7jUyjT7dIb3E61YW/5mucvCBMgyZssUznRc+xlUMPYyXZZluZjE1k5z+2yQ==
18005+
dependencies:
18006+
"@babel/parser" "^7.21.8"
18007+
"@babel/types" "^7.21.5"
18008+
recast "^0.22.0"
18009+
1797518010
make-dir@3.1.0, make-dir@^3.0.0, make-dir@^3.0.2, make-dir@^3.1.0, make-dir@~3.1.0:
1797618011
version "3.1.0"
1797718012
resolved "https://registry.yarnpkg.com/make-dir/-/make-dir-3.1.0.tgz#415e967046b3a7f1d185277d84aa58203726a13f"
@@ -22902,6 +22937,17 @@ recast@^0.20.5:
2290222937
source-map "~0.6.1"
2290322938
tslib "^2.0.1"
2290422939

22940+
recast@^0.22.0:
22941+
version "0.22.0"
22942+
resolved "https://registry.yarnpkg.com/recast/-/recast-0.22.0.tgz#1dd3bf1b86e5eb810b044221a1a734234ed3e9c0"
22943+
integrity sha512-5AAx+mujtXijsEavc5lWXBPQqrM4+Dl5qNH96N2aNeuJFUzpiiToKPsxQD/zAIJHspz7zz0maX0PCtCTFVlixQ==
22944+
dependencies:
22945+
assert "^2.0.0"
22946+
ast-types "0.15.2"
22947+
esprima "~4.0.0"
22948+
source-map "~0.6.1"
22949+
tslib "^2.0.1"
22950+
2290522951
rechoir@^0.6.2:
2290622952
version "0.6.2"
2290722953
resolved "https://registry.yarnpkg.com/rechoir/-/rechoir-0.6.2.tgz#85204b54dba82d5742e28c96756ef43af50e3384"

0 commit comments

Comments
 (0)