From 392981c1b72e06705ac9d2c22d0ea6a846806543 Mon Sep 17 00:00:00 2001 From: Matthew Beale Date: Sun, 1 Aug 2021 10:16:35 -0400 Subject: [PATCH] Add build assertion against `{{outlet named}}` Named outlets are effectively removed in Ember 4.0, as the render hook and `renderTemplate` method which are used to configure them have been removed. Add an assertion to catch anyone trying to use this API going forward. --- .../glimmer/tests/integration/outlet-test.js | 140 ------------------ .../plugins/assert-against-named-outlets.ts | 37 +++++ .../lib/plugins/index.ts | 3 + .../assert-against-named-outlets-test.js | 26 ++++ 4 files changed, 66 insertions(+), 140 deletions(-) create mode 100644 packages/ember-template-compiler/lib/plugins/assert-against-named-outlets.ts create mode 100644 packages/ember-template-compiler/tests/plugins/assert-against-named-outlets-test.js diff --git a/packages/@ember/-internals/glimmer/tests/integration/outlet-test.js b/packages/@ember/-internals/glimmer/tests/integration/outlet-test.js index 71d4311963b..8c0ed82f978 100644 --- a/packages/@ember/-internals/glimmer/tests/integration/outlet-test.js +++ b/packages/@ember/-internals/glimmer/tests/integration/outlet-test.js @@ -1,7 +1,5 @@ import { RenderingTestCase, moduleFor, runAppend, runTask } from 'internal-test-helpers'; -import { set } from '@ember/-internals/metal'; - moduleFor( 'outlet view', class extends RenderingTestCase { @@ -131,144 +129,6 @@ moduleFor( this.assertText('HIBYE'); } - ['@test should support an optional name']() { - this.registerTemplate('application', '

HI

{{outlet "special"}}'); - let outletState = { - render: { - owner: this.owner, - into: undefined, - outlet: 'main', - name: 'application', - controller: {}, - template: this.owner.lookup('template:application')(this.owner), - }, - outlets: Object.create(null), - }; - - runTask(() => this.component.setOutletState(outletState)); - - runAppend(this.component); - - this.assertText('HI'); - - this.assertStableRerender(); - - this.registerTemplate('special', '

BYE

'); - outletState.outlets.special = { - render: { - owner: this.owner, - into: undefined, - outlet: 'main', - name: 'special', - controller: {}, - template: this.owner.lookup('template:special')(this.owner), - }, - outlets: Object.create(null), - }; - - runTask(() => this.component.setOutletState(outletState)); - - this.assertText('HIBYE'); - } - - ['@test does not default outlet name when positional argument is present']() { - this.registerTemplate('application', '

HI

{{outlet this.someUndefinedThing}}'); - let outletState = { - render: { - owner: this.owner, - into: undefined, - outlet: 'main', - name: 'application', - controller: {}, - template: this.owner.lookup('template:application')(this.owner), - }, - outlets: Object.create(null), - }; - - runTask(() => this.component.setOutletState(outletState)); - - runAppend(this.component); - - this.assertText('HI'); - - this.assertStableRerender(); - - this.registerTemplate('special', '

BYE

'); - outletState.outlets.main = { - render: { - owner: this.owner, - into: undefined, - outlet: 'main', - name: 'special', - controller: {}, - template: this.owner.lookup('template:special')(this.owner), - }, - outlets: Object.create(null), - }; - - runTask(() => this.component.setOutletState(outletState)); - - this.assertText('HI'); - } - - ['@test should support bound outlet name']() { - let controller = { outletName: 'foo' }; - this.registerTemplate('application', '

HI

{{outlet this.outletName}}'); - let outletState = { - render: { - owner: this.owner, - into: undefined, - outlet: 'main', - name: 'application', - controller, - template: this.owner.lookup('template:application')(this.owner), - }, - outlets: Object.create(null), - }; - - runTask(() => this.component.setOutletState(outletState)); - - runAppend(this.component); - - this.assertText('HI'); - - this.assertStableRerender(); - - this.registerTemplate('foo', '

FOO

'); - outletState.outlets.foo = { - render: { - owner: this.owner, - into: undefined, - outlet: 'main', - name: 'foo', - controller: {}, - template: this.owner.lookup('template:foo')(this.owner), - }, - outlets: Object.create(null), - }; - - this.registerTemplate('bar', '

BAR

'); - outletState.outlets.bar = { - render: { - owner: this.owner, - into: undefined, - outlet: 'main', - name: 'bar', - controller: {}, - template: this.owner.lookup('template:bar')(this.owner), - }, - outlets: Object.create(null), - }; - - runTask(() => this.component.setOutletState(outletState)); - - this.assertText('HIFOO'); - - runTask(() => set(controller, 'outletName', 'bar')); - - this.assertText('HIBAR'); - } - ['@test outletState can pass through user code (liquid-fire initimate API) ']() { this.registerTemplate( 'outer', diff --git a/packages/ember-template-compiler/lib/plugins/assert-against-named-outlets.ts b/packages/ember-template-compiler/lib/plugins/assert-against-named-outlets.ts new file mode 100644 index 00000000000..8a0074f4eb5 --- /dev/null +++ b/packages/ember-template-compiler/lib/plugins/assert-against-named-outlets.ts @@ -0,0 +1,37 @@ +import { assert } from '@ember/debug'; +import { AST, ASTPlugin } from '@glimmer/syntax'; +import calculateLocationDisplay from '../system/calculate-location-display'; +import { EmberASTPluginEnvironment } from '../types'; + +/** + @module ember +*/ + +/** + Prevents usage of named outlets, a legacy concept in Ember removed in 4.0. + + @private + @class AssertAgainstNamedOutlets +*/ +export default function assertAgainstNamedOutlets(env: EmberASTPluginEnvironment): ASTPlugin { + let moduleName = env.meta?.moduleName; + + return { + name: 'assert-against-named-outlets', + + visitor: { + MustacheStatement(node: AST.MustacheStatement) { + if ( + node.path.type === 'PathExpression' && + node.path.original === 'outlet' && + node.params[0] + ) { + let sourceInformation = calculateLocationDisplay(moduleName, node.loc); + assert( + `Named outlets were removed in Ember 4.0. See https://deprecations.emberjs.com/v3.x#toc_route-render-template for guidance on alternative APIs for named outlet use cases. ${sourceInformation}` + ); + } + }, + }, + }; +} diff --git a/packages/ember-template-compiler/lib/plugins/index.ts b/packages/ember-template-compiler/lib/plugins/index.ts index 1f6d69ad01e..b9c74da1d48 100644 --- a/packages/ember-template-compiler/lib/plugins/index.ts +++ b/packages/ember-template-compiler/lib/plugins/index.ts @@ -1,5 +1,6 @@ import AssertAgainstDynamicHelpersModifiers from './assert-against-dynamic-helpers-modifiers'; import AssertAgainstNamedBlocks from './assert-against-named-blocks'; +import AssertAgainstNamedOutlets from './assert-against-named-outlets'; import AssertInputHelperWithoutBlock from './assert-input-helper-without-block'; import AssertReservedNamedArguments from './assert-reserved-named-arguments'; import AssertSplattributeExpressions from './assert-splattribute-expression'; @@ -30,6 +31,7 @@ export const RESOLUTION_MODE_TRANSFORMS = Object.freeze( TransformInElement, AssertSplattributeExpressions, TransformEachTrackArray, + AssertAgainstNamedOutlets, TransformWrapMountAndOutlet, !EMBER_NAMED_BLOCKS ? AssertAgainstNamedBlocks : null, EMBER_DYNAMIC_HELPERS_AND_MODIFIERS @@ -47,6 +49,7 @@ export const STRICT_MODE_TRANSFORMS = Object.freeze( TransformInElement, AssertSplattributeExpressions, TransformEachTrackArray, + AssertAgainstNamedOutlets, TransformWrapMountAndOutlet, !EMBER_NAMED_BLOCKS ? AssertAgainstNamedBlocks : null, !EMBER_DYNAMIC_HELPERS_AND_MODIFIERS ? AssertAgainstDynamicHelpersModifiers : null, diff --git a/packages/ember-template-compiler/tests/plugins/assert-against-named-outlets-test.js b/packages/ember-template-compiler/tests/plugins/assert-against-named-outlets-test.js new file mode 100644 index 00000000000..5618961fb1d --- /dev/null +++ b/packages/ember-template-compiler/tests/plugins/assert-against-named-outlets-test.js @@ -0,0 +1,26 @@ +import { compile } from '../../index'; +import { moduleFor, AbstractTestCase } from 'internal-test-helpers'; + +moduleFor( + 'ember-template-compiler: assert-against-named-outlets', + class extends AbstractTestCase { + [`named outlets are asserted against`]() { + expectAssertion(() => { + compile(`{{outlet 'foo'}}`, { + moduleName: 'baz/foo-bar', + }); + }, `Named outlets were removed in Ember 4.0. See https://deprecations.emberjs.com/v3.x#toc_route-render-template for guidance on alternative APIs for named outlet use cases. ('baz/foo-bar' @ L1:C5) `); + + expectAssertion(() => { + compile(`{{outlet foo}}`, { + moduleName: 'baz/foo-bar', + }); + }, `Named outlets were removed in Ember 4.0. See https://deprecations.emberjs.com/v3.x#toc_route-render-template for guidance on alternative APIs for named outlet use cases. ('baz/foo-bar' @ L1:C5) `); + + // No assertion + compile(`{{outlet}}`, { + moduleName: 'baz/foo-bar', + }); + } + } +);