-
Notifications
You must be signed in to change notification settings - Fork 804
/
RequireInTheMiddleSingleton.ts
128 lines (115 loc) · 4.1 KB
/
RequireInTheMiddleSingleton.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
/*
* Copyright The OpenTelemetry Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import * as RequireInTheMiddle from 'require-in-the-middle';
import * as path from 'path';
import { ModuleNameTrie, ModuleNameSeparator } from './ModuleNameTrie';
export type Hooked = {
moduleName: string;
onRequire: RequireInTheMiddle.OnRequireFn;
};
/**
* Whether Mocha is running in this process
* Inspired by https://github.com/AndreasPizsa/detect-mocha
*
* @type {boolean}
*/
const isMocha = [
'afterEach',
'after',
'beforeEach',
'before',
'describe',
'it',
].every(fn => {
// @ts-expect-error TS7053: Element implicitly has an 'any' type
return typeof global[fn] === 'function';
});
/**
* Singleton class for `require-in-the-middle`
* Allows instrumentation plugins to patch modules with only a single `require` patch
* WARNING: Because this class will create its own `require-in-the-middle` (RITM) instance,
* we should minimize the number of new instances of this class.
* Multiple instances of `@opentelemetry/instrumentation` (e.g. multiple versions) in a single process
* will result in multiple instances of RITM, which will have an impact
* on the performance of instrumentation hooks being applied.
*/
export class RequireInTheMiddleSingleton {
private _moduleNameTrie: ModuleNameTrie = new ModuleNameTrie();
private static _instance?: RequireInTheMiddleSingleton;
private constructor() {
this._initialize();
}
private _initialize() {
RequireInTheMiddle(
// Intercept all `require` calls; we will filter the matching ones below
null,
{ internals: true },
(exports, name, basedir) => {
// For internal files on Windows, `name` will use backslash as the path separator
const normalizedModuleName = normalizePathSeparators(name);
const matches = this._moduleNameTrie.search(normalizedModuleName, {
maintainInsertionOrder: true,
// For core modules (e.g. `fs`), do not match on sub-paths (e.g. `fs/promises').
// This matches the behavior of `require-in-the-middle`.
// `basedir` is always `undefined` for core modules.
fullOnly: basedir === undefined,
});
for (const { onRequire } of matches) {
exports = onRequire(exports, name, basedir);
}
return exports;
}
);
}
/**
* Register a hook with `require-in-the-middle`
*
* @param {string} moduleName Module name
* @param {RequireInTheMiddle.OnRequireFn} onRequire Hook function
* @returns {Hooked} Registered hook
*/
register(
moduleName: string,
onRequire: RequireInTheMiddle.OnRequireFn
): Hooked {
const hooked = { moduleName, onRequire };
this._moduleNameTrie.insert(hooked);
return hooked;
}
/**
* Get the `RequireInTheMiddleSingleton` singleton
*
* @returns {RequireInTheMiddleSingleton} Singleton of `RequireInTheMiddleSingleton`
*/
static getInstance(): RequireInTheMiddleSingleton {
// Mocha runs all test suites in the same process
// This prevents test suites from sharing a singleton
if (isMocha) return new RequireInTheMiddleSingleton();
return (this._instance =
this._instance ?? new RequireInTheMiddleSingleton());
}
}
/**
* Normalize the path separators to forward slash in a module name or path
*
* @param {string} moduleNameOrPath Module name or path
* @returns {string} Normalized module name or path
*/
function normalizePathSeparators(moduleNameOrPath: string): string {
return path.sep !== ModuleNameSeparator
? moduleNameOrPath.split(path.sep).join(ModuleNameSeparator)
: moduleNameOrPath;
}