Improved & unified deprecation handling

* De-duplication now happens in core, not in reporters. This ensures that
  the console doesn't get flooded.
* Stack traces are opt-out, not opt-in.
* The current runnable is not reported or logged for certain deprecations
  where it's irrelevant.
* HtmlReporter shows stack traces in expandable widgets.
* Env#deprecated and Env#deprecatedOnceWithStack are merged.
This commit is contained in:
Steve Gravrock
2021-05-24 20:38:26 -07:00
parent 61fb353197
commit 6a2a30d540
19 changed files with 1328 additions and 302 deletions

90
src/core/Deprecator.js Normal file
View File

@@ -0,0 +1,90 @@
getJasmineRequireObj().Deprecator = function(j$) {
function Deprecator(topSuite) {
this.topSuite_ = topSuite;
this.verbose_ = false;
this.toSuppress_ = [];
}
var verboseNote =
'Note: This message will be shown only once. ' +
'Set config.verboseDeprecations to true to see every occurrence.';
Deprecator.prototype.verboseDeprecations = function(enabled) {
this.verbose_ = enabled;
};
// runnable is a spec or a suite.
// deprecation is a string or an Error.
// See Env#deprecated for a description of the options argument.
Deprecator.prototype.addDeprecationWarning = function(
runnable,
deprecation,
options
) {
options = options || {};
if (!this.verbose_ && !j$.isError_(deprecation)) {
if (this.toSuppress_.indexOf(deprecation) !== -1) {
return;
}
this.toSuppress_.push(deprecation);
}
this.log_(runnable, deprecation, options);
this.report_(runnable, deprecation, options);
};
Deprecator.prototype.log_ = function(runnable, deprecation, options) {
var context;
if (j$.isError_(deprecation)) {
console.error(deprecation);
return;
}
if (runnable === this.topSuite_ || options.ignoreRunnable) {
context = '';
} else if (runnable.children) {
context = ' (in suite: ' + runnable.getFullName() + ')';
} else {
context = ' (in spec: ' + runnable.getFullName() + ')';
}
if (!options.omitStackTrace) {
context += '\n' + this.stackTrace_();
}
if (!this.verbose_) {
context += '\n' + verboseNote;
}
console.error('DEPRECATION: ' + deprecation + context);
};
Deprecator.prototype.stackTrace_ = function() {
var formatter = new j$.ExceptionFormatter();
return formatter.stack(j$.util.errorWithStack()).replace(/^Error\n/m, '');
};
Deprecator.prototype.report_ = function(runnable, deprecation, options) {
if (options.ignoreRunnable) {
runnable = this.topSuite_;
}
if (j$.isError_(deprecation)) {
runnable.addDeprecationWarning(deprecation);
return;
}
if (!this.verbose_) {
deprecation += '\n' + verboseNote;
}
runnable.addDeprecationWarning({
message: deprecation,
omitStackTrace: options.omitStackTrace || false
});
};
return Deprecator;
};

View File

@@ -32,7 +32,6 @@ getJasmineRequireObj().Env = function(j$) {
var currentlyExecutingSuites = [];
var currentDeclarationSuite = null;
var hasFailures = false;
var deprecationsToSuppress = [];
/**
* This represents the available options to configure Jasmine.
@@ -228,6 +227,7 @@ getJasmineRequireObj().Env = function(j$) {
if (configuration.hasOwnProperty('verboseDeprecations')) {
config.verboseDeprecations = configuration.verboseDeprecations;
deprecator.verboseDeprecations(config.verboseDeprecations);
}
};
@@ -249,13 +249,19 @@ getJasmineRequireObj().Env = function(j$) {
Object.defineProperty(this, 'specFilter', {
get: function() {
self.deprecated(
'Getting specFilter directly from Env is deprecated and will be removed in a future version of Jasmine, please check the specFilter option from `configuration`'
'Getting specFilter directly from Env is deprecated and will be ' +
'removed in a future version of Jasmine. Please check the ' +
'specFilter option from `configuration` instead.',
{ ignoreRunnable: true }
);
return config.specFilter;
},
set: function(val) {
self.deprecated(
'Setting specFilter directly on Env is deprecated and will be removed in a future version of Jasmine, please use the specFilter option in `configure`'
'Setting specFilter directly on Env is deprecated and will be ' +
'removed in a future version of Jasmine. Please use the ' +
'specFilter option in `configure` instead.',
{ ignoreRunnable: true }
);
config.specFilter = val;
}
@@ -303,7 +309,7 @@ getJasmineRequireObj().Env = function(j$) {
for (var matcherName in matchersToAdd) {
if (matchersToAdd[matcherName].length > 1) {
self.deprecatedOnceWithStack(
self.deprecated(
'The matcher factory for "' +
matcherName +
'" ' +
@@ -328,7 +334,7 @@ getJasmineRequireObj().Env = function(j$) {
for (var matcherName in matchersToAdd) {
if (matchersToAdd[matcherName].length > 1) {
self.deprecatedOnceWithStack(
self.deprecated(
'The matcher factory for "' +
matcherName +
'" ' +
@@ -535,14 +541,21 @@ getJasmineRequireObj().Env = function(j$) {
*/
this.throwOnExpectationFailure = function(value) {
this.deprecated(
'Setting throwOnExpectationFailure directly on Env is deprecated and will be removed in a future version of Jasmine, please use the oneFailurePerSpec option in `configure`'
'Setting throwOnExpectationFailure directly on Env is deprecated ' +
'and will be removed in a future version of Jasmine. Please use the ' +
'oneFailurePerSpec option in `configure` instead.',
{ ignoreRunnable: true }
);
this.configure({ oneFailurePerSpec: !!value });
};
this.throwingExpectationFailures = function() {
this.deprecated(
'Getting throwingExpectationFailures directly from Env is deprecated and will be removed in a future version of Jasmine, please check the oneFailurePerSpec option from `configuration`'
'Getting throwingExpectationFailures directly from Env is ' +
'deprecated and will be removed in a future version of Jasmine. ' +
'Please check the oneFailurePerSpec option from `configuration` ' +
'instead.',
{ ignoreRunnable: true }
);
return config.oneFailurePerSpec;
};
@@ -557,14 +570,20 @@ getJasmineRequireObj().Env = function(j$) {
*/
this.stopOnSpecFailure = function(value) {
this.deprecated(
'Setting stopOnSpecFailure directly is deprecated and will be removed in a future version of Jasmine, please use the failFast option in `configure`'
'Setting stopOnSpecFailure directly is deprecated and will be ' +
'removed in a future version of Jasmine. Please use the failFast ' +
'option in `configure` instead.',
{ ignoreRunnable: true }
);
this.configure({ failFast: !!value });
};
this.stoppingOnSpecFailure = function() {
this.deprecated(
'Getting stoppingOnSpecFailure directly from Env is deprecated and will be removed in a future version of Jasmine, please check the failFast option from `configuration`'
'Getting stoppingOnSpecFailure directly from Env is deprecated ' +
'and will be removed in a future version of Jasmine. Please check ' +
'the failFast option from `configuration` instead.',
{ ignoreRunnable: true }
);
return config.failFast;
};
@@ -579,14 +598,20 @@ getJasmineRequireObj().Env = function(j$) {
*/
this.randomizeTests = function(value) {
this.deprecated(
'Setting randomizeTests directly is deprecated and will be removed in a future version of Jasmine, please use the random option in `configure`'
'Setting randomizeTests directly is deprecated and will be removed ' +
'in a future version of Jasmine. Please use the random option in ' +
'`configure` instead.',
{ ignoreRunnable: true }
);
config.random = !!value;
};
this.randomTests = function() {
this.deprecated(
'Getting randomTests directly from Env is deprecated and will be removed in a future version of Jasmine, please check the random option from `configuration`'
'Getting randomTests directly from Env is deprecated and will be ' +
'removed in a future version of Jasmine. Please check the random ' +
'option from `configuration` instead.',
{ ignoreRunnable: true }
);
return config.random;
};
@@ -601,7 +626,10 @@ getJasmineRequireObj().Env = function(j$) {
*/
this.seed = function(value) {
this.deprecated(
'Setting seed directly is deprecated and will be removed in a future version of Jasmine, please use the seed option in `configure`'
'Setting seed directly is deprecated and will be removed in a ' +
'future version of Jasmine. Please use the seed option in ' +
'`configure` instead.',
{ ignoreRunnable: true }
);
if (value) {
config.seed = value;
@@ -611,7 +639,10 @@ getJasmineRequireObj().Env = function(j$) {
this.hidingDisabled = function(value) {
this.deprecated(
'Getting hidingDisabled directly from Env is deprecated and will be removed in a future version of Jasmine, please check the hideDisabled option from `configuration`'
'Getting hidingDisabled directly from Env is deprecated and will ' +
'be removed in a future version of Jasmine. Please check the ' +
'hideDisabled option from `configuration` instead.',
{ ignoreRunnable: true }
);
return config.hideDisabled;
};
@@ -623,53 +654,39 @@ getJasmineRequireObj().Env = function(j$) {
*/
this.hideDisabled = function(value) {
this.deprecated(
'Setting hideDisabled directly is deprecated and will be removed in a future version of Jasmine, please use the hideDisabled option in `configure`'
'Setting hideDisabled directly is deprecated and will be removed ' +
'in a future version of Jasmine. Please use the hideDisabled option ' +
'in `configure` instead.',
{ ignoreRunnable: true }
);
config.hideDisabled = !!value;
};
this.deprecated = function(deprecation) {
/**
* Causes a deprecation warning to be logged to the console and reported to
* reporters.
*
* The optional second parameter is an object that can have either of the
* following properties:
*
* omitStackTrace: Whether to omit the stack trace. Optional. Defaults to
* false. This option is ignored if the deprecation is an Error. Set this
* when the stack trace will not contain anything that helps the user find
* the source of the deprecation.
*
* ignoreRunnable: Whether to log the deprecation on the root suite, ignoring
* the spec or suite that's running when it happens. Optional. Defaults to
* false.
*
* @name Env#deprecated
* @since 2.99
* @function
* @param {String|Error} deprecation The deprecation message
* @param {Object} [options] Optional extra options, as described above
*/
this.deprecated = function(deprecation, options) {
var runnable = currentRunnable() || topSuite;
var context;
if (runnable === topSuite) {
context = '';
} else if (runnable === currentSuite()) {
context = ' (in suite: ' + runnable.getFullName() + ')';
} else {
context = ' (in spec: ' + runnable.getFullName() + ')';
}
runnable.addDeprecationWarning(deprecation);
if (
typeof console !== 'undefined' &&
typeof console.error === 'function'
) {
console.error('DEPRECATION: ' + deprecation + context);
}
};
this.deprecatedOnceWithStack = function(deprecation) {
var formatter = new j$.ExceptionFormatter(),
stackTrace = formatter
.stack(j$.util.errorWithStack())
.replace(/^Error\n/m, '');
if (config.verboseDeprecations) {
this.deprecated(deprecation + '\n' + stackTrace);
} else {
if (deprecationsToSuppress.indexOf(deprecation) === -1) {
this.deprecated(
deprecation +
'\n' +
'Note: This message will be shown only once. ' +
'Set config.verboseDeprecations to true to see every occurrence.\n' +
stackTrace
);
}
deprecationsToSuppress.push(deprecation);
}
deprecator.addDeprecationWarning(runnable, deprecation, options);
};
var queueRunnerFactory = function(options, args) {
@@ -705,6 +722,7 @@ getJasmineRequireObj().Env = function(j$) {
asyncExpectationFactory: suiteAsyncExpectationFactory,
expectationResultFactory: expectationResultFactory
});
var deprecator = new j$.Deprecator(topSuite);
defaultResourcesForRunnable(topSuite.id);
currentDeclarationSuite = topSuite;

View File

@@ -16,7 +16,7 @@ getJasmineRequireObj().buildExpectationResult = function(j$) {
var result = {
matcherName: options.matcherName,
message: message(),
stack: stack(),
stack: options.omitStackTrace ? '' : stack(),
passed: options.passed
};

View File

@@ -215,25 +215,29 @@ getJasmineRequireObj().QueueRunner = function(j$) {
};
QueueRunner.prototype.diagnoseConflictingAsync_ = function(fn, retval) {
var msg;
if (retval && j$.isFunction_(retval.then)) {
// Issue a warning that matches the user's code
// Issue a warning that matches the user's code.
// Omit the stack trace because there's almost certainly no user code
// on the stack at this point.
if (j$.isAsyncFunction_(fn)) {
this.deprecated(
msg =
'An asynchronous before/it/after ' +
'function was defined with the async keyword but also took a ' +
'done callback. This is not supported and will stop working in' +
' the future. Either remove the done callback (recommended) or ' +
'remove the async keyword.'
);
'function was defined with the async keyword but also took a ' +
'done callback. This is not supported and will stop working in' +
' the future. Either remove the done callback (recommended) or ' +
'remove the async keyword.';
} else {
this.deprecated(
msg =
'An asynchronous before/it/after ' +
'function took a done callback but also returned a promise. ' +
'This is not supported and will stop working in the future. ' +
'Either remove the done callback (recommended) or change the ' +
'function to not return a promise.'
);
'function took a done callback but also returned a promise. ' +
'This is not supported and will stop working in the future. ' +
'Either remove the done callback (recommended) or change the ' +
'function to not return a promise.';
}
this.deprecated(msg, { omitStackTrace: true });
}
};

View File

@@ -35,7 +35,7 @@ getJasmineRequireObj().MatchersUtil = function(j$) {
*/
MatchersUtil.prototype.contains = function(haystack, needle, customTesters) {
if (customTesters) {
j$.getEnv().deprecatedOnceWithStack(
j$.getEnv().deprecated(
'Passing custom equality testers ' +
'to MatchersUtil#contains is deprecated. ' +
'See <https://jasmine.github.io/tutorials/upgrading_to_4.0> for details.'
@@ -172,7 +172,7 @@ getJasmineRequireObj().MatchersUtil = function(j$) {
diffBuilder = customTestersOrDiffBuilder;
} else {
if (customTestersOrDiffBuilder) {
j$.getEnv().deprecatedOnceWithStack(
j$.getEnv().deprecated(
'Passing custom equality testers ' +
'to MatchersUtil#equals is deprecated. ' +
'See <https://jasmine.github.io/tutorials/upgrading_to_4.0> for details.'
@@ -180,7 +180,7 @@ getJasmineRequireObj().MatchersUtil = function(j$) {
}
if (diffBuilderOrNothing) {
j$.getEnv().deprecatedOnceWithStack(
j$.getEnv().deprecated(
'Diff builder should be passed ' +
'as the third argument to MatchersUtil#equals, not the fourth. ' +
'See <https://jasmine.github.io/tutorials/upgrading_to_4.0> for details.'

View File

@@ -42,6 +42,7 @@ var getJasmineRequireObj = (function(jasmineGlobal) {
j$.getClearStack = jRequire.clearStack(j$);
j$.Clock = jRequire.Clock();
j$.DelayedFunctionScheduler = jRequire.DelayedFunctionScheduler(j$);
j$.Deprecator = jRequire.Deprecator(j$);
j$.Env = jRequire.Env(j$);
j$.deprecatingThisProxy = jRequire.deprecatingThisProxy(j$);
j$.StackTrace = jRequire.StackTrace(j$);
@@ -58,7 +59,7 @@ var getJasmineRequireObj = (function(jasmineGlobal) {
j$.basicPrettyPrinter_ = j$.makePrettyPrinter();
Object.defineProperty(j$, 'pp', {
get: function() {
j$.getEnv().deprecatedOnceWithStack(
j$.getEnv().deprecated(
'jasmine.pp is deprecated and will be removed in a future release. ' +
'Use the pp method of the matchersUtil passed to the matcher factory ' +
"or the asymmetric equality tester's `asymmetricMatch` method " +
@@ -75,7 +76,7 @@ var getJasmineRequireObj = (function(jasmineGlobal) {
});
Object.defineProperty(j$, 'matchersUtil', {
get: function() {
j$.getEnv().deprecatedOnceWithStack(
j$.getEnv().deprecated(
'jasmine.matchersUtil is deprecated and will be removed ' +
'in a future release. Use the instance passed to the matcher factory or ' +
"the asymmetric equality tester's `asymmetricMatch` method instead. " +

View File

@@ -277,7 +277,8 @@ jasmineRequire.HtmlReporter = function(j$) {
addDeprecationWarnings(doneResult);
for (i = 0; i < deprecationWarnings.length; i++) {
var context;
var children = [],
context;
switch (deprecationWarnings[i].runnableType) {
case 'spec':
@@ -290,13 +291,23 @@ jasmineRequire.HtmlReporter = function(j$) {
context = '';
}
deprecationWarnings[i].message.split('\n').forEach(function(line) {
children.push(line);
children.push(createDom('br'));
});
children[0] = 'DEPRECATION: ' + children[0];
children.push(context);
if (deprecationWarnings[i].stack) {
children.push(createExpander(deprecationWarnings[i].stack));
}
alert.appendChild(
createDom(
'span',
{ className: 'jasmine-bar jasmine-warning' },
'DEPRECATION: ' + deprecationWarnings[i].message,
createDom('br'),
context
children
)
);
}
@@ -611,17 +622,44 @@ jasmineRequire.HtmlReporter = function(j$) {
if (result && result.deprecationWarnings) {
for (var i = 0; i < result.deprecationWarnings.length; i++) {
var warning = result.deprecationWarnings[i].message;
if (!j$.util.arrayContains(warning)) {
deprecationWarnings.push({
message: warning,
runnableName: result.fullName,
runnableType: runnableType
});
}
deprecationWarnings.push({
message: warning,
stack: result.deprecationWarnings[i].stack,
runnableName: result.fullName,
runnableType: runnableType
});
}
}
}
function createExpander(stackTrace) {
var expandLink = createDom('a', { href: '#' }, 'Show stack trace');
var root = createDom(
'div',
{ className: 'jasmine-expander' },
expandLink,
createDom(
'div',
{ className: 'jasmine-expander-contents jasmine-stack-trace' },
stackTrace
)
);
expandLink.addEventListener('click', function(e) {
e.preventDefault();
if (root.classList.contains('jasmine-expanded')) {
root.classList.remove('jasmine-expanded');
expandLink.textContent = 'Show stack trace';
} else {
root.classList.add('jasmine-expanded');
expandLink.textContent = 'Hide stack trace';
}
});
return root;
}
function find(selector) {
return getContainer().querySelector('.jasmine_html-reporter ' + selector);
}
@@ -635,11 +673,23 @@ jasmineRequire.HtmlReporter = function(j$) {
}
}
function createDom(type, attrs, childrenVarArgs) {
var el = createElement(type);
function createDom(type, attrs, childrenArrayOrVarArgs) {
var el = createElement(type),
children,
i;
for (var i = 2; i < arguments.length; i++) {
var child = arguments[i];
if (j$.isArray_(childrenArrayOrVarArgs)) {
children = childrenArrayOrVarArgs;
} else {
children = [];
for (i = 2; i < arguments.length; i++) {
children.push(arguments[i]);
}
}
for (i = 0; i < children.length; i++) {
var child = children[i];
if (typeof child === 'string') {
el.appendChild(createTextNode(child));

View File

@@ -231,6 +231,8 @@ body {
}
&.jasmine-warning {
margin-top: $margin-unit;
margin-bottom: $margin-unit;
background-color: $pending-color;
color: $text-color;
}
@@ -386,4 +388,27 @@ body {
background: white;
white-space: pre;
}
.jasmine-expander {
a {
display: block;
margin-left: $margin-unit;
color: blue;
text-decoration: underline;
}
}
.jasmine-expander-contents {
display: none;
}
.jasmine-expanded {
padding-bottom: 10px;
}
.jasmine-expanded .jasmine-expander-contents {
display: block;
margin-left: $margin-unit;
padding: 5px;
}
}