Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -13,5 +13,6 @@ ember-cli-build.cjs

# deps & caches
node_modules/
package-lock.json
.eslintcache
.prettiercache
60 changes: 58 additions & 2 deletions src/index.js
Original file line number Diff line number Diff line change
@@ -1,12 +1,22 @@
/* global QUnit */
import { registerDeprecationHandler } from '@ember/debug';
import { VERSION } from '@ember/version';

const LOG_LIMIT = 100;

// Number of minor versions before an upcoming `until` that counts as "approaching"
const APPROACHING_MINOR_WINDOW = 2;

// Minimum current minor version to be considered approaching the next major version.
// For example, if minor releases go 0–9, versions X.8 and X.9 are within the window.
const CROSS_MAJOR_MINOR_THRESHOLD = 10 - APPROACHING_MINOR_WINDOW;

export default function setupDeprecationWorkflow(config) {
self.deprecationWorkflow = self.deprecationWorkflow || {};
self.deprecationWorkflow.deprecationLog = {
messages: new Set(),
};
self.deprecationWorkflow.pressingSilenced = new Set();

registerDeprecationHandler((message, options, next) =>
handleDeprecationWorkflow(config, message, options, next),
Expand All @@ -16,6 +26,44 @@ export default function setupDeprecationWorkflow(config) {

self.deprecationWorkflow.flushDeprecations = (options) =>
flushDeprecations({ config, ...options });

if (typeof QUnit !== 'undefined') {
let pressingSilenced = self.deprecationWorkflow.pressingSilenced;
QUnit.done(() => {
let count = pressingSilenced.size;
if (count > 0) {
console.warn(`Deprecation Workflow: ${count} deprecation(s) silenced.`);
}
});
}
}

export function isApproaching(until, currentVersion = VERSION) {
const untilParts = String(until ?? '').split('.');
const untilMajor = parseInt(untilParts[0], 10);

if (isNaN(untilMajor)) return false;

const untilMinor = parseInt(untilParts[1] ?? '0', 10);

const currentParts = String(currentVersion).split('.');
const currentMajor = parseInt(currentParts[0], 10);
const currentMinor = parseInt(currentParts[1] ?? '0', 10);

if (untilMajor === currentMajor + 1) {
// Crossing to the next major: approaching if we're in the last few minor releases
return currentMinor >= CROSS_MAJOR_MINOR_THRESHOLD;
}

if (untilMajor === currentMajor) {
// Same major: approaching if within the minor window
return (
untilMinor - currentMinor <= APPROACHING_MINOR_WINDOW &&
untilMinor >= currentMinor
);
}

return false;
}

function matchesWorkflow(matcher, value) {
Expand Down Expand Up @@ -76,9 +124,17 @@ export function handleDeprecationWorkflow(config, message, options, next) {
}
} else {
switch (matchingWorkflow.handler) {
case 'silence':
// no-op
case 'silence': {
if (
!options ||
options.for !== 'ember-source' ||
!isApproaching(options.until)
)
break;
let key = options.id || message;
self.deprecationWorkflow.pressingSilenced.add(key);
break;
}
case 'log': {
let key = (options && options.id) || message;

Expand Down
161 changes: 161 additions & 0 deletions tests/unit/handle-deprecation-workflow-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -386,4 +386,165 @@ module('handleDeprecationWorkflow', function (hooks) {
);
}, 'deprecation throws');
});

test('pressing ember-source deprecation is tracked in pressingSilenced Set', function (assert) {
assert.expect(2);

self.deprecationWorkflow.pressingSilenced = new Set();

const config = {
workflow: [{ matchId: /^ember\..*/, handler: 'silence' }],
};

handleDeprecationWorkflow(
config,
'Some ember deprecation',
{
id: 'ember.some-feature',
since: '6.0.0',
until: '7.0',
for: 'ember-source',
},
() => {},
);

assert.strictEqual(
self.deprecationWorkflow.pressingSilenced.size,
1,
'pressing ember-source deprecation is added to the Set',
);
assert.ok(
self.deprecationWorkflow.pressingSilenced.has('ember.some-feature'),
'Set contains the deprecation id',
);
});

test('pressing ember-source deprecation is only counted once per unique id', function (assert) {
assert.expect(1);

self.deprecationWorkflow.pressingSilenced = new Set();

const config = {
workflow: [{ matchId: /^ember\..*/, handler: 'silence' }],
};

const options = {
id: 'ember.some-feature',
since: '6.0.0',
until: '7.0',
for: 'ember-source',
};

handleDeprecationWorkflow(
config,
'Some ember deprecation',
options,
() => {},
);
handleDeprecationWorkflow(
config,
'Some ember deprecation',
options,
() => {},
);
handleDeprecationWorkflow(
config,
'Some ember deprecation',
options,
() => {},
);

assert.strictEqual(
self.deprecationWorkflow.pressingSilenced.size,
1,
'Set contains only one entry after repeated firings of the same deprecation',
);
});

test('multiple distinct pressing ember-source deprecations are all tracked', function (assert) {
assert.expect(1);

self.deprecationWorkflow.pressingSilenced = new Set();

const config = {
workflow: [{ matchId: /^ember\..*/, handler: 'silence' }],
};

handleDeprecationWorkflow(
config,
'First ember deprecation',
{ id: 'ember.first', since: '6.0.0', until: '7.0', for: 'ember-source' },
() => {},
);

handleDeprecationWorkflow(
config,
'Second ember deprecation',
{ id: 'ember.second', since: '6.0.0', until: '7.0', for: 'ember-source' },
() => {},
);

assert.strictEqual(
self.deprecationWorkflow.pressingSilenced.size,
2,
'Set contains both deprecation ids',
);
});

test('deprecation silenced for ember-source with non-approaching until is not tracked', function (assert) {
assert.expect(1);

self.deprecationWorkflow.pressingSilenced = new Set();

const config = {
workflow: [{ matchId: 'ember.far-future', handler: 'silence' }],
};

// 8.0 is not approaching from 6.11.0 (two major versions ahead)
handleDeprecationWorkflow(
config,
'Far future ember deprecation',
{
id: 'ember.far-future',
since: '6.0.0',
until: '8.0',
for: 'ember-source',
},
() => {},
);

assert.strictEqual(
self.deprecationWorkflow.pressingSilenced.size,
0,
'non-approaching deprecation is not tracked',
);
});

test('deprecation silenced for non-ember-source is not tracked', function (assert) {
assert.expect(1);

self.deprecationWorkflow.pressingSilenced = new Set();

const config = {
workflow: [{ matchId: 'some-addon.feature', handler: 'silence' }],
};

handleDeprecationWorkflow(
config,
'Some addon deprecation',
{
id: 'some-addon.feature',
since: '1.0.0',
until: '7.0',
for: 'some-addon',
},
() => {},
);

assert.strictEqual(
self.deprecationWorkflow.pressingSilenced.size,
0,
'non-ember-source deprecation is not tracked',
);
});
});
86 changes: 86 additions & 0 deletions tests/unit/is-approaching-test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
import { module, test } from 'qunit';
import { isApproaching } from '#src/index.js';

module('isApproaching', function () {
test('returns true when until is the next major and current minor is >= 8', function (assert) {
assert.true(
isApproaching('8.0', '7.8.0'),
'7.8 approaches 8.0 (exactly 2 minor steps away)',
);
assert.true(
isApproaching('8.0', '7.9.0'),
'7.9 approaches 8.0 (1 minor step away)',
);
assert.true(
isApproaching('8.0.0', '7.8.0'),
'handles 3-part until version',
);
assert.true(
isApproaching('8.0', '7.10.0'),
'7.10 approaches 8.0 (minor >= 8)',
);
});

test('returns false when until is the next major but current minor is < 8', function (assert) {
assert.false(
isApproaching('8.0', '7.7.0'),
'7.7 does not approach 8.0 (more than 2 minor steps away)',
);
assert.false(isApproaching('8.0', '7.0.0'), '7.0 does not approach 8.0');
});

test('returns true when until is within 2 minor versions in the same major', function (assert) {
assert.true(
isApproaching('7.3', '7.1.0'),
'7.1 approaches 7.3 (2 minors away)',
);
assert.true(
isApproaching('7.2', '7.1.0'),
'7.1 approaches 7.2 (1 minor away)',
);
assert.true(
isApproaching('7.1', '7.1.0'),
'7.1 approaches 7.1 (same version - overdue)',
);
});

test('returns false when until is more than 2 minor versions ahead in same major', function (assert) {
assert.false(
isApproaching('7.4', '7.1.0'),
'7.1 does not approach 7.4 (3 minors away)',
);
assert.false(isApproaching('7.10', '7.1.0'), '7.1 does not approach 7.10');
});

test('returns false when until is 2+ majors ahead', function (assert) {
assert.false(
isApproaching('9.0', '7.9.0'),
'7.9 does not approach 9.0 (2 majors away)',
);
assert.false(isApproaching('10.0', '7.9.0'), '7.9 does not approach 10.0');
});

test('returns false when until is not a valid version', function (assert) {
assert.false(
isApproaching('forever', '7.9.0'),
'forever is not approaching',
);
assert.false(isApproaching(null, '7.9.0'), 'null is not approaching');
assert.false(
isApproaching(undefined, '7.9.0'),
'undefined is not approaching',
);
assert.false(isApproaching('', '7.9.0'), 'empty string is not approaching');
});

test('returns false when until is in the past (older major)', function (assert) {
assert.false(
isApproaching('6.0', '7.9.0'),
'past version is not approaching',
);
assert.false(
isApproaching('5.0', '7.9.0'),
'much older version is not approaching',
);
});
});
Loading