Skip to content

Commit b06903c

Browse files
committed
fix(ruleset-bundler): builtins plugin should create a new instance for each module
1 parent 0263bf0 commit b06903c

File tree

2 files changed

+116
-45
lines changed

2 files changed

+116
-45
lines changed

packages/ruleset-bundler/src/plugins/__tests__/builtins.spec.ts

Lines changed: 96 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -12,12 +12,23 @@ import { builtins } from '../builtins';
1212

1313
describe('Builtins Plugin', () => {
1414
let io: IO;
15+
let randomSpy: jest.SpyInstance;
1516

1617
beforeEach(() => {
1718
io = {
1819
fs,
1920
fetch: runtime.fetch,
2021
};
22+
23+
randomSpy = jest
24+
.spyOn(Math, 'random')
25+
.mockReturnValueOnce(0.8229275205939697)
26+
.mockReturnValueOnce(0.7505242801973444)
27+
.mockReturnValueOnce(0.5647855410879519);
28+
});
29+
30+
afterEach(() => {
31+
randomSpy.mockRestore();
2132
});
2233

2334
describe.each<BundleOptions['target']>(['browser', 'runtime'])('given %s target', target => {
@@ -51,21 +62,21 @@ export default {
5162
});
5263

5364
expect(code)
54-
.toEqual(`const alphabetical = globalThis[Symbol.for('@stoplight/spectral-functions')]['alphabetical'];
55-
const casing = globalThis[Symbol.for('@stoplight/spectral-functions')]['casing'];
56-
const defined = globalThis[Symbol.for('@stoplight/spectral-functions')]['defined'];
57-
const enumeration = globalThis[Symbol.for('@stoplight/spectral-functions')]['enumeration'];
58-
const falsy = globalThis[Symbol.for('@stoplight/spectral-functions')]['falsy'];
59-
const length = globalThis[Symbol.for('@stoplight/spectral-functions')]['length'];
60-
const pattern = globalThis[Symbol.for('@stoplight/spectral-functions')]['pattern'];
61-
const schema = globalThis[Symbol.for('@stoplight/spectral-functions')]['schema'];
62-
const truthy = globalThis[Symbol.for('@stoplight/spectral-functions')]['truthy'];
63-
const undefined$1 = globalThis[Symbol.for('@stoplight/spectral-functions')]['undefined'];
64-
const unreferencedReusableObject = globalThis[Symbol.for('@stoplight/spectral-functions')]['unreferencedReusableObject'];
65-
const xor = globalThis[Symbol.for('@stoplight/spectral-functions')]['xor'];
66-
67-
const oas = globalThis[Symbol.for('@stoplight/spectral-rulesets')]['oas'];
68-
const asyncapi = globalThis[Symbol.for('@stoplight/spectral-rulesets')]['asyncapi'];
65+
.toEqual(`const alphabetical = globalThis[Symbol.for('@stoplight-spectral/builtins')]['822928']['@stoplight/spectral-functions']['alphabetical'];
66+
const casing = globalThis[Symbol.for('@stoplight-spectral/builtins')]['822928']['@stoplight/spectral-functions']['casing'];
67+
const defined = globalThis[Symbol.for('@stoplight-spectral/builtins')]['822928']['@stoplight/spectral-functions']['defined'];
68+
const enumeration = globalThis[Symbol.for('@stoplight-spectral/builtins')]['822928']['@stoplight/spectral-functions']['enumeration'];
69+
const falsy = globalThis[Symbol.for('@stoplight-spectral/builtins')]['822928']['@stoplight/spectral-functions']['falsy'];
70+
const length = globalThis[Symbol.for('@stoplight-spectral/builtins')]['822928']['@stoplight/spectral-functions']['length'];
71+
const pattern = globalThis[Symbol.for('@stoplight-spectral/builtins')]['822928']['@stoplight/spectral-functions']['pattern'];
72+
const schema = globalThis[Symbol.for('@stoplight-spectral/builtins')]['822928']['@stoplight/spectral-functions']['schema'];
73+
const truthy = globalThis[Symbol.for('@stoplight-spectral/builtins')]['822928']['@stoplight/spectral-functions']['truthy'];
74+
const undefined$1 = globalThis[Symbol.for('@stoplight-spectral/builtins')]['822928']['@stoplight/spectral-functions']['undefined'];
75+
const unreferencedReusableObject = globalThis[Symbol.for('@stoplight-spectral/builtins')]['822928']['@stoplight/spectral-functions']['unreferencedReusableObject'];
76+
const xor = globalThis[Symbol.for('@stoplight-spectral/builtins')]['822928']['@stoplight/spectral-functions']['xor'];
77+
78+
const oas = globalThis[Symbol.for('@stoplight-spectral/builtins')]['822928']['@stoplight/spectral-rulesets']['oas'];
79+
const asyncapi = globalThis[Symbol.for('@stoplight-spectral/builtins')]['822928']['@stoplight/spectral-rulesets']['asyncapi'];
6980
7081
var input = {
7182
extends: [oas],
@@ -87,7 +98,9 @@ var input = {
8798
export { input as default };
8899
`);
89100

90-
expect(globalThis[Symbol.for('@stoplight/spectral-functions')]).toStrictEqual(functions);
101+
expect(
102+
globalThis[Symbol.for('@stoplight-spectral/builtins')]['822928']['@stoplight/spectral-functions'],
103+
).toStrictEqual(functions);
91104
});
92105

93106
it('should support overrides', async () => {
@@ -113,30 +126,78 @@ readFile();`,
113126
],
114127
});
115128

116-
expect(code).toEqual(`const fetch = globalThis[Symbol.for('@stoplight/spectral-runtime')]['fetch'];
117-
const DEFAULT_REQUEST_OPTIONS = globalThis[Symbol.for('@stoplight/spectral-runtime')]['DEFAULT_REQUEST_OPTIONS'];
118-
const decodeSegmentFragment = globalThis[Symbol.for('@stoplight/spectral-runtime')]['decodeSegmentFragment'];
119-
const printError = globalThis[Symbol.for('@stoplight/spectral-runtime')]['printError'];
120-
const PrintStyle = globalThis[Symbol.for('@stoplight/spectral-runtime')]['PrintStyle'];
121-
const printPath = globalThis[Symbol.for('@stoplight/spectral-runtime')]['printPath'];
122-
const printValue = globalThis[Symbol.for('@stoplight/spectral-runtime')]['printValue'];
123-
const startsWithProtocol = globalThis[Symbol.for('@stoplight/spectral-runtime')]['startsWithProtocol'];
124-
const isAbsoluteRef = globalThis[Symbol.for('@stoplight/spectral-runtime')]['isAbsoluteRef'];
125-
const traverseObjUntilRef = globalThis[Symbol.for('@stoplight/spectral-runtime')]['traverseObjUntilRef'];
126-
const getEndRef = globalThis[Symbol.for('@stoplight/spectral-runtime')]['getEndRef'];
127-
const safePointerToPath = globalThis[Symbol.for('@stoplight/spectral-runtime')]['safePointerToPath'];
128-
const getClosestJsonPath = globalThis[Symbol.for('@stoplight/spectral-runtime')]['getClosestJsonPath'];
129-
const readFile = globalThis[Symbol.for('@stoplight/spectral-runtime')]['readFile'];
130-
const readParsable = globalThis[Symbol.for('@stoplight/spectral-runtime')]['readParsable'];
129+
expect(code)
130+
.toEqual(`const fetch = globalThis[Symbol.for('@stoplight-spectral/builtins')]['822928']['@stoplight/spectral-runtime']['fetch'];
131+
const DEFAULT_REQUEST_OPTIONS = globalThis[Symbol.for('@stoplight-spectral/builtins')]['822928']['@stoplight/spectral-runtime']['DEFAULT_REQUEST_OPTIONS'];
132+
const decodeSegmentFragment = globalThis[Symbol.for('@stoplight-spectral/builtins')]['822928']['@stoplight/spectral-runtime']['decodeSegmentFragment'];
133+
const printError = globalThis[Symbol.for('@stoplight-spectral/builtins')]['822928']['@stoplight/spectral-runtime']['printError'];
134+
const PrintStyle = globalThis[Symbol.for('@stoplight-spectral/builtins')]['822928']['@stoplight/spectral-runtime']['PrintStyle'];
135+
const printPath = globalThis[Symbol.for('@stoplight-spectral/builtins')]['822928']['@stoplight/spectral-runtime']['printPath'];
136+
const printValue = globalThis[Symbol.for('@stoplight-spectral/builtins')]['822928']['@stoplight/spectral-runtime']['printValue'];
137+
const startsWithProtocol = globalThis[Symbol.for('@stoplight-spectral/builtins')]['822928']['@stoplight/spectral-runtime']['startsWithProtocol'];
138+
const isAbsoluteRef = globalThis[Symbol.for('@stoplight-spectral/builtins')]['822928']['@stoplight/spectral-runtime']['isAbsoluteRef'];
139+
const traverseObjUntilRef = globalThis[Symbol.for('@stoplight-spectral/builtins')]['822928']['@stoplight/spectral-runtime']['traverseObjUntilRef'];
140+
const getEndRef = globalThis[Symbol.for('@stoplight-spectral/builtins')]['822928']['@stoplight/spectral-runtime']['getEndRef'];
141+
const safePointerToPath = globalThis[Symbol.for('@stoplight-spectral/builtins')]['822928']['@stoplight/spectral-runtime']['safePointerToPath'];
142+
const getClosestJsonPath = globalThis[Symbol.for('@stoplight-spectral/builtins')]['822928']['@stoplight/spectral-runtime']['getClosestJsonPath'];
143+
const readFile = globalThis[Symbol.for('@stoplight-spectral/builtins')]['822928']['@stoplight/spectral-runtime']['readFile'];
144+
const readParsable = globalThis[Symbol.for('@stoplight-spectral/builtins')]['822928']['@stoplight/spectral-runtime']['readParsable'];
131145
132146
readFile();
133147
`);
134148

135-
expect(globalThis[Symbol.for('@stoplight/spectral-runtime')]).toStrictEqual({
149+
expect(
150+
globalThis[Symbol.for('@stoplight-spectral/builtins')]['822928']['@stoplight/spectral-runtime'],
151+
).toStrictEqual({
136152
...runtime,
137153
readFile,
138154
});
139155
});
156+
157+
it('should isolate each instance', async () => {
158+
serveAssets({
159+
'/tmp/input.js': `import { readFile } from '@stoplight/spectral-runtime';
160+
161+
readFile();`,
162+
});
163+
164+
// eslint-disable-next-line @typescript-eslint/no-empty-function
165+
function readFile(): void {}
166+
// eslint-disable-next-line @typescript-eslint/no-empty-function
167+
function readFile2(): void {}
168+
169+
await bundleRuleset('/tmp/input.js', {
170+
format: 'esm',
171+
target,
172+
plugins: [
173+
builtins({
174+
'@stoplight/spectral-runtime': {
175+
readFile,
176+
},
177+
}),
178+
builtins({
179+
'@stoplight/spectral-runtime': {
180+
readFile: readFile2,
181+
},
182+
}),
183+
virtualFs(io),
184+
],
185+
});
186+
187+
expect(
188+
globalThis[Symbol.for('@stoplight-spectral/builtins')]['822928']['@stoplight/spectral-runtime'],
189+
).toStrictEqual({
190+
...runtime,
191+
readFile,
192+
});
193+
194+
expect(
195+
globalThis[Symbol.for('@stoplight-spectral/builtins')]['750524']['@stoplight/spectral-runtime'],
196+
).toStrictEqual({
197+
...runtime,
198+
readFile: readFile2,
199+
});
200+
});
140201
});
141202

142203
describe('given node target', () => {
@@ -191,7 +252,9 @@ var input = {
191252
export { input as default };
192253
`);
193254

194-
expect(globalThis[Symbol.for('@stoplight/spectral-functions')]).toStrictEqual(functions);
255+
expect(
256+
globalThis[Symbol.for('@stoplight-spectral/builtins')]['822928']['@stoplight/spectral-functions'],
257+
).toStrictEqual(functions);
195258
});
196259
});
197260
});

packages/ruleset-bundler/src/plugins/builtins.ts

Lines changed: 20 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -11,15 +11,21 @@ type Module = 'core' | 'formats' | 'functions' | 'parsers' | 'ref-resolver' | 'r
1111
type GlobalModules = Record<`@stoplight/spectral-${Module}`, string>;
1212
type Overrides = Record<keyof GlobalModules, Record<string, unknown>>;
1313

14+
const NAME = '@stoplight-spectral/builtins';
15+
1416
function registerModule(
17+
instanceId: number,
1518
id: keyof GlobalModules,
1619
members: Record<string, unknown>,
1720
overrides: Partial<Overrides>,
1821
): [string, string] {
1922
const actualOverrides = overrides[id];
20-
globalThis[Symbol.for(id)] = actualOverrides ? { ...members, ...actualOverrides } : members;
23+
const instances = (globalThis[Symbol.for(NAME)] ??= {}) as Record<string, Partial<Overrides>>;
24+
const root = (instances[instanceId] ??= {});
25+
26+
root[id] = actualOverrides ? { ...members, ...actualOverrides } : members;
2127

22-
const m = `globalThis[Symbol.for('${id}')]`;
28+
const m = `globalThis[Symbol.for('${NAME}')]['${instanceId}']['${id}']`;
2329
let code = '';
2430
for (const member of Object.keys(members)) {
2531
code += `export const ${member} = ${m}['${member}'];\n`;
@@ -29,26 +35,28 @@ function registerModule(
2935
}
3036

3137
export const builtins = (overrides: Partial<Overrides> = {}): Plugin => {
38+
const instanceId = Math.round(Math.random() * 1_000_000);
39+
3240
const modules = Object.fromEntries([
33-
registerModule('@stoplight/spectral-core', core, overrides),
34-
registerModule('@stoplight/spectral-formats', formats, overrides),
35-
registerModule('@stoplight/spectral-functions', functions, overrides),
36-
registerModule('@stoplight/spectral-parsers', parsers, overrides),
37-
registerModule('@stoplight/spectral-ref-resolver', refResolver, overrides),
38-
registerModule('@stoplight/spectral-rulesets', rulesets, overrides),
39-
registerModule('@stoplight/spectral-runtime', runtime, overrides),
41+
registerModule(instanceId, '@stoplight/spectral-core', core, overrides),
42+
registerModule(instanceId, '@stoplight/spectral-formats', formats, overrides),
43+
registerModule(instanceId, '@stoplight/spectral-functions', functions, overrides),
44+
registerModule(instanceId, '@stoplight/spectral-parsers', parsers, overrides),
45+
registerModule(instanceId, '@stoplight/spectral-ref-resolver', refResolver, overrides),
46+
registerModule(instanceId, '@stoplight/spectral-rulesets', rulesets, overrides),
47+
registerModule(instanceId, '@stoplight/spectral-runtime', runtime, overrides),
4048
]) as GlobalModules;
4149

4250
return {
43-
name: '@stoplight-spectral/builtins',
44-
resolveId(id) {
51+
name: NAME,
52+
resolveId(id): string | null {
4553
if (id in modules) {
4654
return id;
4755
}
4856

4957
return null;
5058
},
51-
load(id) {
59+
load(id): string | undefined {
5260
if (id in modules) {
5361
return modules[id] as string;
5462
}

0 commit comments

Comments
 (0)