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

Cleanup EXTEND_PROTOTYPES #20730

Merged
merged 4 commits into from
Aug 22, 2024
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
8 changes: 0 additions & 8 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -98,13 +98,6 @@ jobs:
- name: "Production build, with optional features"
BUILD: "production"
ENABLE_OPTIONAL_FEATURES: "true"
- name: "Extend prototypes"
EXTEND_PROTOTYPES: "true"
RAISE_ON_DEPRECATION: "false"
- name: "Extend prototypes, with optional features"
EXTEND_PROTOTYPES: "true"
ENABLE_OPTIONAL_FEATURES: "true"
RAISE_ON_DEPRECATION: "false"

steps:
- uses: actions/checkout@v4
Expand All @@ -115,7 +108,6 @@ jobs:
env:
ALL_DEPRECATIONS_ENABLED: ${{ matrix.ALL_DEPRECATIONS_ENABLED }}
OVERRIDE_DEPRECATION_VERSION: ${{ matrix.OVERRIDE_DEPRECATION_VERSION }}
EXTEND_PROTOTYPES: ${{ matrix.EXTEND_PROTOTYPES }}
ENABLE_OPTIONAL_FEATURES: ${{ matrix.ENABLE_OPTIONAL_FEATURES }}
RAISE_ON_DEPRECATION: ${{ matrix.RAISE_ON_DEPRECATION }}

Expand Down
4 changes: 0 additions & 4 deletions bin/run-tests.js
Original file line number Diff line number Diff line change
Expand Up @@ -18,10 +18,6 @@ const variants = [
// hit its "until" version, the tests for it will behave correctly.
'OVERRIDE_DEPRECATION_VERSION',

// This enables the legacy Ember feature that causes Ember to extend built-in
// platform features like Array.
'EXTEND_PROTOTYPES',

// This enables all canary feature flags for unreleased feature within Ember
// itself.
'ENABLE_OPTIONAL_FEATURES',
Expand Down
3 changes: 0 additions & 3 deletions index.html
Original file line number Diff line number Diff line change
Expand Up @@ -24,9 +24,6 @@
EmberENV.__test_hook_count__ += object;
});

// Handle extending prototypes
EmberENV['EXTEND_PROTOTYPES'] = !!QUnit.urlParams.EXTEND_PROTOTYPES;

// Handle testing feature flags
if (QUnit.urlParams.ENABLE_OPTIONAL_FEATURES) {
EmberENV.ENABLE_OPTIONAL_FEATURES = true;
Expand Down
16 changes: 7 additions & 9 deletions packages/@ember/-internals/environment/lib/env.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,22 +20,17 @@ export const ENV = {
native object prototypes, a few extra methods in order to provide a more
friendly API.

We generally recommend leaving this option set to true however, if you need
to turn it off, you can add the configuration property
`EXTEND_PROTOTYPES` to `EmberENV` and set it to `false`.

Note, when disabled (the default configuration for Ember Addons), you will
instead have to access all methods and functions from the Ember
namespace.
The behavior from setting this option to `true` was deprecated in Ember 5.10.

@property EXTEND_PROTOTYPES
@type Boolean
@default true
@for EmberENV
@public
@private
@deprecated in v5.10
*/
EXTEND_PROTOTYPES: {
Array: true,
Array: false,
},

/**
Expand Down Expand Up @@ -201,6 +196,9 @@ export const ENV = {
}
}

// TODO: Remove in Ember 6.5. This setting code for EXTEND_PROTOTYPES
// should stay for at least an LTS cycle so that users get the explicit
// deprecation exception when it breaks in >= 6.0.0.
let { EXTEND_PROTOTYPES } = EmberENV;
if (EXTEND_PROTOTYPES !== undefined) {
if (typeof EXTEND_PROTOTYPES === 'object' && EXTEND_PROTOTYPES !== null) {
Expand Down
54 changes: 16 additions & 38 deletions packages/@ember/array/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,6 @@ import { assert } from '@ember/debug';
import Enumerable from '@ember/enumerable';
import MutableEnumerable from '@ember/enumerable/mutable';
import { compare, typeOf } from '@ember/utils';
import { ENV } from '@ember/-internals/environment';
import Observable from '@ember/object/observable';
import type { MethodNamesOf, MethodParams, MethodReturns } from '@ember/-internals/utility-types';
import type { ComputedPropertyCallback } from '@ember/-internals/metal';
Expand Down Expand Up @@ -1858,11 +1857,7 @@ const MutableArray = Mixin.create(EmberArray, MutableEnumerable, {

/**
Creates an `Ember.NativeArray` from an Array-like object.
Does not modify the original object's contents. `A()` is not needed if
`EmberENV.EXTEND_PROTOTYPES` is `true` (the default value). However,
it is recommended that you use `A()` when creating addons for
ember or when you can not guarantee that `EmberENV.EXTEND_PROTOTYPES`
will be `true`.
Does not modify the original object's contents.

Example

Expand Down Expand Up @@ -2061,10 +2056,7 @@ interface MutableArrayWithoutNative<T>

/**
The NativeArray mixin contains the properties needed to make the native
Array support MutableArray and all of its dependent APIs. Unless you
have `EmberENV.EXTEND_PROTOTYPES` or `EmberENV.EXTEND_PROTOTYPES.Array` set to
false, this will be applied automatically. Otherwise you can apply the mixin
at anytime by calling `Ember.NativeArray.apply(Array.prototype)`.
Array support MutableArray and all of its dependent APIs.

@class Ember.NativeArray
@uses MutableArray
Expand Down Expand Up @@ -2101,34 +2093,20 @@ NativeArray = NativeArray.without(...ignore);

let A: <T>(arr?: Array<T>) => NativeArray<T>;

if (ENV.EXTEND_PROTOTYPES.Array) {
NativeArray.apply(Array.prototype, true);

A = function <T>(this: unknown, arr?: Array<T>) {
assert(
'You cannot create an Ember Array with `new A()`, please update to calling A as a function: `A()`',
!(this instanceof A)
);

// SAFTEY: Since we are extending prototypes all true native arrays are Ember NativeArrays
return (arr || []) as NativeArray<T>;
};
} else {
A = function <T>(this: unknown, arr?: Array<T>) {
assert(
'You cannot create an Ember Array with `new A()`, please update to calling A as a function: `A()`',
!(this instanceof A)
);

if (isEmberArray(arr)) {
// SAFETY: If it's a true native array and it is also an EmberArray then it should be an Ember NativeArray
return arr as unknown as NativeArray<T>;
} else {
// SAFETY: This will return an NativeArray but TS can't infer that.
return NativeArray.apply(arr ?? []) as NativeArray<T>;
}
};
}
A = function <T>(this: unknown, arr?: Array<T>) {
assert(
'You cannot create an Ember Array with `new A()`, please update to calling A as a function: `A()`',
!(this instanceof A)
);

if (isEmberArray(arr)) {
// SAFETY: If it's a true native array and it is also an EmberArray then it should be an Ember NativeArray
return arr as unknown as NativeArray<T>;
} else {
// SAFETY: This will return an NativeArray but TS can't infer that.
return NativeArray.apply(arr ?? []) as NativeArray<T>;
}
};

export { A, NativeArray, MutableArray };

Expand Down
24 changes: 12 additions & 12 deletions tests/node/app-boot-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,21 +13,21 @@ QUnit.module('App Boot', function (hooks) {
QUnit.test('nested {{component}}', function (assert) {
this.template('index', '{{root-component}}');

this.template(
'components/root-component',
this.component(
'root-component',
{
location: 'World',
hasExistence: true,
},
"\
<h1>Hello {{#if this.hasExistence}}{{this.location}}{{/if}}</h1>\
<div>{{component 'foo-bar'}}</div>\
"
<h1>Hello {{#if this.hasExistence}}{{this.location}}{{/if}}</h1>\
<div>{{component 'foo-bar'}}</div>\
"
);

this.component('root-component', {
location: 'World',
hasExistence: true,
});

this.template(
'components/foo-bar',
this.component(
'foo-bar',
undefined,
'\
<p>The files are *inside* the computer?!</p>\
'
Expand Down
9 changes: 7 additions & 2 deletions tests/node/helpers/setup-app.js
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,8 @@ module.exports = function (hooks) {

this.Ember = Ember;
this.compile = compile;
this.setComponentTemplate = Ember._setComponentTemplate;
this.templateOnlyComponent = Ember._templateOnlyComponent;

Ember.testing = true;

Expand Down Expand Up @@ -166,8 +168,11 @@ function registerTemplate(name, template) {
this.register('template:' + name, this.compile(template));
}

function registerComponent(name, componentProps) {
let component = this.Ember.Component.extend(componentProps);
function registerComponent(name, componentProps, templateContents) {
let component = this.setComponentTemplate(
this.compile(templateContents),
componentProps ? this.Ember.Component.extend(componentProps) : this.templateOnlyComponent()
);
this.register('component:' + name, component);
}

Expand Down
26 changes: 14 additions & 12 deletions tests/node/visit-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -51,21 +51,24 @@ QUnit.module('Ember.Application - visit() Integration Tests', function (hooks) {
this.template('application', '<h1>Hello world</h1>\n{{outlet}}');
this.template('a', '<h2>Welcome to {{x-foo page="A"}}</h2>');
this.template('b', '<h2>{{x-foo page="B"}}</h2>');
this.template('components/x-foo', 'Page {{this.page}}');

let initCalled = false;
let didInsertElementCalled = false;

this.component('x-foo', {
tagName: 'span',
init: function () {
this._super();
initCalled = true;
},
didInsertElement: function () {
didInsertElementCalled = true;
this.component(
'x-foo',
{
tagName: 'span',
init: function () {
this._super();
initCalled = true;
},
didInsertElement: function () {
didInsertElementCalled = true;
},
},
});
'Page {{this.page}}'
);

let App = this.createApplication();

Expand Down Expand Up @@ -337,8 +340,7 @@ QUnit.module('Ember.Application - visit() Integration Tests', function (hooks) {

QUnit.test('FastBoot: tagless components can render', function (assert) {
this.template('application', "<div class='my-context'>{{my-component}}</div>");
this.component('my-component', { tagName: '' });
this.template('components/my-component', '<h1>hello world</h1>');
this.component('my-component', { tagName: '' }, '<h1>hello world</h1>');

let App = this.createApplication();

Expand Down