Skip to content

Commit c517517

Browse files
committed
feat: add optional chaining support to require-computed-property-dependencies rule
1 parent 4119272 commit c517517

File tree

5 files changed

+72
-3
lines changed

5 files changed

+72
-3
lines changed

lib/rules/require-computed-property-dependencies.js

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -224,8 +224,11 @@ function findThisGetCalls(node) {
224224
new Traverser().traverse(node, {
225225
enter(child, parent) {
226226
if (
227-
types.isMemberExpression(child) &&
228-
!(types.isCallExpression(parent) && parent.callee === child) &&
227+
(types.isOptionalMemberExpression(child) || types.isMemberExpression(child)) &&
228+
!(
229+
(types.isCallExpression(parent) || types.isOptionalCallExpression(parent)) &&
230+
parent.callee === child
231+
) &&
229232
!(types.isAssignmentExpression(child.parent) && child === parent.left) && // Ignore the left side (x) of an assignment: this.x = 123;
230233
propertyGetterUtils.isSimpleThisExpression(child)
231234
) {

lib/utils/property-getter.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -87,7 +87,7 @@ function nodeToDependentKey(nodeWithThisExpression, context) {
8787
return javascriptUtils.removeWhitespace(
8888
sourceCode
8989
.getText(nodeWithThisExpression)
90-
.replace(/^this\./, '')
90+
.replace(/^this\??\./, '')
9191
.replace(/\?\./g, '.') // Replace any optional chaining.
9292
);
9393
}

lib/utils/types.js

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ module.exports = {
2929
isNewExpression,
3030
isObjectExpression,
3131
isObjectPattern,
32+
isOptionalCallExpression,
3233
isOptionalMemberExpression,
3334
isProperty,
3435
isReturnStatement,
@@ -341,6 +342,16 @@ function isObjectPattern(node) {
341342
return node !== undefined && node.type === 'ObjectPattern';
342343
}
343344

345+
/**
346+
* Check whether or not a node is an OptionalCallExpression.
347+
*
348+
* @param {Object} node The node to check.
349+
* @returns {boolean} Whether or not the node is an OptionalCallExpression.
350+
*/
351+
function isOptionalCallExpression(node) {
352+
return node.type === 'OptionalCallExpression';
353+
}
354+
344355
/**
345356
* Check whether or not a node is an OptionalMemberExpression.
346357
*

tests/lib/rules/require-computed-macros.js

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -134,6 +134,12 @@ ruleTester.run('require-computed-macros', rule, {
134134
output: "computed.reads('x.y.z')",
135135
errors: [{ message: ERROR_MESSAGE_READS, type: 'CallExpression' }],
136136
},
137+
{
138+
// Optional chaining unnecessarily used on `this`.
139+
code: 'computed(function() { return this?.x?.y?.z; })',
140+
output: "computed.reads('x.y.z')",
141+
errors: [{ message: ERROR_MESSAGE_READS, type: 'CallExpression' }],
142+
},
137143
{
138144
// Decorator:
139145
code: "class Test { @computed('x') get someProp() { return this.x; } }",

tests/lib/rules/require-computed-property-dependencies.js

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,9 @@ ruleTester.run('require-computed-property-dependencies', rule, {
2525
"Ember.computed('name', function() { return this.get('name'); });",
2626
// String concatenation in dependent key:
2727
" Ember.computed('na' + 'me', function() { return this.get('name'); });",
28+
// Optional chaining:
29+
'Ember.computed(function() { return this?.someFunction(); });',
30+
"Ember.computed('x.y', function() { return this?.x?.y });",
2831
// Without `Ember.`:
2932
"computed('name', function() {return this.get('name');});",
3033
`
@@ -880,5 +883,51 @@ ruleTester.run('require-computed-property-dependencies', rule, {
880883
},
881884
],
882885
},
886+
887+
{
888+
// Optional chaining:
889+
code: 'computed(function() { return this.x?.y?.z; })',
890+
output: "computed('x.y.z', function() { return this.x?.y?.z; })",
891+
errors: [
892+
{
893+
message: 'Use of undeclared dependencies in computed property: x.y.z',
894+
type: 'CallExpression',
895+
},
896+
],
897+
},
898+
{
899+
// Optional chaining plus overlap with non-optional-chaining:
900+
code: 'computed(function() { return this.x?.y?.z + this.x.y.foo; })',
901+
output: "computed('x.y.{foo,z}', function() { return this.x?.y?.z + this.x.y.foo; })",
902+
errors: [
903+
{
904+
message: 'Use of undeclared dependencies in computed property: x.y.foo, x.y.z',
905+
type: 'CallExpression',
906+
},
907+
],
908+
},
909+
{
910+
// Optional chaining with function call:
911+
code: 'computed(function() { return this.x?.y?.someFunction(); })',
912+
output: "computed('x.y', function() { return this.x?.y?.someFunction(); })",
913+
errors: [
914+
{
915+
message: 'Use of undeclared dependencies in computed property: x.y',
916+
type: 'CallExpression',
917+
},
918+
],
919+
},
920+
{
921+
// Optional chaining with array/object access:
922+
code: 'computed(function() { return this.x?.someArrayOrObject[index]; })',
923+
output:
924+
"computed('x.someArrayOrObject', function() { return this.x?.someArrayOrObject[index]; })",
925+
errors: [
926+
{
927+
message: 'Use of undeclared dependencies in computed property: x.someArrayOrObject',
928+
type: 'CallExpression',
929+
},
930+
],
931+
},
883932
],
884933
});

0 commit comments

Comments
 (0)