diff --git a/README.md b/README.md index 091945bf97..b73f8f9a16 100644 --- a/README.md +++ b/README.md @@ -101,6 +101,7 @@ module.exports = { | [closure-actions](docs/rules/closure-actions.md) | enforce usage of closure actions | ✅ | | | | [new-module-imports](docs/rules/new-module-imports.md) | enforce using "New Module Imports" from Ember RFC #176 | ✅ | | | | [no-array-prototype-extensions](docs/rules/no-array-prototype-extensions.md) | disallow usage of Ember's `Array` prototype extensions | | 🔧 | | +| [no-at-ember-render-modifiers](docs/rules/no-at-ember-render-modifiers.md) | disallow importing from @ember/render-modifiers | | | | | [no-deprecated-router-transition-methods](docs/rules/no-deprecated-router-transition-methods.md) | enforce usage of router service transition methods | | 🔧 | | | [no-function-prototype-extensions](docs/rules/no-function-prototype-extensions.md) | disallow usage of Ember's `function` prototype extensions | ✅ | | | | [no-implicit-injections](docs/rules/no-implicit-injections.md) | enforce usage of implicit service injections | | 🔧 | | diff --git a/docs/rules/no-at-ember-render-modifiers.md b/docs/rules/no-at-ember-render-modifiers.md new file mode 100644 index 0000000000..11c8131a23 --- /dev/null +++ b/docs/rules/no-at-ember-render-modifiers.md @@ -0,0 +1,56 @@ +# ember/no-at-ember-render-modifiers + + + +What's wrong with `{{did-insert}}`, `{{did-update}}`, and `{{will-destroy}}`? + +These modifiers were meant for temporary migration purposes for quickly migrating `@ember/component` from before Octane to the `@glimmer/component` we have today. Since `@ember/component` implicitly had a wrapping `div` around each component and `@glimmer/component`s have "outer HTML" semantics, an automated migration could have ended up looking something like: + +```hbs +
+ ... +
+``` + +It was intended that this would be a temporary step to help get folks off of `@ember/components` quickly in early 2020 when folks migrated to the Octane editon, but some folks continued using these modifiers. + +Additionally, this style of mananging data flow has some flaws: + +- an element is required + - this can be mitigated by using helpers, but they have the same problems mentioned below +- the behavior that is used with these modifiers can cause extra renders and infinite rendering loops + - this is the nature of "effect"-driven development / data-flow, every time an effect runs, rendering must re-occur. +- behavior that needs to be co-located is spread out, making maintenance and debugging harder + - each part of the responsibility of a "behavior" or "feature" is spread out, making it harder to find and comprehend the full picture of that behavior or feature. + +## Examples + +This rule **forbids** the following: + +```js +import didInsert from '@ember/render-modifiers/modifiers/did-insert'; +``` + +```js +import didUpdate from '@ember/render-modifiers/modifiers/did-update'; +``` + +```js +import willDestroy from '@ember/render-modifiers/modifiers/will-destroy'; +``` + +For more examples, see [the docs on ember-template-lint](https://github.com/ember-template-lint/ember-template-lint/blob/master/docs/rule/no-at-ember-render-modifiers.md). + +## References + +- [Editions](https://emberjs.com/editions/) +- [Octane Upgrade Guide](https://guides.emberjs.com/release/upgrading/current-edition/) +- [Component Documentation](https://guides.emberjs.com/release/components/) +- [Avoiding Lifecycle in Component](https://nullvoxpopuli.com/avoiding-lifecycle) +- [The `ember-template-lint` version of this rule](https://github.com/ember-template-lint/ember-template-lint/blob/master/docs/rule/no-at-ember-render-modifiers.md) +- [`ember-modifier`](https://github.com/ember-modifier/ember-modifier) +- [`@ember/render-modifiers`](https://github.com/emberjs/ember-render-modifiers) (deprecated) diff --git a/lib/rules/no-at-ember-render-modifiers.js b/lib/rules/no-at-ember-render-modifiers.js new file mode 100644 index 0000000000..d3db3ce9e5 --- /dev/null +++ b/lib/rules/no-at-ember-render-modifiers.js @@ -0,0 +1,41 @@ +'use strict'; + +const ERROR_MESSAGE = + 'Do not use @ember/render-modifiers. Instead, use derived data patterns, and/or co-locate destruction via @ember/destroyable'; +//------------------------------------------------------------------------------ +// Rule Definition +//------------------------------------------------------------------------------ + +function importDeclarationIsPackageName(node, path) { + return node.source.value === path || node.source.value.startsWith(`${path}/`); +} + +/** @type {import('eslint').Rule.RuleModule} */ +module.exports = { + meta: { + type: 'suggestion', + docs: { + description: 'disallow importing from @ember/render-modifiers', + category: 'Deprecations', + recommended: false, + url: 'https://github.com/ember-cli/eslint-plugin-ember/tree/master/docs/rules/no-at-ember-render-modifiers.md', + }, + fixable: null, + schema: [], + }, + + ERROR_MESSAGE, + + create(context) { + return { + ImportDeclaration(node) { + if (importDeclarationIsPackageName(node, '@ember/render-modifiers')) { + context.report({ + node, + message: ERROR_MESSAGE, + }); + } + }, + }; + }, +}; diff --git a/tests/lib/rules/no-at-ember-render-modifiers.js b/tests/lib/rules/no-at-ember-render-modifiers.js new file mode 100644 index 0000000000..2a7b093156 --- /dev/null +++ b/tests/lib/rules/no-at-ember-render-modifiers.js @@ -0,0 +1,60 @@ +//------------------------------------------------------------------------------ +// Requirements +//------------------------------------------------------------------------------ + +const rule = require('../../../lib/rules/no-at-ember-render-modifiers'); +const RuleTester = require('eslint').RuleTester; + +const { ERROR_MESSAGE } = rule; + +//------------------------------------------------------------------------------ +// Tests +//------------------------------------------------------------------------------ + +const ruleTester = new RuleTester({ + parserOptions: { + ecmaVersion: 2020, + sourceType: 'module', + }, +}); +ruleTester.run('no-at-ember-render-modifiers', rule, { + valid: [ + 'import x from "x"', + '', + "import { x } from 'foo';", + "import x from '@ember/foo';", + "import x from '@ember/render-modifiers-foo';", + ], + invalid: [ + { + code: 'import "@ember/render-modifiers";', + output: null, + errors: [{ message: ERROR_MESSAGE, type: 'ImportDeclaration' }], + }, + { + code: 'import x from "@ember/render-modifiers";', + output: null, + errors: [{ message: ERROR_MESSAGE, type: 'ImportDeclaration' }], + }, + { + code: 'import { x } from "@ember/render-modifiers";', + output: null, + errors: [{ message: ERROR_MESSAGE, type: 'ImportDeclaration' }], + }, + { + code: 'import didInsert from "@ember/render-modifiers/modifiers/did-insert";', + output: null, + errors: [{ message: ERROR_MESSAGE, type: 'ImportDeclaration' }], + }, + { + code: 'import didUpdate from "@ember/render-modifiers/modifiers/did-update";', + output: null, + errors: [{ message: ERROR_MESSAGE, type: 'ImportDeclaration' }], + }, + { + code: 'import willDestroy from "@ember/render-modifiers/modifiers/will-destroy";', + output: null, + errors: [{ message: ERROR_MESSAGE, type: 'ImportDeclaration' }], + }, + ], +});