@atlassian/aui
Version:
Atlassian User Interface Framework
341 lines (292 loc) • 13.6 kB
JavaScript
import $ from '../jquery';
import globalize from './globalize';
var has = Object.prototype.hasOwnProperty;
var deprecationCalls = [];
var deprecatedSelectorMap = [];
function toSentenceCase (str) {
str += '';
if (!str) {
return '';
}
return str.charAt(0).toUpperCase() + str.substring(1);
}
function getDeprecatedLocation (printFrameOffset) {
var err = new Error();
var stack = err.stack || err.stacktrace;
var stackMessage = (stack && stack.replace(/^Error\n/, '')) || '';
if (stackMessage) {
stackMessage = stackMessage.split('\n');
return stackMessage[printFrameOffset + 2];
}
return stackMessage;
}
function logger () {
if (typeof console !== 'undefined' && console.warn) {
Function.prototype.apply.call(console.warn, console, arguments);
}
}
/**
* Return a function that logs a deprecation warning to the console the first time it is called from a certain location.
* It will also print the stack frame of the calling function.
*
* @param {string} displayName the name of the thing being deprecated
* @param {object} options
* @param {string} options.removeInVersion the version this will be removed in
* @param {string} options.alternativeName the name of an alternative to use
* @param {string} options.sinceVersion the version this has been deprecated since
* @param {string} options.extraInfo extra information to be printed at the end of the deprecation log
* @param {string} options.extraObject an extra object that will be printed at the end
* @param {string} options.deprecationType type of the deprecation to append to the start of the deprecation message. e.g. JS or CSS
* @return {Function} that logs the warning and stack frame of the calling function. Takes in an optional parameter for the offset of
* the stack frame to print, the default is 0. For example, 0 will log it for the line of the calling function,
* -1 will print the location the logger was called from
*/
function getShowDeprecationMessage (displayName, options) {
// This can be used internally to pas in a showmessage fn
if (typeof displayName === 'function') {
return displayName;
}
var called = false;
options = options || {};
return function (printFrameOffset) {
var deprecatedLocation = getDeprecatedLocation(printFrameOffset ? printFrameOffset : 1) || '';
// Only log once if the stack frame doesn't exist to avoid spamming the console/test output
if (!called || deprecationCalls.indexOf(deprecatedLocation) === -1) {
deprecationCalls.push(deprecatedLocation);
called = true;
var deprecationType = (options.deprecationType + ' ') || '';
var message = 'DEPRECATED ' + deprecationType + '- ' + toSentenceCase(displayName) +
' has been deprecated' + (options.sinceVersion ? ' since ' + options.sinceVersion : '') +
' and will be removed in ' + (options.removeInVersion || 'a future release') + '.';
if (options.alternativeName) {
message += ' Use ' + options.alternativeName + ' instead. ';
}
if (options.extraInfo) {
message += ' ' + options.extraInfo;
}
if (deprecatedLocation === '') {
deprecatedLocation = ' \n ' + 'No stack trace of the deprecated usage is available in your current browser.';
} else {
deprecatedLocation = ' \n ' + deprecatedLocation;
}
if (options.extraObject) {
message += '\n';
logger(message, options.extraObject, deprecatedLocation);
} else {
logger(message, deprecatedLocation);
}
}
};
}
function logCssDeprecation (selectorMap, newNode) {
var displayName = selectorMap.options.displayName;
displayName = displayName ? ' (' + displayName + ')' : '';
var options = $.extend({
deprecationType: 'CSS',
extraObject: newNode
}, selectorMap.options);
getShowDeprecationMessage('\'' + selectorMap.selector + '\' pattern' + displayName, options)();
}
/**
* Returns a wrapped version of the function that logs a deprecation warning when the function is used.
* @param {Function} fn the fn to wrap
* @param {string} displayName the name of the fn to be displayed in the message
* @param {string} options.removeInVersion the version this will be removed in
* @param {string} options.alternativeName the name of an alternative to use
* @param {string} options.sinceVersion the version this has been deprecated since
* @param {string} options.extraInfo extra information to be printed at the end of the deprecation log
* @return {Function} wrapping the original function
*/
function deprecateFunctionExpression(fn, displayName, options) {
options = options || {};
options.deprecationType = options.deprecationType || 'JS';
var showDeprecationMessage = getShowDeprecationMessage(displayName || fn.name || 'this function', options);
return function () {
showDeprecationMessage();
return fn.apply(this, arguments);
};
}
/**
* Returns a wrapped version of the constructor that logs a deprecation warning when the constructor is instantiated.
* @param {Function} constructorFn the constructor function to wrap
* @param {string} displayName the name of the fn to be displayed in the message
* @param {string} options.removeInVersion the version this will be removed in
* @param {string} options.alternativeName the name of an alternative to use
* @param {string} options.sinceVersion the version this has been deprecated since
* @param {string} options.extraInfo extra information to be printed at the end of the deprecation log
* @return {Function} wrapping the original function
*/
function deprecateConstructor(constructorFn, displayName, options) {
options = options || {};
options.deprecationType = options.deprecationType || 'JS';
var deprecatedConstructor = deprecateFunctionExpression(constructorFn, displayName, options);
deprecatedConstructor.prototype = constructorFn.prototype;
$.extend(deprecatedConstructor, constructorFn); //copy static methods across;
return deprecatedConstructor;
}
var supportsProperties = false;
try {
if (Object.defineProperty) {
Object.defineProperty({}, 'blam', {get: function () {}, set: function () {}});
supportsProperties = true;
}
} catch (e) {
/* IE8 doesn't support on non-DOM elements */
}
/**
* Wraps a "value" object property in a deprecation warning in browsers supporting Object.defineProperty
* @param {Object} obj the object containing the property
* @param {string} prop the name of the property to deprecate
* @param {string} options.removeInVersion the version this will be removed in
* @param {string} options.displayName the display name of the property to deprecate (optional, will fall back to the property name)
* @param {string} options.alternativeName the name of an alternative to use
* @param {string} options.sinceVersion the version this has been deprecated since
* @param {string} options.extraInfo extra information to be printed at the end of the deprecation log
*/
function deprecateValueProperty(obj, prop, options) {
if (supportsProperties) {
var oldVal = obj[prop];
options = options || {};
options.deprecationType = options.deprecationType || 'JS';
var displayNameOrShowMessageFn = options.displayName || prop;
var showDeprecationMessage = getShowDeprecationMessage(displayNameOrShowMessageFn, options);
Object.defineProperty(obj, prop, {
get: function () {
showDeprecationMessage();
return oldVal;
},
set: function (val) {
oldVal = val;
showDeprecationMessage();
return val;
}
});
}
}
/**
* Wraps an object property in a deprecation warning, if possible. functions will always log warnings, but other
* types of properties will only log in browsers supporting Object.defineProperty
* @param {Object} obj the object containing the property
* @param {string} prop the name of the property to deprecate
* @param {string} options.removeInVersion the version this will be removed in
* @param {string} options.displayName the display name of the property to deprecate (optional, will fall back to the property name)
* @param {string} options.alternativeName the name of an alternative to use
* @param {string} options.sinceVersion the version this has been deprecated since
* @param {string} options.extraInfo extra information to be printed at the end of the deprecation log
*/
function deprecateObjectProperty(obj, prop, options) {
if (typeof obj[prop] === 'function') {
options = options || {};
options.deprecationType = options.deprecationType || 'JS';
var displayNameOrShowMessageFn = options.displayName || prop;
obj[prop] = deprecateFunctionExpression(obj[prop], displayNameOrShowMessageFn, options);
} else {
deprecateValueProperty(obj, prop, options);
}
}
/**
* Wraps all an objects properties in a deprecation warning, if possible. functions will always log warnings, but other
* types of properties will only log in browsers supporting Object.defineProperty
* @param {Object} obj the object to be wrapped
* @param {string} objDisplayPrefix the object's prefix to be used in logs
* @param {string} options.removeInVersion the version this will be removed in
* @param {string} options.alternativeNamePrefix the name of another object to prefix the deprecated objects properties with
* @param {string} options.sinceVersion the version this has been deprecated since
* @param {string} options.extraInfo extra information to be printed at the end of the deprecation log
*/
function deprecateAllProperties(obj, objDisplayPrefix, options) {
options = options || {};
for (var attr in obj) {
if (has.call(obj, attr)) {
options.deprecationType = options.deprecationType || 'JS';
options.displayName = objDisplayPrefix + attr;
options.alternativeName = options.alternativeNamePrefix && (options.alternativeNamePrefix + attr);
deprecateObjectProperty(obj, attr, $.extend({}, options));
}
}
}
function matchesSelector(el, selector) {
return (el.matches || el.msMatchesSelector || el.webkitMatchesSelector || el.mozMatchesSelector || el.oMatchesSelector).call(el, selector);
}
function handleAddingSelector(options) {
return function (selector) {
var selectorMap = {
selector: selector,
options: options || {}
};
deprecatedSelectorMap.push(selectorMap);
// Search if matches have already been added
var matches = document.querySelectorAll(selector);
for (var i = 0; i < matches.length; i++) {
logCssDeprecation(selectorMap, matches[i]);
}
};
}
/**
* Return a function that logs a deprecation warning to the console the first time it is called from a certain location.
* It will also print the stack frame of the calling function.
*
* @param {string|Array} selectors a selector or list of selectors that match deprecated markup
* @param {object} options
* @param {string} options.displayName a name describing these selectors
* @param {string} options.alternativeName the name of an alternative to use
* @param {string} options.removeInVersion the version these will be removed in
* @param {string} options.sinceVersion the version these have been deprecated since
* @param {string} options.extraInfo extra information to be printed at the end of the deprecation log
*/
function deprecateCSS(selectors, options) {
if (!window.MutationObserver) {
logger('CSS could not be deprecated as Mutation Observer was not found.');
return;
}
if (typeof selectors === 'string') {
selectors = [selectors];
}
selectors.forEach(handleAddingSelector(options));
}
function testAndHandleDeprecation(newNode) {
return function (selectorMap) {
if (matchesSelector(newNode, selectorMap.selector)) {
logCssDeprecation(selectorMap, newNode);
}
};
}
if (window.MutationObserver) {
var observer = new MutationObserver(function (mutations) {
mutations.forEach(function (mutation) {
// TODO - should this also look at class changes, if possible?
var addedNodes = mutation.addedNodes;
for (var i = 0; i < addedNodes.length; i++) {
var newNode = addedNodes[i];
if (newNode.nodeType === 1) {
deprecatedSelectorMap.forEach(testAndHandleDeprecation(newNode));
}
}
});
});
var config = {
childList: true,
subtree: true
};
observer.observe(document, config);
}
var deprecate = {
fn: deprecateFunctionExpression,
construct: deprecateConstructor,
css: deprecateCSS,
prop: deprecateObjectProperty,
obj: deprecateAllProperties,
propertyDeprecationSupported: supportsProperties,
getMessageLogger: getShowDeprecationMessage
};
globalize('deprecate', deprecate);
export {
deprecateFunctionExpression as fn,
deprecateConstructor as construct,
deprecateCSS as css,
deprecateObjectProperty as prop,
deprecateAllProperties as obj,
supportsProperties as propertyDeprecationSupported,
getShowDeprecationMessage as getMessageLogger
};
;