From 2a5449345e05e96eb60dc2b92ebbb5fd39f735cc Mon Sep 17 00:00:00 2001 From: NullVoxPopuli Date: Sat, 29 Jan 2022 17:12:08 -0500 Subject: [PATCH 1/8] Propose new rule: no-unsafe-this-access --- ...no-unsafe-this-access-in-async-function.js | 160 ++++++++++++++++++ 1 file changed, 160 insertions(+) create mode 100644 lib/rules/no-unsafe-this-access-in-async-function.js diff --git a/lib/rules/no-unsafe-this-access-in-async-function.js b/lib/rules/no-unsafe-this-access-in-async-function.js new file mode 100644 index 0000000000..2ed737f401 --- /dev/null +++ b/lib/rules/no-unsafe-this-access-in-async-function.js @@ -0,0 +1,160 @@ +'use strict'; + +const ERROR_MESSAGE = + // eslint-disable-next-line eslint-plugin/prefer-placeholders + 'Unsafe `this` access after `await`. ' + + 'Guard against accessing data on destroyed objects with `@ember/destroyable` `isDestroyed` and `isDestroying`'; + +const types = require('../utils/types'); +const { getImportIdentifier } = require('../utils/import'); + +// Test here: +// https://astexplorer.net/#/gist/e364803b7c576e08f232839bf3c17287/15913876e050a1ca02af71932e14b14242e36ead + +/** + * These objects have their own destroyable APIs on `this` + */ +const FRAMEWORK_EXTENDABLES = [ + { + importPath: '@glimmer/component', + }, + { + importPath: '@ember/component', + }, + { + importPath: '@ember/component/helper', + }, + { + importPath: '@ember/routing/route', + }, + { + importPath: '@ember/controller', + }, +]; + +// if already has protection, also early return +// two forms: +// - isDestroying(this) || isDestroyed(this) // on any destroyable object +// - this.isDestroying || this.isDestroyed // available on most framework objects +function isProtection(node) { + const fns = new Set(['isDestroying', 'isDestroyed']); + + switch (node.type) { + case 'CallExpression': { + return ( + fns.has(node.callee.name) && + node.arguments.length === 1 && + node.arguments[0].type === 'ThisExpression' + ); + } + case 'MemberExpression': + return node.object.type === 'ThisExpression' && fns.has(node.property.name); + default: + console.log('unhandled protection check', node); + } + + return false; +} + +//------------------------------------------------------------------------------ +// Rule Definition +//------------------------------------------------------------------------------ +/** @type {import('eslint').Rule.RuleModule} */ +module.exports = { + meta: { + type: 'suggestion', + docs: { + description: 'disallow `this` access after await unless destruction protection is present', + category: 'Miscellaneous', + recommended: true, + url: 'https://github.com/ember-cli/eslint-plugin-ember/tree/master/docs/rules/no-unsafe-this-access-in-async-function.md', + }, + fixable: 'code', + schema: [], + }, + + create(context) { + const inFunction = []; + const inClass = []; + let encounteredAwait; + let lastProtection; + + // https://eslint.org/docs/developer-guide/working-with-rules#contextgetsourcecode + const source = context.getSourceCode(); + + return { + ClassDeclaration(node) { + inClass.push(node); + }, + 'ClassDeclaration:exit'(node) { + inClass.pop(); + }, + FunctionExpression(node) { + inFunction.push(node); + encounteredAwait = null; + }, + 'FunctionExpression:exit'(node) { + inFunction.pop(); + }, + IfStatement(node) { + const { test } = node; + + switch (test.type) { + case 'LogicalExpression': { + const { left, right } = test; + + if (isProtection(left) || isProtection(right)) { + lastProtection = node; + encounteredAwait = null; + } + break; + } + default: + console.log('unhandled if statestatement', node); + } + }, + AwaitExpression(node) { + if (inClass.length === 0) { + return; + } + if (inFunction.length === 0) { + return; + } + + encounteredAwait = node.parent; + }, + MemberExpression(node) { + if (node.object.type !== 'ThisExpression') { + return; + } + if (!encounteredAwait) { + return; + } + + context.report({ + node: node.object, + message: ERROR_MESSAGE, + + // https://eslint.org/docs/developer-guide/working-with-rules#applying-fixes + *fix(fixer) { + if (!encounteredAwait) { + return; + } + + const toFix = encounteredAwait; + encounteredAwait = null; + + const protection = '\nif (isDestroying(this) || isDestroyed(this)) return;'; + const original = source.getText(toFix); + + yield fixer.replaceText(toFix, original + protection); + + // extend range of the fix to the range + yield fixer.insertTextBefore(toFix, ''); + yield fixer.insertTextAfter(toFix, ''); + }, + }); + }, + }; + }, +}; From 4ac8d6604d1465e2c03c30b1cc42cf415a069c86 Mon Sep 17 00:00:00 2001 From: NullVoxPopuli Date: Sat, 2 Jul 2022 10:11:03 -0400 Subject: [PATCH 2/8] Add some tests --- ...no-unsafe-this-access-in-async-function.js | 15 ++- ...no-unsafe-this-access-in-async-function.js | 109 ++++++++++++++++++ 2 files changed, 123 insertions(+), 1 deletion(-) create mode 100644 tests/lib/rules/no-unsafe-this-access-in-async-function.js diff --git a/lib/rules/no-unsafe-this-access-in-async-function.js b/lib/rules/no-unsafe-this-access-in-async-function.js index 2ed737f401..0a9d49cb55 100644 --- a/lib/rules/no-unsafe-this-access-in-async-function.js +++ b/lib/rules/no-unsafe-this-access-in-async-function.js @@ -23,7 +23,7 @@ const FRAMEWORK_EXTENDABLES = [ }, { importPath: '@ember/component/helper', - }, + } { importPath: '@ember/routing/route', }, @@ -32,6 +32,16 @@ const FRAMEWORK_EXTENDABLES = [ }, ]; +/** + * These objects don't have their own destroyable APIs but are + * wired in to the framework via associateDestroyableChild + */ +const KNOWN_DESTROYABLES = [ + { + importPath: 'ember-modifier' + } +] + // if already has protection, also early return // two forms: // - isDestroying(this) || isDestroyed(this) // on any destroyable object @@ -61,6 +71,8 @@ function isProtection(node) { //------------------------------------------------------------------------------ /** @type {import('eslint').Rule.RuleModule} */ module.exports = { + KNOWN_DESTROYABLES, + FRAMEWORK_EXTENDABLES, meta: { type: 'suggestion', docs: { @@ -158,3 +170,4 @@ module.exports = { }; }, }; + diff --git a/tests/lib/rules/no-unsafe-this-access-in-async-function.js b/tests/lib/rules/no-unsafe-this-access-in-async-function.js new file mode 100644 index 0000000000..8a3d9b3a98 --- /dev/null +++ b/tests/lib/rules/no-unsafe-this-access-in-async-function.js @@ -0,0 +1,109 @@ +//------------------------------------------------------------------------------ +// Requirements +//------------------------------------------------------------------------------ + +const rule = require('../../../lib/rules/no-unsafe-this-access-in-async-function'); +const RuleTester = require('eslint').RuleTester; + +//------------------------------------------------------------------------------ +// Tests +//------------------------------------------------------------------------------ + +const ruleTester = new RuleTester({ + parserOptions: { + ecmaVersion: 2020, + sourceType: 'module', + }, + parser: require.resolve('@babel/eslint-parser'), +}); + +function eachDefaultExport(builder, rest = {}) { + let paths = [...rule.FRAMEWORK_EXTENDABLES, ...rule.KNOWN_DESTROYABLES].map(x => x.importPath); + let results = []; + + for (let importPath of paths) { + let specifier = 'X'; + let testCase = { + ...rest, + code: `import ${specifier} from '${importPath}'\n\n` + builder(specifier), + }; + + results.push(testCase); + } + + return results; +} + + +ruleTester.run('no-unsafe-this-access-in-async-function', rule, { + valid: [ + `class { + async foo() { + await Promise.resolve(); + this.foo; + } + }`, + eachDefaultExport((parentClass) => ` + class extends ${parentClass} { + async foo() { + await Promise.resolve(); + + if (this.isDestroying || this.isDestroyed) return; + + this.hello(); + } + } + `), + eachDefaultExport((parentClass) => ` + import { isDestroying, isDestroyed } from '@ember/destroyable'; + + class extends ${parentClass} { + async foo() { + await Promise.resolve(); + + if (isDestroying(this) || isDestroyed(this)) return; + + this.hello(); + } + } + `), + eachDefaultExport((parentClass) => ` + import { isDestroying as A, isDestroyed as B } from '@ember/destroyable'; + + class extends ${parentClass} { + async foo() { + await Promise.resolve(); + + if (B(this) || A(this)) return; + + this.hello(); + } + } + `), + ], + invalild: [ + { + code: ` + import Component from '@glimmer/component'; + + class extends Component { + async foo() { + await Promise.resolve(); + this.foo; + } + } + `, + output: ` + import Component from '@glimmer/component'; + + class extends Component { + async foo() { + await Promise.resolve(); + if (this.isDestroyed || this.isDestroying) return; + this.foo; + } + } + `, + } + ] +}) From 283da5cfe3edf956c665aa47f3c0a54241f0073c Mon Sep 17 00:00:00 2001 From: NullVoxPopuli Date: Sat, 2 Jul 2022 10:12:22 -0400 Subject: [PATCH 3/8] lints --- ...no-unsafe-this-access-in-async-function.js | 15 ++++--- ...no-unsafe-this-access-in-async-function.js | 41 +++++++++++-------- 2 files changed, 31 insertions(+), 25 deletions(-) diff --git a/lib/rules/no-unsafe-this-access-in-async-function.js b/lib/rules/no-unsafe-this-access-in-async-function.js index 0a9d49cb55..7a38475465 100644 --- a/lib/rules/no-unsafe-this-access-in-async-function.js +++ b/lib/rules/no-unsafe-this-access-in-async-function.js @@ -23,7 +23,7 @@ const FRAMEWORK_EXTENDABLES = [ }, { importPath: '@ember/component/helper', - } + }, { importPath: '@ember/routing/route', }, @@ -33,14 +33,14 @@ const FRAMEWORK_EXTENDABLES = [ ]; /** - * These objects don't have their own destroyable APIs but are - * wired in to the framework via associateDestroyableChild - */ + * These objects don't have their own destroyable APIs but are + * wired in to the framework via associateDestroyableChild + */ const KNOWN_DESTROYABLES = [ { - importPath: 'ember-modifier' - } -] + importPath: 'ember-modifier', + }, +]; // if already has protection, also early return // two forms: @@ -170,4 +170,3 @@ module.exports = { }; }, }; - diff --git a/tests/lib/rules/no-unsafe-this-access-in-async-function.js b/tests/lib/rules/no-unsafe-this-access-in-async-function.js index 8a3d9b3a98..0e086b1e1e 100644 --- a/tests/lib/rules/no-unsafe-this-access-in-async-function.js +++ b/tests/lib/rules/no-unsafe-this-access-in-async-function.js @@ -18,14 +18,16 @@ const ruleTester = new RuleTester({ }); function eachDefaultExport(builder, rest = {}) { - let paths = [...rule.FRAMEWORK_EXTENDABLES, ...rule.KNOWN_DESTROYABLES].map(x => x.importPath); - let results = []; - - for (let importPath of paths) { - let specifier = 'X'; - let testCase = { + const paths = [...rule.FRAMEWORK_EXTENDABLES, ...rule.KNOWN_DESTROYABLES].map( + (x) => x.importPath + ); + const results = []; + + for (const importPath of paths) { + const specifier = 'X'; + const testCase = { ...rest, - code: `import ${specifier} from '${importPath}'\n\n` + builder(specifier), + code: `import ${specifier} from '${importPath}'\n\n${builder(specifier)}`, }; results.push(testCase); @@ -34,7 +36,6 @@ function eachDefaultExport(builder, rest = {}) { return results; } - ruleTester.run('no-unsafe-this-access-in-async-function', rule, { valid: [ `class { @@ -43,7 +44,8 @@ ruleTester.run('no-unsafe-this-access-in-async-function', rule, { this.foo; } }`, - eachDefaultExport((parentClass) => ` + eachDefaultExport( + (parentClass) => ` class extends ${parentClass} { async foo() { await Promise.resolve(); @@ -53,8 +55,10 @@ ruleTester.run('no-unsafe-this-access-in-async-function', rule, { this.hello(); } } - `), - eachDefaultExport((parentClass) => ` + ` + ), + eachDefaultExport( + (parentClass) => ` import { isDestroying, isDestroyed } from '@ember/destroyable'; class extends ${parentClass} { @@ -66,8 +70,10 @@ ruleTester.run('no-unsafe-this-access-in-async-function', rule, { this.hello(); } } - `), - eachDefaultExport((parentClass) => ` + ` + ), + eachDefaultExport( + (parentClass) => ` import { isDestroying as A, isDestroyed as B } from '@ember/destroyable'; class extends ${parentClass} { @@ -79,7 +85,8 @@ ruleTester.run('no-unsafe-this-access-in-async-function', rule, { this.hello(); } } - `), + ` + ), ], invalild: [ { @@ -104,6 +111,6 @@ ruleTester.run('no-unsafe-this-access-in-async-function', rule, { } } `, - } - ] -}) + }, + ], +}); From e79fa3d05411750ced17e94d734b65d7aab0f916 Mon Sep 17 00:00:00 2001 From: NullVoxPopuli Date: Sat, 2 Jul 2022 10:13:26 -0400 Subject: [PATCH 4/8] force caniuse-lite update --- yarn.lock | 13 ++++--------- 1 file changed, 4 insertions(+), 9 deletions(-) diff --git a/yarn.lock b/yarn.lock index 79ce2cc7f9..ce900d0255 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1607,15 +1607,10 @@ camelcase@^6.2.0: resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-6.2.0.tgz#924af881c9d525ac9d87f40d964e5cea982a1809" integrity sha512-c7wVvbw3f37nuobQNtgsgG9POC9qMbNuMQmTCqZv23b6MIz0fcYpBiOlv9gEN/hdLdnZTDQhg6e9Dq5M1vKvfg== -caniuse-lite@^1.0.30001219: - version "1.0.30001223" - resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001223.tgz#39b49ff0bfb3ee3587000d2f66c47addc6e14443" - integrity sha512-k/RYs6zc/fjbxTjaWZemeSmOjO0JJV+KguOBA3NwPup8uzxM1cMhR2BD9XmO86GuqaqTCO8CgkgH9Rz//vdDiA== - -caniuse-lite@^1.0.30001286: - version "1.0.30001298" - resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001298.tgz#0e690039f62e91c3ea581673d716890512e7ec52" - integrity sha512-AcKqikjMLlvghZL/vfTHorlQsLDhGRalYf1+GmWCf5SCMziSGjRYQW/JEksj14NaYHIR6KIhrFAy0HV5C25UzQ== +caniuse-lite@^1.0.30001219, caniuse-lite@^1.0.30001286: + version "1.0.30001361" + resolved "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001361.tgz" + integrity sha512-ybhCrjNtkFji1/Wto6SSJKkWk6kZgVQsDq5QI83SafsF6FXv2JB4df9eEdH6g8sdGgqTXrFLjAxqBGgYoU3azQ== chalk@4.1.2, chalk@^4.0.0, chalk@^4.1.0, chalk@^4.1.1, chalk@^4.1.2: version "4.1.2" From 5a86cecdcaf4af6dfd71a57d58e440c34cec7d5a Mon Sep 17 00:00:00 2001 From: NullVoxPopuli Date: Sat, 2 Jul 2022 10:21:15 -0400 Subject: [PATCH 5/8] Add docs --- ...no-unsafe-this-access-in-async-function.md | 59 +++++++++++++++++++ 1 file changed, 59 insertions(+) create mode 100644 docs/rules/no-unsafe-this-access-in-async-function.md diff --git a/docs/rules/no-unsafe-this-access-in-async-function.md b/docs/rules/no-unsafe-this-access-in-async-function.md new file mode 100644 index 0000000000..793195d952 --- /dev/null +++ b/docs/rules/no-unsafe-this-access-in-async-function.md @@ -0,0 +1,59 @@ +# TODO: no-unsafe-this-access-in-async-function + +(TODO: only include this line if the rule is recommended) ✅ The `"extends": "plugin:ember/recommended"` property in a configuration file enables this rule. + +(TODO: only include this line if the rule is fixable) 🔧 The `--fix` option on the [command line](https://eslint.org/docs/user-guide/command-line-interface#fixing-problems) can automatically fix some of the problems reported by this rule. + +With async behaviors, accessing `this` after an `await` may be unsafe. For example, an unsafe `this`-access situation may occur if a component is torn down before an async function runs to completion. When the function resumes execution, if the component is already torn down, `this` may be undefined. This also comes up in testing where the whole application is torn down and a function may try to access other framework objects, which will be unavailable when the application is torn down. + +## Rule Details + +TODO: what the rule does goes here + +## Examples + +Examples of **incorrect** code for this rule: + +```js +// TODO: Example 1 +``` + +```js +// TODO: Example 2 +``` + +Examples of **correct** code for this rule: + +```js +// TODO: Example 1 +``` + +```js +// TODO: Example 2 +``` + +## Migration + +TODO: suggest any fast/automated techniques for fixing violations in a large codebase + +* TODO: suggestion on how to fix violations using find-and-replace / regexp +* TODO: suggestion on how to fix violations using a codemod + +## Configuration + +TODO: exclude this section if the rule has no extra configuration + +* object -- containing the following properties: + * string -- `parameterName1` -- TODO: description of parameter including the possible values and default value + * boolean -- `parameterName2` -- TODO: description of parameter including the possible values and default value + +## Related Rules + +* [TODO-related-rule-name1](related-rule-name1.md) +* [TODO-related-rule-name2](related-rule-name2.md) + +## References + +* TODO: link to relevant documentation goes here) +* TODO: link to relevant function spec goes here +* TODO: link to relevant guide goes here From a8fd18c31631b7caca42c37a9e4d7c7449de6d57 Mon Sep 17 00:00:00 2001 From: NullVoxPopuli Date: Sat, 2 Jul 2022 10:45:34 -0400 Subject: [PATCH 6/8] docs --- README.md | 1 + .../no-unsafe-this-access-in-async-function.md | 14 ++------------ lib/recommended-rules.js | 1 + 3 files changed, 4 insertions(+), 12 deletions(-) diff --git a/README.md b/README.md index 1108cf8b82..18ea19d424 100644 --- a/README.md +++ b/README.md @@ -158,6 +158,7 @@ Rules are grouped by category to help you understand their purpose. Each rule ha | [no-incorrect-calls-with-inline-anonymous-functions](./docs/rules/no-incorrect-calls-with-inline-anonymous-functions.md) | disallow inline anonymous functions as arguments to `debounce`, `once`, and `scheduleOnce` | ✅ | | | | [no-invalid-debug-function-arguments](./docs/rules/no-invalid-debug-function-arguments.md) | disallow usages of Ember's `assert()` / `warn()` / `deprecate()` functions that have the arguments passed in the wrong order. | ✅ | | | | [no-restricted-property-modifications](./docs/rules/no-restricted-property-modifications.md) | disallow modifying the specified properties | | 🔧 | | +| [no-unsafe-this-access-in-async-function](./docs/rules/no-unsafe-this-access-in-async-function.md) | disallow `this` access after await unless destruction protection is present | ✅ | 🔧 | | | [require-fetch-import](./docs/rules/require-fetch-import.md) | enforce explicit import for `fetch()` | | | | ### Routes diff --git a/docs/rules/no-unsafe-this-access-in-async-function.md b/docs/rules/no-unsafe-this-access-in-async-function.md index 793195d952..2aa006a813 100644 --- a/docs/rules/no-unsafe-this-access-in-async-function.md +++ b/docs/rules/no-unsafe-this-access-in-async-function.md @@ -1,8 +1,6 @@ -# TODO: no-unsafe-this-access-in-async-function +# no-unsafe-this-access-in-async-function -(TODO: only include this line if the rule is recommended) ✅ The `"extends": "plugin:ember/recommended"` property in a configuration file enables this rule. - -(TODO: only include this line if the rule is fixable) 🔧 The `--fix` option on the [command line](https://eslint.org/docs/user-guide/command-line-interface#fixing-problems) can automatically fix some of the problems reported by this rule. +🔧 The `--fix` option on the [command line](https://eslint.org/docs/user-guide/command-line-interface#fixing-problems) can automatically fix some of the problems reported by this rule. With async behaviors, accessing `this` after an `await` may be unsafe. For example, an unsafe `this`-access situation may occur if a component is torn down before an async function runs to completion. When the function resumes execution, if the component is already torn down, `this` may be undefined. This also comes up in testing where the whole application is torn down and a function may try to access other framework objects, which will be unavailable when the application is torn down. @@ -39,14 +37,6 @@ TODO: suggest any fast/automated techniques for fixing violations in a large cod * TODO: suggestion on how to fix violations using find-and-replace / regexp * TODO: suggestion on how to fix violations using a codemod -## Configuration - -TODO: exclude this section if the rule has no extra configuration - -* object -- containing the following properties: - * string -- `parameterName1` -- TODO: description of parameter including the possible values and default value - * boolean -- `parameterName2` -- TODO: description of parameter including the possible values and default value - ## Related Rules * [TODO-related-rule-name1](related-rule-name1.md) diff --git a/lib/recommended-rules.js b/lib/recommended-rules.js index 62d9d0b60b..e30b369df7 100644 --- a/lib/recommended-rules.js +++ b/lib/recommended-rules.js @@ -59,6 +59,7 @@ module.exports = { "ember/no-test-this-render": "error", "ember/no-try-invoke": "error", "ember/no-unnecessary-route-path-option": "error", + "ember/no-unsafe-this-access-in-async-function": "error", "ember/no-volatile-computed-properties": "error", "ember/prefer-ember-test-helpers": "error", "ember/require-computed-macros": "error", From da15e4b43a31b16cfedf67f028192f0692173dda Mon Sep 17 00:00:00 2001 From: NullVoxPopuli Date: Sat, 2 Jul 2022 10:49:52 -0400 Subject: [PATCH 7/8] docs --- README.md | 2 +- lib/recommended-rules.js | 1 - lib/rules/no-unsafe-this-access-in-async-function.js | 3 ++- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 18ea19d424..645513b847 100644 --- a/README.md +++ b/README.md @@ -158,7 +158,7 @@ Rules are grouped by category to help you understand their purpose. Each rule ha | [no-incorrect-calls-with-inline-anonymous-functions](./docs/rules/no-incorrect-calls-with-inline-anonymous-functions.md) | disallow inline anonymous functions as arguments to `debounce`, `once`, and `scheduleOnce` | ✅ | | | | [no-invalid-debug-function-arguments](./docs/rules/no-invalid-debug-function-arguments.md) | disallow usages of Ember's `assert()` / `warn()` / `deprecate()` functions that have the arguments passed in the wrong order. | ✅ | | | | [no-restricted-property-modifications](./docs/rules/no-restricted-property-modifications.md) | disallow modifying the specified properties | | 🔧 | | -| [no-unsafe-this-access-in-async-function](./docs/rules/no-unsafe-this-access-in-async-function.md) | disallow `this` access after await unless destruction protection is present | ✅ | 🔧 | | +| [no-unsafe-this-access-in-async-function](./docs/rules/no-unsafe-this-access-in-async-function.md) | disallow `this` access after await unless destruction protection is present | | 🔧 | | | [require-fetch-import](./docs/rules/require-fetch-import.md) | enforce explicit import for `fetch()` | | | | ### Routes diff --git a/lib/recommended-rules.js b/lib/recommended-rules.js index e30b369df7..62d9d0b60b 100644 --- a/lib/recommended-rules.js +++ b/lib/recommended-rules.js @@ -59,7 +59,6 @@ module.exports = { "ember/no-test-this-render": "error", "ember/no-try-invoke": "error", "ember/no-unnecessary-route-path-option": "error", - "ember/no-unsafe-this-access-in-async-function": "error", "ember/no-volatile-computed-properties": "error", "ember/prefer-ember-test-helpers": "error", "ember/require-computed-macros": "error", diff --git a/lib/rules/no-unsafe-this-access-in-async-function.js b/lib/rules/no-unsafe-this-access-in-async-function.js index 7a38475465..f78ef0c371 100644 --- a/lib/rules/no-unsafe-this-access-in-async-function.js +++ b/lib/rules/no-unsafe-this-access-in-async-function.js @@ -78,7 +78,8 @@ module.exports = { docs: { description: 'disallow `this` access after await unless destruction protection is present', category: 'Miscellaneous', - recommended: true, + // Move to recommended in next major? + recommended: false, url: 'https://github.com/ember-cli/eslint-plugin-ember/tree/master/docs/rules/no-unsafe-this-access-in-async-function.md', }, fixable: 'code', From c1f48ca965a4fc156f4a9b85fe64eff6bbdf5256 Mon Sep 17 00:00:00 2001 From: NullVoxPopuli Date: Sat, 2 Jul 2022 22:32:24 -0400 Subject: [PATCH 8/8] wip: typo --- tests/lib/rules/no-unsafe-this-access-in-async-function.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/lib/rules/no-unsafe-this-access-in-async-function.js b/tests/lib/rules/no-unsafe-this-access-in-async-function.js index 0e086b1e1e..820f6fbb70 100644 --- a/tests/lib/rules/no-unsafe-this-access-in-async-function.js +++ b/tests/lib/rules/no-unsafe-this-access-in-async-function.js @@ -88,7 +88,7 @@ ruleTester.run('no-unsafe-this-access-in-async-function', rule, { ` ), ], - invalild: [ + invalid: [ { code: ` import Component from '@glimmer/component';