Skip to content

Commit 4119272

Browse files
authored
Merge pull request #846 from bmish/require-computed-macros-optional-chaining
Support optional chaining in `require-computed-macros` rule
2 parents 2d6e04b + ed86337 commit 4119272

File tree

4 files changed

+50
-5
lines changed

4 files changed

+50
-5
lines changed

lib/utils/property-getter.js

Lines changed: 15 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -31,23 +31,30 @@ function isThisGetCall(node) {
3131
}
3232

3333
/**
34-
* Checks if a MemberExpression node looks like `this.x` or `this.x.y` or `this.get('x')`.
34+
* Checks if a MemberExpression node looks like:
35+
* * `this.x`
36+
* * `this.x.y`
37+
* * `this.x?.y`
38+
* * `this.get('x')`.
3539
*
3640
* @param {Node} node The MemberExpression node to check.
37-
* @returns {boolean} Whether the node looks like `this.x` or `this.x.y` or `this.get('x')`.
41+
* @returns {boolean} Whether the node looks as expected.
3842
*/
3943
function isSimpleThisExpression(node) {
4044
if (isThisGetCall(node)) {
4145
return true;
4246
}
4347

44-
if (!types.isMemberExpression(node)) {
48+
if (!(types.isMemberExpression(node) || types.isOptionalMemberExpression(node))) {
4549
return false;
4650
}
4751

4852
let current = node;
4953
while (current !== null) {
50-
if (types.isMemberExpression(current) && !current.computed) {
54+
if (
55+
(types.isMemberExpression(current) || types.isOptionalMemberExpression(current)) &&
56+
!current.computed
57+
) {
5158
if (!types.isIdentifier(current.property)) {
5259
return false;
5360
}
@@ -78,6 +85,9 @@ function nodeToDependentKey(nodeWithThisExpression, context) {
7885

7986
const sourceCode = context.getSourceCode();
8087
return javascriptUtils.removeWhitespace(
81-
sourceCode.getText(nodeWithThisExpression).replace(/^this\./, '')
88+
sourceCode
89+
.getText(nodeWithThisExpression)
90+
.replace(/^this\./, '')
91+
.replace(/\?\./g, '.') // Replace any optional chaining.
8292
);
8393
}

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+
isOptionalMemberExpression,
3233
isProperty,
3334
isReturnStatement,
3435
isSpreadElement,
@@ -340,6 +341,16 @@ function isObjectPattern(node) {
340341
return node !== undefined && node.type === 'ObjectPattern';
341342
}
342343

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

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

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -97,6 +97,8 @@ ruleTester.run('require-computed-macros', rule, {
9797

9898
// MAPBY
9999
"mapBy('children', 'age')",
100+
"computed(function() { return this.children?.mapBy('age'); })", // Ignored because function might not exist.
101+
"computed(function() { return this.nested?.children.mapBy('age'); })", // Ignored because function might not exist.
100102

101103
// Decorator (these are ignored when the `includeNativeGetters` option is off):
102104
"class Test { @computed('x') get someProp() { return this.x; } }",
@@ -126,6 +128,12 @@ ruleTester.run('require-computed-macros', rule, {
126128
output: "computed.reads('x')",
127129
errors: [{ message: ERROR_MESSAGE_READS, type: 'CallExpression' }],
128130
},
131+
{
132+
// Optional chaining.
133+
code: 'computed(function() { return this.x?.y?.z; })',
134+
output: "computed.reads('x.y.z')",
135+
errors: [{ message: ERROR_MESSAGE_READS, type: 'CallExpression' }],
136+
},
129137
{
130138
// Decorator:
131139
code: "class Test { @computed('x') get someProp() { return this.x; } }",
@@ -155,6 +163,12 @@ ruleTester.run('require-computed-macros', rule, {
155163
output: "computed.and('x', 'y.z', 'w')",
156164
errors: [{ message: ERROR_MESSAGE_AND, type: 'CallExpression' }],
157165
},
166+
{
167+
// Optional chaining.
168+
code: 'computed(function() { return this.x?.y && this.z; })',
169+
output: "computed.and('x.y', 'z')",
170+
errors: [{ message: ERROR_MESSAGE_AND, type: 'CallExpression' }],
171+
},
158172
{
159173
// Decorator:
160174
code: 'class Test { @computed() get someProp() { return this.x && this.y; } }',
@@ -176,6 +190,12 @@ ruleTester.run('require-computed-macros', rule, {
176190
output: "computed.gt('x', 123)",
177191
errors: [{ message: ERROR_MESSAGE_GT, type: 'CallExpression' }],
178192
},
193+
{
194+
// Optional chaining:
195+
code: 'computed(function() { return this.x?.y > 123; })',
196+
output: "computed.gt('x.y', 123)",
197+
errors: [{ message: ERROR_MESSAGE_GT, type: 'CallExpression' }],
198+
},
179199
{
180200
// Decorator:
181201
code: 'class Test { @computed() get someProp() { return this.x > 123; } }',

tests/lib/utils/property-getter-test.js

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,10 +16,13 @@ describe('isSimpleThisExpression', () => {
1616
expect(propertyGetterUtils.isSimpleThisExpression(parse('this.x[1]'))).toBeFalsy();
1717
expect(propertyGetterUtils.isSimpleThisExpression(parse('this.x[i]'))).toBeFalsy();
1818
expect(propertyGetterUtils.isSimpleThisExpression(parse('this.x.y[i]'))).toBeFalsy();
19+
expect(propertyGetterUtils.isSimpleThisExpression(parse('this?.get()'))).toBeFalsy();
1920

2021
// True:
2122
expect(propertyGetterUtils.isSimpleThisExpression(parse('this.x'))).toBeTruthy();
2223
expect(propertyGetterUtils.isSimpleThisExpression(parse('this.x.y'))).toBeTruthy();
24+
expect(propertyGetterUtils.isSimpleThisExpression(parse('this.x?.y'))).toBeTruthy();
25+
expect(propertyGetterUtils.isSimpleThisExpression(parse('this.x?.y?.z'))).toBeTruthy();
2326
expect(propertyGetterUtils.isSimpleThisExpression(parse('this.get("property")'))).toBeTruthy();
2427
expect(propertyGetterUtils.isSimpleThisExpression(parse('this.get("x.y")'))).toBeTruthy();
2528
});
@@ -40,6 +43,7 @@ describe('isThisGetCall', () => {
4043
expect(
4144
propertyGetterUtils.isThisGetCall(parse('this.get("unexpected", "argument").something'))
4245
).toBeFalsy();
46+
expect(propertyGetterUtils.isThisGetCall(parse('this?.get("property")'))).toBeFalsy();
4347

4448
// True:
4549
expect(propertyGetterUtils.isThisGetCall(parse('this.get("property")'))).toBeTruthy();

0 commit comments

Comments
 (0)