Skip to content

Commit

Permalink
Add a deprecate util
Browse files Browse the repository at this point in the history
  • Loading branch information
bertdeblock committed Dec 15, 2021
1 parent a94710e commit f2ce8fe
Show file tree
Hide file tree
Showing 3 changed files with 286 additions and 0 deletions.
110 changes: 110 additions & 0 deletions lib/debug/deprecate.js
@@ -0,0 +1,110 @@
'use strict';

const chalk = require('chalk');
const semver = require('semver');
const assert = require('./assert');

/**
* Display a deprecation message.
*
* ```js
* const { deprecate } = require('ember-cli/lib/debug');
*
* deprecate('The `foo` method is deprecated.', false, {
* for: 'ember-cli',
* id: 'ember-cli.foo-method',
* since: {
* available: '4.1.0',
* enabled: '4.2.0',
* },
* until: '5.0.0',
* url: 'https://example.com',
* });
* ```
*
* @param {String} description Describes the deprecation.
* @param {Any} condition If falsy, the deprecation message will be displayed.
* @param {Object} options An object including the deprecation's details:
* - `for` The library that the deprecation is for
* - `id` The deprecation's unique id
* - `since.available` A SemVer version indicating when the deprecation was made available
* - `since.enabled` A SemVer version indicating when the deprecation was enabled
* - `until` A SemVer version indicating until when the deprecation will be active
* - `url` A URL that refers to additional information about the deprecation
*/
function deprecate(description, condition, options) {
assert('When calling `deprecate`, you must provide a description as the first argument.', description);
assert('When calling `deprecate`, you must provide a condition as the second argument.', arguments.length > 1);

assert(
'When calling `deprecate`, you must provide an options object as the third argument. The options object must include the `for`, `id`, `since` and `until` options (`url` is optional).',
options
);

assert('When calling `deprecate`, you must provide the `for` option.', options.for);
assert('When calling `deprecate`, you must provide the `id` option.', options.id);

assert(
'When calling `deprecate`, you must provide the `since` option. `since` must include the `available` and/or the `enabled` option.',
options.since
);

assert(
'When calling `deprecate`, you must provide the `since.available` and/or the `since.enabled` option.',
options.since.available || options.since.enabled
);

assert(
'`since.available` must be a valid SemVer version.',
!options.since.available || isSemVer(options.since.available)
);

assert('`since.enabled` must be a valid SemVer version.', !options.since.enabled || isSemVer(options.since.enabled));

assert(
'When calling `deprecate`, you must provide a valid SemVer version for the `until` option.',
isSemVer(options.until)
);

if (condition) {
return;
}

let message = formatMessage(description, options);

warn(message);
warn(getStackTrace());

// Return the message for testing purposes.
// This can be removed once we can register deprecation handlers.
return message;
}

function isSemVer(version) {
return semver.valid(version) !== null;
}

function formatMessage(description, options) {
let message = [`DEPRECATION: ${description}`, `[ID: ${options.id}]`];

if (options.url) {
message.push(`See ${options.url} for more details.`);
}

return message.join(' ');
}

function getStackTrace() {
let error = new Error();
let lines = error.stack.split('\n');

lines.shift(); // Remove the word `Error`.

return lines.map((line) => line.trim()).join('\n');
}

function warn(message) {
console.warn(chalk.yellow(message));
}

module.exports = deprecate;
1 change: 1 addition & 0 deletions lib/debug/index.js
Expand Up @@ -2,4 +2,5 @@

module.exports = {
assert: require('./assert'),
deprecate: require('./deprecate'),
};
175 changes: 175 additions & 0 deletions tests/unit/debug/deprecate-test.js
@@ -0,0 +1,175 @@
'use strict';

const { expect } = require('chai');
const { deprecate } = require('../../../lib/debug');

describe('deprecate', function () {
it('it throws when the description argument is missing', function () {
expect(() => {
deprecate();
}).to.throw('ASSERTION FAILED: When calling `deprecate`, you must provide a description as the first argument.');

expect(() => {
deprecate('');
}).to.throw('ASSERTION FAILED: When calling `deprecate`, you must provide a description as the first argument.');
});

it('it throws when the condition argument is missing', function () {
expect(() => {
deprecate('description');
}).to.throw('ASSERTION FAILED: When calling `deprecate`, you must provide a condition as the second argument.');
});

it('it throws when the options argument is missing', function () {
expect(() => {
deprecate('description', true);
}).to.throw(
'ASSERTION FAILED: When calling `deprecate`, you must provide an options object as the third argument. The options object must include the `for`, `id`, `since` and `until` options (`url` is optional).'
);

expect(() => {
deprecate('description', undefined);
}).to.throw(
'ASSERTION FAILED: When calling `deprecate`, you must provide an options object as the third argument. The options object must include the `for`, `id`, `since` and `until` options (`url` is optional).'
);

expect(() => {
deprecate('description', false, null);
}).to.throw(
'ASSERTION FAILED: When calling `deprecate`, you must provide an options object as the third argument. The options object must include the `for`, `id`, `since` and `until` options (`url` is optional).'
);
});

it('it throws when the `for` option is missing', function () {
expect(() => {
deprecate('description', true, {});
}).to.throw('ASSERTION FAILED: When calling `deprecate`, you must provide the `for` option.');
});

it('it throws when the `id` option is missing', function () {
expect(() => {
deprecate('description', true, {
for: 'foo',
});
}).to.throw('ASSERTION FAILED: When calling `deprecate`, you must provide the `id` option.');
});

it('it throws when the `since` option is missing', function () {
expect(() => {
deprecate('description', true, {
for: 'foo',
id: 'foo',
});
}).to.throw(
'ASSERTION FAILED: When calling `deprecate`, you must provide the `since` option. `since` must include the `available` and/or the `enabled` option.'
);
});

it('it throws when both the `since.available` and `since.enabled` options are missing', function () {
expect(() => {
deprecate('description', true, {
for: 'foo',
id: 'foo',
since: {},
});
}).to.throw(
'ASSERTION FAILED: When calling `deprecate`, you must provide the `since.available` and/or the `since.enabled` option.'
);
});

it('it throws when the `since.available` option is not a valid SemVer version', function () {
expect(() => {
deprecate('description', true, {
for: 'foo',
id: 'foo',
since: {
available: 'foo',
},
});
}).to.throw('ASSERTION FAILED: `since.available` must be a valid SemVer version.');
});

it('it throws when the `since.enabled` option is not a valid SemVer version', function () {
expect(() => {
deprecate('description', true, {
for: 'foo',
id: 'foo',
since: {
enabled: 'foo',
},
});
}).to.throw('ASSERTION FAILED: `since.enabled` must be a valid SemVer version.');
});

it('it throws when the `until` option is not a valid SemVer version', function () {
expect(() => {
deprecate('description', true, {
for: 'foo',
id: 'foo',
since: {
available: '4.0.0',
enabled: '4.0.0',
},
});
}).to.throw(
'ASSERTION FAILED: When calling `deprecate`, you must provide a valid SemVer version for the `until` option.'
);

expect(() => {
deprecate('description', true, {
for: 'foo',
id: 'foo',
since: {
available: '4.0.0',
enabled: '4.0.0',
},
until: 'foo',
});
}).to.throw(
'ASSERTION FAILED: When calling `deprecate`, you must provide a valid SemVer version for the `until` option.'
);
});

it('it does nothing when the condition argument is truthy', function () {
let message = deprecate('description', true, {
for: 'foo',
id: 'foo',
since: {
available: '4.0.0',
enabled: '4.0.0',
},
until: '5.0.0',
});

expect(message).to.be.undefined;
});

it('it displays a deprecation message when the condition argument is falsy', function () {
let message = deprecate('description', false, {
for: 'foo',
id: 'foo',
since: {
available: '4.0.0',
enabled: '4.0.0',
},
until: '5.0.0',
});

expect(message).to.be.equal('DEPRECATION: description [ID: foo]');
});

it('it includes the `url` option in the deprecation message when provided', function () {
let message = deprecate('description', false, {
for: 'foo',
id: 'foo',
since: {
available: '4.0.0',
enabled: '4.0.0',
},
until: '5.0.0',
url: 'https://example.com',
});

expect(message).to.be.equal('DEPRECATION: description [ID: foo] See https://example.com for more details.');
});
});

0 comments on commit f2ce8fe

Please sign in to comment.