Detect monkey patching and emit a deprecation warning.

This isn't comprehensive but it should be broad enough to ensure that most
people who would be affected by blocking monkey patching see a warning.
Covers the jasmine namespace as well as classes that are monkey patched by
zone.js.

Replacing globals (describe/it/etc) doesn't trigger a warning because they
belong to the user and are expected to be replaced.
This commit is contained in:
Steve Gravrock
2025-11-25 16:54:08 -08:00
parent 32168be6c7
commit 23894c1a0a
15 changed files with 315 additions and 47 deletions

View File

@@ -309,12 +309,14 @@ jasmineRequire.HtmlSpecFilter = function(j$) {
* @deprecated Use {@link HtmlReporterV2Urls} instead.
*/
function HtmlSpecFilter(options) {
j$.getEnv().deprecated(
const env = options?.env ?? j$.getEnv();
env.deprecated(
'HtmlReporter and HtmlSpecFilter are deprecated. Use HtmlReporterV2 instead.'
);
const filterString =
options &&
options.filterString &&
options.filterString() &&
options.filterString().replace(/[-[\]{}()*+?.,\\^$|#\s]/g, '\\$&');
const filterPattern = new RegExp(filterString);

View File

@@ -44,9 +44,14 @@ var getJasmineRequireObj = (function() {
}
getJasmineRequire().core = function(jRequire) {
const j$ = { private: {} };
const j$ = {};
Object.defineProperty(j$, 'private', {
enumerable: true,
value: {}
});
jRequire.base(j$, globalThis);
j$.private.deprecateMonkeyPatching = jRequire.deprecateMonkeyPatching(j$);
j$.private.util = jRequire.util(j$);
j$.private.errors = jRequire.errors();
j$.private.formatErrorMsg = jRequire.formatErrorMsg(j$);
@@ -56,7 +61,7 @@ var getJasmineRequireObj = (function() {
j$.private.CallTracker = jRequire.CallTracker(j$);
j$.private.MockDate = jRequire.MockDate(j$);
j$.private.getStackClearer = jRequire.StackClearer(j$);
j$.private.Clock = jRequire.Clock();
j$.private.Clock = jRequire.Clock(j$);
j$.private.DelayedFunctionScheduler = jRequire.DelayedFunctionScheduler(j$);
j$.private.Deprecator = jRequire.Deprecator(j$);
j$.private.Configuration = jRequire.Configuration(j$);
@@ -122,6 +127,20 @@ var getJasmineRequireObj = (function() {
j$.private.loadedAsBrowserEsm =
globalThis.document && !globalThis.document.currentScript;
j$.private.deprecateMonkeyPatching(j$, [
// These are meant to be set by users.
'DEFAULT_TIMEOUT_INTERVAL',
'MAX_PRETTY_PRINT_ARRAY_LENGTH',
'MAX_PRETTY_PRINT_CHARS',
'MAX_PRETTY_PRINT_DEPTH',
// These are part of the deprecation warning mechanism. To avoid infinite
// recursion, they're separately protected in a way that doesn't emit
// deprecation warnings.
'private',
'getEnv'
]);
return j$;
};
@@ -246,12 +265,15 @@ getJasmineRequireObj().base = function(j$, jasmineGlobal) {
* @function
* @return {Env}
*/
j$.getEnv = function(options) {
const env = (j$.private.currentEnv_ =
j$.private.currentEnv_ || new j$.private.Env(options));
//jasmine. singletons in here (setTimeout blah blah).
return env;
};
Object.defineProperty(j$, 'getEnv', {
enumerable: true,
value: function(options) {
const env = (j$.private.currentEnv_ =
j$.private.currentEnv_ || new j$.private.Env(options));
//jasmine. singletons in here (setTimeout blah blah).
return env;
}
});
j$.private.isArray = function(value) {
return j$.private.isA('Array', value);
@@ -1520,10 +1542,13 @@ getJasmineRequireObj().Env = function(j$) {
* @param {String|Error} deprecation The deprecation message
* @param {Object} [options] Optional extra options, as described above
*/
this.deprecated = function(deprecation, options) {
const runable = runner.currentRunable() || topSuite;
deprecator.addDeprecationWarning(runable, deprecation, options);
};
Object.defineProperty(this, 'deprecated', {
enumerable: true,
value: function(deprecation, options) {
const runable = runner.currentRunable() || topSuite;
deprecator.addDeprecationWarning(runable, deprecation, options);
}
});
function runQueue(options) {
options.clearStack = options.clearStack || stackClearer;
@@ -1550,7 +1575,8 @@ getJasmineRequireObj().Env = function(j$) {
runQueue
});
topSuite = suiteBuilder.topSuite;
const deprecator = new j$.private.Deprecator(topSuite);
const deprecator =
envOptions?.deprecator ?? new j$.private.Deprecator(topSuite);
/**
* Provides the root suite, through which all suites and specs can be
@@ -2055,6 +2081,8 @@ getJasmineRequireObj().Env = function(j$) {
this.cleanup_ = function() {
uninstallGlobalErrors();
};
j$.private.deprecateMonkeyPatching(this, ['deprecated']);
}
function indirectCallerFilename(depth) {
@@ -2927,7 +2955,7 @@ getJasmineRequireObj().CallTracker = function(j$) {
return CallTracker;
};
getJasmineRequireObj().Clock = function() {
getJasmineRequireObj().Clock = function(j$) {
'use strict';
/* global process */
@@ -3120,6 +3148,9 @@ callbacks to execute _before_ running the next one.
clearTimeout[IsMockClockTimingFn] = true;
setInterval[IsMockClockTimingFn] = true;
clearInterval[IsMockClockTimingFn] = true;
j$.private.deprecateMonkeyPatching(this);
return this;
// Advances the Clock's time until the mode changes.
@@ -3828,6 +3859,29 @@ getJasmineRequireObj().DelayedFunctionScheduler = function(j$) {
return DelayedFunctionScheduler;
};
getJasmineRequireObj().deprecateMonkeyPatching = function(j$) {
return function deprecateMonkeyPatching(obj, keysToSkip) {
for (const key of Object.keys(obj)) {
if (!keysToSkip?.includes(key)) {
let value = obj[key];
Object.defineProperty(obj, key, {
enumerable: key in obj,
get() {
return value;
},
set(newValue) {
j$.getEnv().deprecated(
'Monkey patching detected. This is not supported and will break in a future jasmine-core release.'
);
value = newValue;
}
});
}
}
};
};
getJasmineRequireObj().Deprecator = function(j$) {
'use strict';
@@ -9378,6 +9432,7 @@ getJasmineRequireObj().interface = function(jasmine, env) {
*/
jasmine: jasmine
};
const existingKeys = Object.keys(jasmine);
/**
* Add a custom equality tester for the current scope of specs.
@@ -9544,6 +9599,8 @@ getJasmineRequireObj().interface = function(jasmine, env) {
* @namespace asymmetricEqualityTesters
*/
jasmine.private.deprecateMonkeyPatching(jasmine, existingKeys);
return jasmineInterface;
};