Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[DEPRECATION] Add deprecation for Function.prototype extensions. #17910

Merged
Merged
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
9 changes: 7 additions & 2 deletions packages/@ember/-internals/environment/lib/env.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { FUNCTION_PROTOTYPE_EXTENSIONS } from '@ember/deprecated-features';
import global from './global';

/**
Expand Down Expand Up @@ -155,12 +156,16 @@ export const ENV = {
if (EXTEND_PROTOTYPES !== undefined) {
if (typeof EXTEND_PROTOTYPES === 'object' && EXTEND_PROTOTYPES !== null) {
ENV.EXTEND_PROTOTYPES.String = EXTEND_PROTOTYPES.String !== false;
ENV.EXTEND_PROTOTYPES.Function = EXTEND_PROTOTYPES.Function !== false;
if (FUNCTION_PROTOTYPE_EXTENSIONS) {
ENV.EXTEND_PROTOTYPES.Function = EXTEND_PROTOTYPES.Function !== false;
}
ENV.EXTEND_PROTOTYPES.Array = EXTEND_PROTOTYPES.Array !== false;
} else {
let isEnabled = EXTEND_PROTOTYPES !== false;
ENV.EXTEND_PROTOTYPES.String = isEnabled;
ENV.EXTEND_PROTOTYPES.Function = isEnabled;
if (FUNCTION_PROTOTYPE_EXTENSIONS) {
ENV.EXTEND_PROTOTYPES.Function = isEnabled;
}
ENV.EXTEND_PROTOTYPES.Array = isEnabled;
}
}
Expand Down
33 changes: 19 additions & 14 deletions packages/@ember/-internals/metal/tests/observer_test.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import {
set,
} from '..';
import { moduleFor, AbstractTestCase } from 'internal-test-helpers';
import { FUNCTION_PROTOTYPE_EXTENSIONS } from '@ember/deprecated-features';

function K() {}

Expand Down Expand Up @@ -103,18 +104,20 @@ moduleFor(
assert.equal(observerCount, 10, 'should continue to fire indefinitely');
}

['@test observer added declaratively via brace expansion should fire when property changes'](
['@test observer added via Function.prototype extensions and brace expansion should fire when property changes'](
assert
) {
if (ENV.EXTEND_PROTOTYPES.Function) {
if (!FUNCTION_PROTOTYPE_EXTENSIONS && ENV.EXTEND_PROTOTYPES.Function) {
let obj = {};
let count = 0;

mixin(obj, {
observeFooAndBar: function() {
count++;
}.observes('{foo,bar}'),
});
expectDeprecation(() => {
mixin(obj, {
observeFooAndBar: function() {
count++;
}.observes('{foo,bar}'),
});
}, /Function prototype extensions have been deprecated, please migrate from function\(\){}.observes\('foo'\) to observer\('foo', function\(\) {}\)/);

set(obj, 'foo', 'foo');
assert.equal(count, 1, 'observer specified via brace expansion invoked on property change');
Expand All @@ -129,10 +132,10 @@ moduleFor(
}
}

['@test observer specified declaratively via brace expansion should fire when dependent property changes'](
['@test observer specified via Function.prototype extensions via brace expansion should fire when dependent property changes'](
assert
) {
if (ENV.EXTEND_PROTOTYPES.Function) {
if (!FUNCTION_PROTOTYPE_EXTENSIONS && ENV.EXTEND_PROTOTYPES.Function) {
let obj = { baz: 'Initial' };
let count = 0;

Expand All @@ -152,11 +155,13 @@ moduleFor(
})
);

mixin(obj, {
fooAndBarWatcher: function() {
count++;
}.observes('{foo,bar}'),
});
expectDeprecation(() => {
mixin(obj, {
fooAndBarWatcher: function() {
count++;
}.observes('{foo,bar}'),
});
}, /Function prototype extensions have been deprecated, please migrate from function\(\){}.observes\('foo'\) to observer\('foo', function\(\) {}\)/);

get(obj, 'foo');
set(obj, 'baz', 'Baz');
Expand Down
33 changes: 32 additions & 1 deletion packages/@ember/-internals/runtime/lib/ext/function.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,10 @@

import { ENV } from '@ember/-internals/environment';
import { on, computed, observer } from '@ember/-internals/metal';
import { deprecate } from '@ember/debug';
import { FUNCTION_PROTOTYPE_EXTENSIONS } from '@ember/deprecated-features';

if (ENV.EXTEND_PROTOTYPES.Function) {
if (FUNCTION_PROTOTYPE_EXTENSIONS && ENV.EXTEND_PROTOTYPES.Function) {
Object.defineProperties(Function.prototype, {
/**
The `property` extension of Javascript's Function prototype is available
Expand Down Expand Up @@ -77,6 +79,15 @@ if (ENV.EXTEND_PROTOTYPES.Function) {
enumerable: false,
writable: true,
value: function() {
deprecate(
`Function prototype extensions have been deprecated, please migrate from function(){}.property('bar') to computed('bar', function() {}).`,
false,
{
id: 'function-prototype-extensions.property',
until: '4.0.0',
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we also need since: '3.10.0' (or '3.11.0') here?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No, deprecate's options do not include a since field. The intent is for the @ember/deprecated-features exports to do that, and ultimately be used to enable svelte infrastructure.

url: 'https://deprecations.emberjs.com/v3.x#toc_function-prototype-extensions-property',
}
);
return computed(...arguments, this);
},
},
Expand Down Expand Up @@ -113,6 +124,16 @@ if (ENV.EXTEND_PROTOTYPES.Function) {
enumerable: false,
writable: true,
value: function() {
deprecate(
`Function prototype extensions have been deprecated, please migrate from function(){}.observes('foo') to observer('foo', function() {}).`,
false,
{
id: 'function-prototype-extensions.observes',
until: '4.0.0',
url: 'https://deprecations.emberjs.com/v3.x#toc_function-prototype-extensions-observes',
}
);

return observer(...arguments, this);
},
},
Expand Down Expand Up @@ -147,6 +168,16 @@ if (ENV.EXTEND_PROTOTYPES.Function) {
enumerable: false,
writable: true,
value: function() {
deprecate(
`Function prototype extensions have been deprecated, please migrate from function(){}.on('foo') to on('foo', function() {}).`,
false,
{
id: 'function-prototype-extensions.on',
until: '4.0.0',
url: 'https://deprecations.emberjs.com/v3.x#toc_function-prototype-extensions-on',
}
);

return on(...arguments, this);
},
},
Expand Down
75 changes: 44 additions & 31 deletions packages/@ember/-internals/runtime/tests/ext/function_test.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,26 +3,30 @@ import { Mixin, mixin, get, set } from '@ember/-internals/metal';
import EmberObject from '../../lib/system/object';
import Evented from '../../lib/mixins/evented';
import { moduleFor, AbstractTestCase } from 'internal-test-helpers';
import { FUNCTION_PROTOTYPE_EXTENSIONS } from '@ember/deprecated-features';

moduleFor(
'Function.prototype.observes() helper',
class extends AbstractTestCase {
['@test global observer helper takes multiple params'](assert) {
if (!ENV.EXTEND_PROTOTYPES.Function) {
if (!FUNCTION_PROTOTYPE_EXTENSIONS || !ENV.EXTEND_PROTOTYPES.Function) {
assert.ok(
'undefined' === typeof Function.prototype.observes,
'Function.prototype helper disabled'
);
return;
}

let MyMixin = Mixin.create({
count: 0,
let MyMixin;
expectDeprecation(() => {
MyMixin = Mixin.create({
count: 0,

foo: function() {
set(this, 'count', get(this, 'count') + 1);
}.observes('bar', 'baz'),
});
foo: function() {
set(this, 'count', get(this, 'count') + 1);
}.observes('bar', 'baz'),
});
}, /Function prototype extensions have been deprecated, please migrate from function\(\){}.observes\('foo'\) to observer\('foo', function\(\) {}\)/);

let obj = mixin({}, MyMixin);
assert.equal(get(obj, 'count'), 0, 'should not invoke observer immediately');
Expand All @@ -38,21 +42,24 @@ moduleFor(
'Function.prototype.on() helper',
class extends AbstractTestCase {
['@test sets up an event listener, and can trigger the function on multiple events'](assert) {
if (!ENV.EXTEND_PROTOTYPES.Function) {
if (!FUNCTION_PROTOTYPE_EXTENSIONS || !ENV.EXTEND_PROTOTYPES.Function) {
assert.ok(
'undefined' === typeof Function.prototype.on,
'Function.prototype helper disabled'
);
return;
}

let MyMixin = Mixin.create({
count: 0,
let MyMixin;
expectDeprecation(() => {
MyMixin = Mixin.create({
count: 0,

foo: function() {
set(this, 'count', get(this, 'count') + 1);
}.on('bar', 'baz'),
});
foo: function() {
set(this, 'count', get(this, 'count') + 1);
}.on('bar', 'baz'),
});
}, /Function prototype extensions have been deprecated, please migrate from function\(\){}.on\('foo'\) to on\('foo', function\(\) {}\)/);

let obj = mixin({}, Evented, MyMixin);
assert.equal(get(obj, 'count'), 0, 'should not invoke listener immediately');
Expand All @@ -63,19 +70,22 @@ moduleFor(
}

['@test can be chained with observes'](assert) {
if (!ENV.EXTEND_PROTOTYPES.Function) {
if (!FUNCTION_PROTOTYPE_EXTENSIONS || !ENV.EXTEND_PROTOTYPES.Function) {
assert.ok('Function.prototype helper disabled');
return;
}

let MyMixin = Mixin.create({
count: 0,
bay: 'bay',
foo: function() {
set(this, 'count', get(this, 'count') + 1);
}
.observes('bay')
.on('bar'),
let MyMixin;
expectDeprecation(function() {
MyMixin = Mixin.create({
count: 0,
bay: 'bay',
foo: function() {
set(this, 'count', get(this, 'count') + 1);
}
.observes('bay')
.on('bar'),
});
});

let obj = mixin({}, Evented, MyMixin);
Expand All @@ -92,21 +102,24 @@ moduleFor(
'Function.prototype.property() helper',
class extends AbstractTestCase {
['@test sets up a ComputedProperty'](assert) {
if (!ENV.EXTEND_PROTOTYPES.Function) {
if (!FUNCTION_PROTOTYPE_EXTENSIONS || !ENV.EXTEND_PROTOTYPES.Function) {
assert.ok(
'undefined' === typeof Function.prototype.property,
'Function.prototype helper disabled'
);
return;
}

let MyClass = EmberObject.extend({
firstName: null,
lastName: null,
fullName: function() {
return get(this, 'firstName') + ' ' + get(this, 'lastName');
}.property('firstName', 'lastName'),
});
let MyClass;
expectDeprecation(function() {
MyClass = EmberObject.extend({
firstName: null,
lastName: null,
fullName: function() {
return get(this, 'firstName') + ' ' + get(this, 'lastName');
}.property('firstName', 'lastName'),
});
}, /Function prototype extensions have been deprecated, please migrate from function\(\){}.property\('bar'\) to computed\('bar', function\(\) {}\)/);

let obj = MyClass.create({ firstName: 'Fred', lastName: 'Flinstone' });
assert.equal(get(obj, 'fullName'), 'Fred Flinstone', 'should return the computed value');
Expand Down
1 change: 1 addition & 0 deletions packages/@ember/deprecated-features/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,3 +13,4 @@ export const COMPONENT_MANAGER_STRING_LOOKUP = !!'3.8.0';
export const JQUERY_INTEGRATION = !!'3.9.0';
export const ALIAS_METHOD = !!'3.9.0';
export const APP_CTRL_ROUTER_PROPS = !!'3.10.0-beta.1';
export const FUNCTION_PROTOTYPE_EXTENSIONS = !!'3.11.0-beta.1';