diff --git a/packages/@ember/-internals/glimmer/index.ts b/packages/@ember/-internals/glimmer/index.ts index f29f23a4a77..c7a0759c051 100644 --- a/packages/@ember/-internals/glimmer/index.ts +++ b/packages/@ember/-internals/glimmer/index.ts @@ -88,34 +88,6 @@ @public */ -/** - Use the `{{with}}` helper when you want to alias a property to a new name. This is helpful - for semantic clarity as it allows you to retain default scope or to reference a property from another - `{{with}}` block. - - If the aliased property is "falsey", for example: `false`, `undefined` `null`, `""`, `0`, `NaN` or - an empty array, the block will not be rendered. - - ```app/templates/application.hbs - {{! Will only render if user.posts contains items}} - {{#with @model.posts as |blogPosts|}} -
- There are {{blogPosts.length}} blog posts written by {{@model.name}}. -
- {{#each blogPosts as |post|}} -
  • {{post.title}}
  • - {{/each}} - {{/with}} - ``` - - @method with - @for Ember.Templates.helpers - @param {Object} options - @return {String} HTML string - @deprecated Use '{{#let}}' instead - @public - */ - /** `{{yield}}` denotes an area of a template that will be rendered inside of another template. diff --git a/packages/@ember/-internals/glimmer/tests/integration/helpers/partial-test.js b/packages/@ember/-internals/glimmer/tests/integration/helpers/partial-test.js index 825b3cf8042..1f0b9b10e56 100644 --- a/packages/@ember/-internals/glimmer/tests/integration/helpers/partial-test.js +++ b/packages/@ember/-internals/glimmer/tests/integration/helpers/partial-test.js @@ -352,38 +352,6 @@ moduleFor( this.assertText('number: EVEN0number: ODD1number: EVEN2number: ODD3'); } - ['@test [DEPRECATED] dynamic partials in {{#with}}']() { - this.registerPartial('_thing', '{{t}}'); - - expectDeprecation(() => { - this.render( - strip` - {{#with this.item.thing as |t|}} - {{partial t}} - {{else}} - Nothing! - {{/with}}`, - { - item: { thing: false }, - } - ); - }, /`{{#with}}` is deprecated\./); - - this.assertStableRerender(); - - this.assertText('Nothing!'); - - expectDeprecation(() => { - runTask(() => set(this.context, 'item.thing', 'thing')); - }, 'The use of `{{partial}}` is deprecated, please refactor the "thing" partial to a component'); - - this.assertText('thing'); - - runTask(() => set(this.context, 'item', { thing: false })); - - this.assertText('Nothing!'); - } - ['@test dynamic partials in {{#let}}']() { this.registerPartial('_thing', '{{t}}'); diff --git a/packages/@ember/-internals/glimmer/tests/integration/syntax/with-test.js b/packages/@ember/-internals/glimmer/tests/integration/syntax/with-test.js deleted file mode 100644 index b3e44698d1d..00000000000 --- a/packages/@ember/-internals/glimmer/tests/integration/syntax/with-test.js +++ /dev/null @@ -1,447 +0,0 @@ -import { moduleFor, RenderingTestCase, strip, runTask } from 'internal-test-helpers'; - -import { get, set } from '@ember/-internals/metal'; -import { A as emberA, ObjectProxy, removeAt } from '@ember/-internals/runtime'; - -import { IfUnlessWithSyntaxTest } from '../../utils/shared-conditional-tests'; - -moduleFor( - 'Syntax test: {{#with}}', - class extends IfUnlessWithSyntaxTest { - beforeEach() { - expectDeprecation(/^`{{#with}}` is deprecated\./); - } - - templateFor({ cond, truthy, falsy }) { - return `{{#with ${cond}}}${truthy}{{else}}${falsy}{{/with}}`; - } - } -); - -moduleFor( - 'Syntax test: {{#with as}}', - class extends IfUnlessWithSyntaxTest { - beforeEach() { - expectDeprecation(/^`{{#with}}` is deprecated\./); - } - - templateFor({ cond, truthy, falsy }) { - return `{{#with ${cond} as |test|}}${truthy}{{else}}${falsy}{{/with}}`; - } - - ['@test keying off of `undefined` does not render']() { - this.render( - strip` - {{#with this.foo.bar.baz as |thing|}} - {{thing}} - {{/with}}`, - { foo: {} } - ); - - this.assertText(''); - - runTask(() => this.rerender()); - - this.assertText(''); - - runTask(() => set(this.context, 'foo', { bar: { baz: 'Here!' } })); - - this.assertText('Here!'); - - runTask(() => set(this.context, 'foo', {})); - - this.assertText(''); - } - - ['@test it renders and hides the given block based on the conditional']() { - this.render(`{{#with this.cond1 as |cond|}}{{cond.greeting}}{{else}}False{{/with}}`, { - cond1: { greeting: 'Hello' }, - }); - - this.assertText('Hello'); - - runTask(() => this.rerender()); - - this.assertText('Hello'); - - runTask(() => set(this.context, 'cond1.greeting', 'Hello world')); - - this.assertText('Hello world'); - - runTask(() => set(this.context, 'cond1', false)); - - this.assertText('False'); - - runTask(() => set(this.context, 'cond1', { greeting: 'Hello' })); - - this.assertText('Hello'); - } - - ['@test can access alias and original scope']() { - this.render(`{{#with this.person as |tom|}}{{this.title}}: {{tom.name}}{{/with}}`, { - title: 'Señor Engineer', - person: { name: 'Tom Dale' }, - }); - - this.assertText('Señor Engineer: Tom Dale'); - - runTask(() => this.rerender()); - - this.assertText('Señor Engineer: Tom Dale'); - - runTask(() => { - set(this.context, 'person.name', 'Yehuda Katz'); - set(this.context, 'title', 'Principal Engineer'); - }); - - this.assertText('Principal Engineer: Yehuda Katz'); - - runTask(() => { - set(this.context, 'person', { name: 'Tom Dale' }); - set(this.context, 'title', 'Señor Engineer'); - }); - - this.assertText('Señor Engineer: Tom Dale'); - } - - ['@test the scoped variable is not available outside the {{#with}} block.']() { - expectDeprecation( - /The `[^`]+` property(?: path)? was used in the `[^`]+` template without using `this`. This fallback behavior has been deprecated, all properties must be looked up on `this` when used in the template: {{[^}]+}}/ - ); - - this.render(`{{name}}-{{#with this.other as |name|}}{{name}}{{/with}}-{{name}}`, { - name: 'Stef', - other: 'Yehuda', - }); - - this.assertText('Stef-Yehuda-Stef'); - - runTask(() => this.rerender()); - - this.assertText('Stef-Yehuda-Stef'); - - runTask(() => set(this.context, 'other', 'Chad')); - - this.assertText('Stef-Chad-Stef'); - - runTask(() => set(this.context, 'name', 'Tom')); - - this.assertText('Tom-Chad-Tom'); - - runTask(() => { - set(this.context, 'name', 'Stef'); - set(this.context, 'other', 'Yehuda'); - }); - - this.assertText('Stef-Yehuda-Stef'); - } - - ['@test inverse template is displayed with context']() { - this.render( - `{{#with this.falsyThing as |thing|}}Has Thing{{else}}No Thing {{this.otherThing}}{{/with}}`, - { - falsyThing: null, - otherThing: 'bar', - } - ); - - this.assertText('No Thing bar'); - - runTask(() => this.rerender()); - - this.assertText('No Thing bar'); - - runTask(() => set(this.context, 'otherThing', 'biz')); - - this.assertText('No Thing biz'); - - runTask(() => set(this.context, 'falsyThing', true)); - - this.assertText('Has Thing'); - - runTask(() => set(this.context, 'otherThing', 'baz')); - - this.assertText('Has Thing'); - - runTask(() => { - set(this.context, 'otherThing', 'bar'); - set(this.context, 'falsyThing', null); - }); - - this.assertText('No Thing bar'); - } - - ['@test can access alias of a proxy']() { - this.render(`{{#with this.proxy as |person|}}{{person.name}}{{/with}}`, { - proxy: ObjectProxy.create({ content: { name: 'Tom Dale' } }), - }); - - this.assertText('Tom Dale'); - - runTask(() => this.rerender()); - - this.assertText('Tom Dale'); - - runTask(() => set(this.context, 'proxy.name', 'Yehuda Katz')); - - this.assertText('Yehuda Katz'); - - runTask(() => set(this.context, 'proxy.content', { name: 'Godfrey Chan' })); - - this.assertText('Godfrey Chan'); - - runTask(() => set(this.context, 'proxy.content.name', 'Stefan Penner')); - - this.assertText('Stefan Penner'); - - runTask(() => set(this.context, 'proxy.content', null)); - - this.assertText(''); - - runTask(() => - set(this.context, 'proxy', ObjectProxy.create({ content: { name: 'Tom Dale' } })) - ); - - this.assertText('Tom Dale'); - } - - ['@test can access alias of an array']() { - this.render( - `{{#with this.arrayThing as |words|}}{{#each words as |word|}}{{word}}{{/each}}{{/with}}`, - { - arrayThing: emberA(['Hello', ' ', 'world']), - } - ); - - this.assertText('Hello world'); - - runTask(() => this.rerender()); - - this.assertText('Hello world'); - - runTask(() => { - let array = get(this.context, 'arrayThing'); - array.replace(0, 1, ['Goodbye']); - removeAt(array, 1); - array.insertAt(1, ', '); - array.pushObject('!'); - }); - - this.assertText('Goodbye, world!'); - - runTask(() => set(this.context, 'arrayThing', ['Hello', ' ', 'world'])); - - this.assertText('Hello world'); - } - - ['@test `attrs` can be used as a block param [GH#14678]']() { - this.render('{{#with this.hash as |attrs|}}[{{this.hash.foo}}-{{attrs.foo}}]{{/with}}', { - hash: { foo: 'foo' }, - }); - - this.assertText('[foo-foo]'); - - runTask(() => this.rerender()); - - this.assertText('[foo-foo]'); - - runTask(() => this.context.set('hash.foo', 'FOO')); - - this.assertText('[FOO-FOO]'); - - runTask(() => this.context.set('hash.foo', 'foo')); - - this.assertText('[foo-foo]'); - } - } -); - -moduleFor( - 'Syntax test: Multiple {{#with as}} helpers', - class extends RenderingTestCase { - beforeEach() { - expectDeprecation(/^`{{#with}}` is deprecated\./); - } - - ['@test re-using the same variable with different {{#with}} blocks does not override each other']() { - this.render( - `Admin: {{#with this.admin as |person|}}{{person.name}}{{/with}} User: {{#with this.user as |person|}}{{person.name}}{{/with}}`, - { - admin: { name: 'Tom Dale' }, - user: { name: 'Yehuda Katz' }, - } - ); - - this.assertText('Admin: Tom Dale User: Yehuda Katz'); - - runTask(() => this.rerender()); - - this.assertText('Admin: Tom Dale User: Yehuda Katz'); - - runTask(() => { - set(this.context, 'admin.name', 'Godfrey Chan'); - set(this.context, 'user.name', 'Stefan Penner'); - }); - - this.assertText('Admin: Godfrey Chan User: Stefan Penner'); - - runTask(() => { - set(this.context, 'admin', { name: 'Tom Dale' }); - set(this.context, 'user', { name: 'Yehuda Katz' }); - }); - - this.assertText('Admin: Tom Dale User: Yehuda Katz'); - } - - ['@test the scoped variable is not available outside the {{#with}} block']() { - expectDeprecation( - /The `[^`]+` property(?: path)? was used in the `[^`]+` template without using `this`. This fallback behavior has been deprecated, all properties must be looked up on `this` when used in the template: {{[^}]+}}/ - ); - - this.render( - `{{ring}}-{{#with this.first as |ring|}}{{ring}}-{{#with this.fifth as |ring|}}{{ring}}-{{#with this.ninth as |ring|}}{{ring}}-{{/with}}{{ring}}-{{/with}}{{ring}}-{{/with}}{{ring}}`, - { - ring: 'Greed', - first: 'Limbo', - fifth: 'Wrath', - ninth: 'Treachery', - } - ); - - this.assertText('Greed-Limbo-Wrath-Treachery-Wrath-Limbo-Greed'); - - runTask(() => this.rerender()); - - this.assertText('Greed-Limbo-Wrath-Treachery-Wrath-Limbo-Greed'); - - runTask(() => { - set(this.context, 'ring', 'O'); - set(this.context, 'fifth', 'D'); - }); - - this.assertText('O-Limbo-D-Treachery-D-Limbo-O'); - - runTask(() => { - set(this.context, 'first', 'I'); - set(this.context, 'ninth', 'K'); - }); - - this.assertText('O-I-D-K-D-I-O'); - - runTask(() => { - set(this.context, 'ring', 'Greed'); - set(this.context, 'first', 'Limbo'); - set(this.context, 'fifth', 'Wrath'); - set(this.context, 'ninth', 'Treachery'); - }); - - this.assertText('Greed-Limbo-Wrath-Treachery-Wrath-Limbo-Greed'); - } - - ['@test it should support {{#with name as |foo|}}, then {{#with foo as |bar|}}']() { - this.render(`{{#with this.name as |foo|}}{{#with foo as |bar|}}{{bar}}{{/with}}{{/with}}`, { - name: 'caterpillar', - }); - - this.assertText('caterpillar'); - - runTask(() => this.rerender()); - - this.assertText('caterpillar'); - - runTask(() => set(this.context, 'name', 'butterfly')); - - this.assertText('butterfly'); - - runTask(() => set(this.context, 'name', 'caterpillar')); - - this.assertText('caterpillar'); - } - - ['@test updating the context should update the alias']() { - this.render(`{{#with this as |person|}}{{person.name}}{{/with}}`, { - name: 'Los Pivots', - }); - - this.assertText('Los Pivots'); - - runTask(() => this.rerender()); - - this.assertText('Los Pivots'); - - runTask(() => set(this.context, 'name', "l'Pivots")); - - this.assertText("l'Pivots"); - - runTask(() => set(this.context, 'name', 'Los Pivots')); - - this.assertText('Los Pivots'); - } - - ['@test nested {{#with}} blocks should have access to root context']() { - expectDeprecation( - /The `[^`]+` property(?: path)? was used in the `[^`]+` template without using `this`. This fallback behavior has been deprecated, all properties must be looked up on `this` when used in the template: {{[^}]+}}/ - ); - - this.render( - strip` - {{name}} - {{#with this.committer1.name as |name|}} - [{{name}} - {{#with this.committer2.name as |name|}} - [{{name}}] - {{/with}} - {{name}}] - {{/with}} - {{name}} - {{#with this.committer2.name as |name|}} - [{{name}} - {{#with this.committer1.name as |name|}} - [{{name}}] - {{/with}} - {{name}}] - {{/with}} - {{name}} - `, - { - name: 'ebryn', - committer1: { name: 'trek' }, - committer2: { name: 'machty' }, - } - ); - - this.assertText('ebryn[trek[machty]trek]ebryn[machty[trek]machty]ebryn'); - - runTask(() => this.rerender()); - - this.assertText('ebryn[trek[machty]trek]ebryn[machty[trek]machty]ebryn'); - - runTask(() => set(this.context, 'name', 'chancancode')); - - this.assertText('chancancode[trek[machty]trek]chancancode[machty[trek]machty]chancancode'); - - runTask(() => set(this.context, 'committer1', { name: 'krisselden' })); - - this.assertText( - 'chancancode[krisselden[machty]krisselden]chancancode[machty[krisselden]machty]chancancode' - ); - - runTask(() => { - set(this.context, 'committer1.name', 'wycats'); - set(this.context, 'committer2', { name: 'rwjblue' }); - }); - - this.assertText( - 'chancancode[wycats[rwjblue]wycats]chancancode[rwjblue[wycats]rwjblue]chancancode' - ); - - runTask(() => { - set(this.context, 'name', 'ebryn'); - set(this.context, 'committer1', { name: 'trek' }); - set(this.context, 'committer2', { name: 'machty' }); - }); - - this.assertText('ebryn[trek[machty]trek]ebryn[machty[trek]machty]ebryn'); - } - } -); diff --git a/packages/@ember/-internals/glimmer/tests/utils/shared-conditional-tests.js b/packages/@ember/-internals/glimmer/tests/utils/shared-conditional-tests.js index 9868ed3a1dc..e6571a274fc 100644 --- a/packages/@ember/-internals/glimmer/tests/utils/shared-conditional-tests.js +++ b/packages/@ember/-internals/glimmer/tests/utils/shared-conditional-tests.js @@ -217,7 +217,7 @@ class ObjectProxyGenerator extends AbstractGenerator { } // Testing behaviors shared across all conditionals, i.e. {{#if}}, {{#unless}}, -// {{#with}}, {{#each}}, {{#each-in}}, (if) and (unless) +// {{#each}}, {{#each-in}}, (if) and (unless) export class BasicConditionalsTest extends AbstractConditionalsTest { ['@test it renders the corresponding block based on the conditional']() { this.renderValues(this.truthyValue, this.falsyValue); @@ -461,7 +461,7 @@ const IfUnlessWithTestCases = [ ]; // Testing behaviors shared across the "toggling" conditionals, i.e. {{#if}}, -// {{#unless}}, {{#with}}, {{#each}}, {{#each-in}}, (if) and (unless) +// {{#unless}}, {{#each}}, {{#each-in}}, (if) and (unless) export class TogglingConditionalsTest extends BasicConditionalsTest {} // Testing behaviors shared across the (if) and (unless) helpers @@ -632,7 +632,7 @@ export class IfUnlessHelperTest extends TogglingHelperConditionalsTest {} applyMixins(IfUnlessHelperTest, ...IfUnlessWithTestCases); // Testing behaviors shared across the "toggling" syntatical constructs, -// i.e. {{#if}}, {{#unless}}, {{#with}}, {{#each}} and {{#each-in}} +// i.e. {{#if}}, {{#unless}}, {{#each}} and {{#each-in}} export class TogglingSyntaxConditionalsTest extends TogglingConditionalsTest { renderValues(...values) { let templates = []; diff --git a/packages/ember-template-compiler/lib/plugins/deprecate-with.ts b/packages/ember-template-compiler/lib/plugins/deprecate-with.ts deleted file mode 100644 index 9567a8a0eea..00000000000 --- a/packages/ember-template-compiler/lib/plugins/deprecate-with.ts +++ /dev/null @@ -1,190 +0,0 @@ -import { assert, deprecate } from '@ember/debug'; -import { AST, ASTPlugin } from '@glimmer/syntax'; -import calculateLocationDisplay from '../system/calculate-location-display'; -import { EmberASTPluginEnvironment } from '../types'; -import { isPath } from './utils'; - -/** - @module ember -*/ - -/** - A Glimmer2 AST transformation that deprecates `{{#with}}` and replace it - with `{{#let}}` and `{{#if}}` as per RFC 445. - - Transforms: - - ```handlebars - {{#with this.foo as |bar|}} - ... - {{/with}} - ``` - - Into: - - ```handlebars - {{#let this.foo as |bar|}} - {{#if bar}} - ... - {{/if}} - {{/let}} - ``` - - And: - - ```handlebars - {{#with this.foo as |bar|}} - ... - {{else}} - ... - {{/with}} - ``` - - Into: - - ```handlebars - {{#let this.foo as |bar|}} - {{#if bar}} - ... - {{else}} - ... - {{/if}} - {{/let}} - ``` - - And issues a deprecation message. - - @private - @class DeprecateWith -*/ -export default function deprecateWith(env: EmberASTPluginEnvironment): ASTPlugin { - let moduleName = env.meta?.moduleName; - let { builders: b } = env.syntax; - - return { - name: 'deprecate-with', - - visitor: { - BlockStatement(node: AST.BlockStatement) { - if (!isPath(node.path) || node.path.original !== 'with') return; - - let { params, hash, program, inverse, loc, openStrip, inverseStrip, closeStrip } = node; - - let sourceInformation = calculateLocationDisplay(moduleName, node.loc); - - assert( - `\`{{#with}}\` takes a single positional argument (the path to alias), received ${displayParams( - params - )}. ${sourceInformation}`, - params.length === 1 - ); - - assert( - `\`{{#with}}\` does not take any named arguments, received ${displayHash( - hash - )}. ${sourceInformation}`, - hash.pairs.length === 0 - ); - - assert( - `\`{{#with}}\` yields a single block param, received ${displayBlockParams( - program.blockParams - )}. ${sourceInformation}`, - program.blockParams.length <= 1 - ); - - let recommendation; - - if (program.blockParams.length === 0) { - recommendation = 'Use `{{#if}}` instead.'; - } else if (inverse) { - recommendation = 'Use `{{#let}}` together with `{{#if}} instead.'; - } else { - recommendation = - 'If you always want the block to render, replace `{{#with}}` with `{{#let}}`. ' + - 'If you want to conditionally render the block, use `{{#let}}` together with `{{#if}} instead.'; - } - - deprecate(`\`{{#with}}\` is deprecated. ${recommendation} ${sourceInformation}`, false, { - id: 'ember-glimmer.with-syntax', - until: '4.0.0', - for: 'ember-source', - url: 'https://deprecations.emberjs.com/v3.x/#toc_ember-glimmer-with-syntax', - since: { - enabled: '3.26.0-beta.1', - }, - }); - - if (program.blockParams.length === 0) { - return b.block( - 'if', - params, - null, - program, - inverse, - loc, - openStrip, - inverseStrip, - closeStrip - ); - } else { - return b.block( - 'let', - params, - null, - b.blockItself( - [ - b.block( - 'if', - [b.path(program.blockParams[0])], - null, - b.blockItself(program.body, [], program.chained, program.loc), - inverse, - loc, - openStrip, - inverseStrip, - closeStrip - ), - ], - program.blockParams, - false, - loc - ), - null, - loc, - openStrip, - inverseStrip, - closeStrip - ); - } - }, - }, - }; -} - -function displayParams(params: AST.Expression[]): string { - if (params.length === 0) { - return 'no positional arguments'; - } else { - let display = params.map((param) => `\`${JSON.stringify(param)}\``).join(', '); - return `${params.length} positional arguments: ${display}`; - } -} - -function displayHash({ pairs }: AST.Hash): string { - if (pairs.length === 0) { - return 'no named arguments'; - } else { - let display = pairs.map((pair) => `\`${pair.key}\``).join(', '); - return `${pairs.length} named arguments: ${display}`; - } -} - -function displayBlockParams(blockParams: string[]): string { - if (blockParams.length === 0) { - return 'no block params'; - } else { - let display = blockParams.map((param) => `\`${param}\``).join(', '); - return `${blockParams.length} block params: ${display}`; - } -} diff --git a/packages/ember-template-compiler/lib/plugins/index.ts b/packages/ember-template-compiler/lib/plugins/index.ts index 2efa104176a..525bea17e25 100644 --- a/packages/ember-template-compiler/lib/plugins/index.ts +++ b/packages/ember-template-compiler/lib/plugins/index.ts @@ -4,7 +4,6 @@ import AssertInputHelperWithoutBlock from './assert-input-helper-without-block'; import AssertReservedNamedArguments from './assert-reserved-named-arguments'; import AssertSplattributeExpressions from './assert-splattribute-expression'; import DeprecateSendAction from './deprecate-send-action'; -import DeprecateWith from './deprecate-with'; import TransformActionSyntax from './transform-action-syntax'; import TransformAttrsIntoArgs from './transform-attrs-into-args'; import TransformEachInIntoEach from './transform-each-in-into-each'; @@ -36,7 +35,6 @@ export const RESOLUTION_MODE_TRANSFORMS = Object.freeze( AssertSplattributeExpressions, TransformEachTrackArray, TransformWrapMountAndOutlet, - DeprecateWith, SEND_ACTION ? DeprecateSendAction : null, !EMBER_NAMED_BLOCKS ? AssertAgainstNamedBlocks : null, EMBER_DYNAMIC_HELPERS_AND_MODIFIERS @@ -55,7 +53,6 @@ export const STRICT_MODE_TRANSFORMS = Object.freeze( AssertSplattributeExpressions, TransformEachTrackArray, TransformWrapMountAndOutlet, - DeprecateWith, SEND_ACTION ? DeprecateSendAction : null, !EMBER_NAMED_BLOCKS ? AssertAgainstNamedBlocks : null, !EMBER_DYNAMIC_HELPERS_AND_MODIFIERS ? AssertAgainstDynamicHelpersModifiers : null, diff --git a/tests/docs/expected.js b/tests/docs/expected.js index 340eefc7de2..0e9200b3049 100644 --- a/tests/docs/expected.js +++ b/tests/docs/expected.js @@ -613,7 +613,6 @@ module.exports = { 'willRender', 'willTransition', 'willUpdate', - 'with', 'without', 'wrap', 'wrapModelType',