From 4ac8d6604d1465e2c03c30b1cc42cf415a069c86 Mon Sep 17 00:00:00 2001 From: NullVoxPopuli Date: Sat, 2 Jul 2022 10:11:03 -0400 Subject: [PATCH] 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; + } + } + `, + } + ] +})