From 4d79934d060da1c13c5634c8a3421def8b6e9009 Mon Sep 17 00:00:00 2001 From: samridhi Date: Mon, 17 Apr 2023 16:51:18 +0200 Subject: [PATCH 1/6] make sure the init hook is called with arguments --- docs/rules/require-super-in-lifecycle-hooks.md | 10 +++++----- lib/rules/require-super-in-lifecycle-hooks.js | 3 ++- 2 files changed, 7 insertions(+), 6 deletions(-) diff --git a/docs/rules/require-super-in-lifecycle-hooks.md b/docs/rules/require-super-in-lifecycle-hooks.md index c209e72106..796de3f273 100644 --- a/docs/rules/require-super-in-lifecycle-hooks.md +++ b/docs/rules/require-super-in-lifecycle-hooks.md @@ -8,7 +8,7 @@ Call super in lifecycle hooks. -When overriding lifecycle hooks inside Ember Components, Controllers, Routes, Mixins, or Services, it is necessary to include a call to super. +When overriding lifecycle hooks inside Ember Components, Controllers, Routes, Mixins, or Services, it is necessary to include a call to super. It is necessary to call the super.int() hook with arguments. ## Examples @@ -20,7 +20,7 @@ import Component from '@ember/component'; export default Component.extend({ init() { this.set('items', []); - } + }, }); ``` @@ -30,7 +30,7 @@ import Component from '@ember/component'; export default Component.extend({ didInsertElement() { // ... - } + }, }); ``` @@ -53,7 +53,7 @@ export default Component.extend({ init(...args) { this._super(...args); this.set('items', []); - } + }, }); ``` @@ -64,7 +64,7 @@ export default Component.extend({ didInsertElement(...args) { this._super(...args); // ... - } + }, }); ``` diff --git a/lib/rules/require-super-in-lifecycle-hooks.js b/lib/rules/require-super-in-lifecycle-hooks.js index 03d91c337d..030fd19a3a 100644 --- a/lib/rules/require-super-in-lifecycle-hooks.js +++ b/lib/rules/require-super-in-lifecycle-hooks.js @@ -44,7 +44,8 @@ function isNativeSuper(node, hook) { types.isMemberExpression(node.callee) && node.callee.object.type === 'Super' && types.isIdentifier(node.callee.property) && - node.callee.property.name === hook + node.callee.property.name === hook && + (hook !== 'init' || node.arguments.length > 0) ); } From ef63805395e935768594268be9a1be11ffee5f34 Mon Sep 17 00:00:00 2001 From: samridhi Date: Mon, 17 Apr 2023 22:51:44 +0200 Subject: [PATCH 2/6] add some test cases --- lib/rules/require-super-in-lifecycle-hooks.js | 45 ++++++++++--- .../rules/require-super-in-lifecycle-hooks.js | 65 +++++++++++++++++++ 2 files changed, 100 insertions(+), 10 deletions(-) diff --git a/lib/rules/require-super-in-lifecycle-hooks.js b/lib/rules/require-super-in-lifecycle-hooks.js index 030fd19a3a..4504fc1acc 100644 --- a/lib/rules/require-super-in-lifecycle-hooks.js +++ b/lib/rules/require-super-in-lifecycle-hooks.js @@ -6,15 +6,20 @@ const estraverse = require('estraverse'); function hasMatchingNode(node, matcher) { let foundMatch = false; + let foundNode = null; estraverse.traverse(node, { enter(child) { if (!foundMatch && matcher(child)) { foundMatch = true; + foundNode = child; } }, fallback: 'iteration', }); - return foundMatch; + return { + foundMatch, + foundNode, + }; } /** @@ -44,8 +49,7 @@ function isNativeSuper(node, hook) { types.isMemberExpression(node.callee) && node.callee.object.type === 'Super' && types.isIdentifier(node.callee.property) && - node.callee.property.name === hook && - (hook !== 'init' || node.arguments.length > 0) + node.callee.property.name === hook ); } @@ -90,7 +94,7 @@ module.exports = { const checkInitOnly = context.options[0] && context.options[0].checkInitOnly; const checkNativeClasses = !context.options[0] || context.options[0].checkNativeClasses; - function report(node, isNativeClass, lifecycleHookName) { + function report(node, isNativeClass, lifecycleHookName, hasSuper) { context.report({ node, message: ERROR_MESSAGE, @@ -101,12 +105,23 @@ module.exports = { : '...arguments'; const replacement = isNativeClass - ? `super.${lifecycleHookName}(${replacementArgs});` - : `this._super(${replacementArgs});`; + ? `super.${lifecycleHookName}(${replacementArgs})` + : `this._super(${replacementArgs})`; + + // If super is already called, replace it with the correct call + if (hasSuper) { + const nodeToBeReplaced = hasMatchingNode(node, (bodyChild) => + isNativeClass + ? isNativeSuper(bodyChild, lifecycleHookName) + : isClassicSuper(bodyChild) + ).foundNode; + return fixer.replaceText(nodeToBeReplaced, replacement); + } + // Insert right after function curly brace. const sourceCode = context.getSourceCode(); const startOfBlockStatement = sourceCode.getFirstToken(node.value.body); - return fixer.insertTextAfter(startOfBlockStatement, `\n${replacement}`); + return fixer.insertTextAfter(startOfBlockStatement, `\n${replacement};`); }, }); } @@ -158,9 +173,19 @@ module.exports = { const body = isNativeSuper ? node.value.body : node.body; const hasSuper = hasMatchingNode(body, (bodyChild) => isNativeClass ? isNativeSuper(bodyChild, hookName) : isClassicSuper(bodyChild) - ); - if (!hasSuper) { - report(node, isNativeClass, hookName); + ).foundMatch; + + if (hookName === 'init' && hasSuper) { + const hasArguments = hasMatchingNode( + body, + (bodyChild) => bodyChild.arguments && bodyChild.arguments.length > 0 + ).foundMatch; + + if (!hasArguments) { + report(node, isNativeClass, hookName, hasSuper); + } + } else if (!hasSuper) { + report(node, isNativeClass, hookName, hasSuper); } } diff --git a/tests/lib/rules/require-super-in-lifecycle-hooks.js b/tests/lib/rules/require-super-in-lifecycle-hooks.js index 02161c98f8..cab9d925d2 100644 --- a/tests/lib/rules/require-super-in-lifecycle-hooks.js +++ b/tests/lib/rules/require-super-in-lifecycle-hooks.js @@ -858,5 +858,70 @@ super.didInsertElement(...arguments);} options: [{ checkNativeClasses: true, checkInitOnly: false }], errors: [{ message, line: 3 }], }, + + // init hooks shoudn't call super without ...arguments + { + code: `import Service from '@ember/service'; + class Foo extends Service { + init() { + super.init(); + } + }`, + output: `import Service from '@ember/service'; + class Foo extends Service { + init() { + super.init(...arguments); + } + }`, + options: [{ checkNativeClasses: true }], + errors: [{ message, line: 3 }], + }, + { + code: `import Service from '@ember/service'; + class Foo extends Service { + init() { + super.init(); + console.log(); + } + }`, + output: `import Service from '@ember/service'; + class Foo extends Service { + init() { + super.init(...arguments); + console.log(); + } + }`, + options: [{ checkNativeClasses: true }], + errors: [{ message, line: 3 }], + }, + { + code: `export default Component.extend({ + init() { + this._super(); + }, + });`, + output: `export default Component.extend({ + init() { + this._super(...arguments); + }, + });`, + options: [{ checkInitOnly: false }], + errors: [{ message, line: 2 }], + }, + { + code: `export default Component.extend({ + init() { + this._super(); + console.log(); + }, + });`, + output: `export default Component.extend({ + init() { + this._super(...arguments); + console.log(); + }, + });`, + errors: [{ message, line: 2 }], + }, ], }); From a15179189026213d46e7b691e943e94b5fad1c8d Mon Sep 17 00:00:00 2001 From: samridhi Date: Mon, 17 Apr 2023 23:10:36 +0200 Subject: [PATCH 3/6] fix bug in hasSuperWithArguments logic --- lib/rules/require-super-in-lifecycle-hooks.js | 9 ++++++--- tests/lib/rules/require-super-in-lifecycle-hooks.js | 6 ++++-- 2 files changed, 10 insertions(+), 5 deletions(-) diff --git a/lib/rules/require-super-in-lifecycle-hooks.js b/lib/rules/require-super-in-lifecycle-hooks.js index 4504fc1acc..0c71f40ce3 100644 --- a/lib/rules/require-super-in-lifecycle-hooks.js +++ b/lib/rules/require-super-in-lifecycle-hooks.js @@ -176,12 +176,15 @@ module.exports = { ).foundMatch; if (hookName === 'init' && hasSuper) { - const hasArguments = hasMatchingNode( + const hasSuperWithArguments = hasMatchingNode( body, - (bodyChild) => bodyChild.arguments && bodyChild.arguments.length > 0 + (bodyChild) => + (isNativeSuper(bodyChild, hookName) || isClassicSuper(bodyChild)) && + bodyChild.arguments && + bodyChild.arguments.length > 0 ).foundMatch; - if (!hasArguments) { + if (!hasSuperWithArguments) { report(node, isNativeClass, hookName, hasSuper); } } else if (!hasSuper) { diff --git a/tests/lib/rules/require-super-in-lifecycle-hooks.js b/tests/lib/rules/require-super-in-lifecycle-hooks.js index cab9d925d2..a5d917fd74 100644 --- a/tests/lib/rules/require-super-in-lifecycle-hooks.js +++ b/tests/lib/rules/require-super-in-lifecycle-hooks.js @@ -912,13 +912,15 @@ super.didInsertElement(...arguments);} code: `export default Component.extend({ init() { this._super(); - console.log(); + this.set('prop', 'value'); + this.set('prop2', 'value2'); }, });`, output: `export default Component.extend({ init() { this._super(...arguments); - console.log(); + this.set('prop', 'value'); + this.set('prop2', 'value2'); }, });`, errors: [{ message, line: 2 }], From 14a539864b9c52ab43f09873ebae67b29f3f675a Mon Sep 17 00:00:00 2001 From: samridhi Date: Tue, 18 Apr 2023 11:54:52 +0200 Subject: [PATCH 4/6] add requireArgs option --- lib/rules/require-super-in-lifecycle-hooks.js | 7 ++++++- .../rules/require-super-in-lifecycle-hooks.js | 20 ++++++++++++++++--- 2 files changed, 23 insertions(+), 4 deletions(-) diff --git a/lib/rules/require-super-in-lifecycle-hooks.js b/lib/rules/require-super-in-lifecycle-hooks.js index 0c71f40ce3..94a27fafaa 100644 --- a/lib/rules/require-super-in-lifecycle-hooks.js +++ b/lib/rules/require-super-in-lifecycle-hooks.js @@ -84,6 +84,10 @@ module.exports = { type: 'boolean', default: true, }, + requireArgs: { + type: 'boolean', + default: false, + }, }, additionalProperties: false, }, @@ -93,6 +97,7 @@ module.exports = { create(context) { const checkInitOnly = context.options[0] && context.options[0].checkInitOnly; const checkNativeClasses = !context.options[0] || context.options[0].checkNativeClasses; + const requireArgs = context.options[0] && context.options[0].requireArgs; function report(node, isNativeClass, lifecycleHookName, hasSuper) { context.report({ @@ -175,7 +180,7 @@ module.exports = { isNativeClass ? isNativeSuper(bodyChild, hookName) : isClassicSuper(bodyChild) ).foundMatch; - if (hookName === 'init' && hasSuper) { + if (hookName === 'init' && hasSuper && requireArgs) { const hasSuperWithArguments = hasMatchingNode( body, (bodyChild) => diff --git a/tests/lib/rules/require-super-in-lifecycle-hooks.js b/tests/lib/rules/require-super-in-lifecycle-hooks.js index a5d917fd74..34c69cea0b 100644 --- a/tests/lib/rules/require-super-in-lifecycle-hooks.js +++ b/tests/lib/rules/require-super-in-lifecycle-hooks.js @@ -231,6 +231,19 @@ eslintTester.run('require-super-in-lifecycle-hooks', rule, { // Does not throw with node type (ClassProperty) not handled by estraverse. 'Component.extend({init() { class Foo { @someDecorator() someProp }; return this._super(...arguments); } });', + + // init hook should not be checked for arguments when requireArgs = false + `import Service from '@ember/service'; + class Foo extends Service { + init() { + super.init(); + } + }`, + `export default Component.extend({ + init() { + this._super(...arguments); + }, + });`, ], invalid: [ { @@ -873,7 +886,7 @@ super.didInsertElement(...arguments);} super.init(...arguments); } }`, - options: [{ checkNativeClasses: true }], + options: [{ requireArgs: true }], errors: [{ message, line: 3 }], }, { @@ -891,7 +904,7 @@ super.didInsertElement(...arguments);} console.log(); } }`, - options: [{ checkNativeClasses: true }], + options: [{ requireArgs: true }], errors: [{ message, line: 3 }], }, { @@ -905,7 +918,7 @@ super.didInsertElement(...arguments);} this._super(...arguments); }, });`, - options: [{ checkInitOnly: false }], + options: [{ requireArgs: true }], errors: [{ message, line: 2 }], }, { @@ -923,6 +936,7 @@ super.didInsertElement(...arguments);} this.set('prop2', 'value2'); }, });`, + options: [{ requireArgs: true }], errors: [{ message, line: 2 }], }, ], From 088a4046469c61401647f9c109c1f06501c07bad Mon Sep 17 00:00:00 2001 From: samridhi Date: Tue, 18 Apr 2023 11:59:19 +0200 Subject: [PATCH 5/6] update documentation with the new requireArgs option --- docs/rules/require-super-in-lifecycle-hooks.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/docs/rules/require-super-in-lifecycle-hooks.md b/docs/rules/require-super-in-lifecycle-hooks.md index 796de3f273..27d856a19e 100644 --- a/docs/rules/require-super-in-lifecycle-hooks.md +++ b/docs/rules/require-super-in-lifecycle-hooks.md @@ -8,7 +8,7 @@ Call super in lifecycle hooks. -When overriding lifecycle hooks inside Ember Components, Controllers, Routes, Mixins, or Services, it is necessary to include a call to super. It is necessary to call the super.int() hook with arguments. +When overriding lifecycle hooks inside Ember Components, Controllers, Routes, Mixins, or Services, it is necessary to include a call to super. ## Examples @@ -96,3 +96,4 @@ This rule takes an optional object containing: - `boolean` -- `checkInitOnly` -- whether the rule should only check the `init` lifecycle hook and not other lifecycle hooks (default `false`) - `boolean` -- `checkNativeClasses` -- whether the rule should check lifecycle hooks in native classes (in addition to classic classes) (default `true`) +- `boolean` -- `requireArgs` -- whether the rule should check if super() in init hook is called with args (default `false`) From c922dc301b731a209b1e68f4d38b55b4a139a41f Mon Sep 17 00:00:00 2001 From: samridhi Date: Tue, 18 Apr 2023 16:32:06 +0200 Subject: [PATCH 6/6] resolve PR feeback --- lib/rules/require-super-in-lifecycle-hooks.js | 14 +++++--------- .../rules/require-super-in-lifecycle-hooks.js | 17 +++++++++++++++++ 2 files changed, 22 insertions(+), 9 deletions(-) diff --git a/lib/rules/require-super-in-lifecycle-hooks.js b/lib/rules/require-super-in-lifecycle-hooks.js index 94a27fafaa..4db2071181 100644 --- a/lib/rules/require-super-in-lifecycle-hooks.js +++ b/lib/rules/require-super-in-lifecycle-hooks.js @@ -176,18 +176,14 @@ module.exports = { } const body = isNativeSuper ? node.value.body : node.body; - const hasSuper = hasMatchingNode(body, (bodyChild) => + const hasSuperMatchingNodeResult = hasMatchingNode(body, (bodyChild) => isNativeClass ? isNativeSuper(bodyChild, hookName) : isClassicSuper(bodyChild) - ).foundMatch; + ); + const hasSuper = hasSuperMatchingNodeResult.foundMatch; if (hookName === 'init' && hasSuper && requireArgs) { - const hasSuperWithArguments = hasMatchingNode( - body, - (bodyChild) => - (isNativeSuper(bodyChild, hookName) || isClassicSuper(bodyChild)) && - bodyChild.arguments && - bodyChild.arguments.length > 0 - ).foundMatch; + const superNode = hasSuperMatchingNodeResult.foundNode; + const hasSuperWithArguments = superNode.arguments && superNode.arguments.length > 0; if (!hasSuperWithArguments) { report(node, isNativeClass, hookName, hasSuper); diff --git a/tests/lib/rules/require-super-in-lifecycle-hooks.js b/tests/lib/rules/require-super-in-lifecycle-hooks.js index 34c69cea0b..fe9f574d10 100644 --- a/tests/lib/rules/require-super-in-lifecycle-hooks.js +++ b/tests/lib/rules/require-super-in-lifecycle-hooks.js @@ -239,11 +239,28 @@ eslintTester.run('require-super-in-lifecycle-hooks', rule, { super.init(); } }`, + { + code: `import Service from '@ember/service'; + class Foo extends Service { + init() { + super.init(); + } + }`, + options: [{ requireArgs: false }], + }, `export default Component.extend({ init() { this._super(...arguments); }, });`, + { + code: `export default Component.extend({ + init() { + this._super(...arguments); + }, + });`, + options: [{ requireArgs: false }], + }, ], invalid: [ {