Skip to content

Commit ec2032f

Browse files
authored
Merge pull request #747 from bmish/no-get-with-default-autofixer
Add autofixer to `no-get-with-default` rule
2 parents 316e770 + 79d6980 commit ec2032f

File tree

4 files changed

+93
-10
lines changed

4 files changed

+93
-10
lines changed

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -120,7 +120,7 @@ Rules are grouped by category to help you understand their purpose. Each rule ha
120120
| | Rule ID | Description |
121121
|:---|:--------|:------------|
122122
| :white_check_mark: | [avoid-leaking-state-in-ember-objects](./docs/rules/avoid-leaking-state-in-ember-objects.md) | disallow state leakage |
123-
| :white_check_mark: | [no-get-with-default](./docs/rules/no-get-with-default.md) | disallow usage of the Ember's `getWithDefault` function |
123+
| :white_check_mark::wrench: | [no-get-with-default](./docs/rules/no-get-with-default.md) | disallow usage of the Ember's `getWithDefault` function |
124124
| :white_check_mark::wrench: | [no-get](./docs/rules/no-get.md) | require using ES5 getters instead of Ember's `get` / `getProperties` functions |
125125
| | [no-proxies](./docs/rules/no-proxies.md) | disallow using array or object proxies |
126126
| :white_check_mark: | [require-super-in-init](./docs/rules/require-super-in-init.md) | require `this._super` to be called in `init` hooks |

docs/rules/no-get-with-default.md

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,15 @@
11
# no-get-with-default
22

3+
:wrench: 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.
4+
35
This rule attempts to catch and prevent the use of `getWithDefault`.
46

57
## Rule Details
68

7-
Even though the behavior for `getWithDefault` is more defined such that it only falls back to the default value on `undefined`,
8-
its inconsistency with the native `||` is confusing to many developers who assume otherwise. This rule encourages developers to use
9-
the native `||` operator instead.
9+
Even though the behavior for `getWithDefault` is more defined such that it only falls back to the default value on `undefined`, its inconsistency with the native `||` is confusing to many developers who assume otherwise. Instead, this rule encourages developers to use:
10+
11+
- `||` operator
12+
- ternary operator
1013

1114
In addition, [Nullish Coalescing Operator `??`](https://github.com/tc39/proposal-nullish-coalescing) will land in the JavaScript language soon so developers can leverage safe property access with native support instead of using `getWithDefault`.
1215

lib/rules/no-get-with-default.js

Lines changed: 37 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ module.exports = {
1515
url:
1616
'https://github.com/ember-cli/eslint-plugin-ember/tree/master/docs/rules/no-get-with-default.md',
1717
},
18+
fixable: 'code',
1819
schema: [],
1920
},
2021
create(context) {
@@ -28,7 +29,13 @@ module.exports = {
2829
node.arguments.length === 2
2930
) {
3031
// Example: this.getWithDefault('foo', 'bar');
31-
context.report(node, ERROR_MESSAGE);
32+
context.report({
33+
node,
34+
message: ERROR_MESSAGE,
35+
fix(fixer) {
36+
return fix(fixer, context, node, node.arguments[0], node.arguments[1], false);
37+
},
38+
});
3239
}
3340

3441
if (
@@ -38,9 +45,37 @@ module.exports = {
3845
types.isThisExpression(node.arguments[0])
3946
) {
4047
// Example: getWithDefault(this, 'foo', 'bar');
41-
context.report(node, ERROR_MESSAGE);
48+
context.report({
49+
node,
50+
message: ERROR_MESSAGE,
51+
fix(fixer) {
52+
return fix(fixer, context, node, node.arguments[1], node.arguments[2], true);
53+
},
54+
});
4255
}
4356
},
4457
};
4558
},
4659
};
60+
61+
/**
62+
* @param {fixer} fixer
63+
* @param {context} context
64+
* @param {node} node - node with: this.getWithDefault('foo', 'bar');
65+
* @param {node} nodeProperty - node with: 'foo'
66+
* @param {node} nodeDefault - node with: 'bar'
67+
*/
68+
function fix(fixer, context, node, nodeProperty, nodeDefault, useImportedGet) {
69+
const sourceCode = context.getSourceCode();
70+
71+
const nodePropertySourceText = sourceCode.getText(nodeProperty);
72+
const nodeDefaultSourceText = sourceCode.getText(nodeDefault);
73+
74+
// We convert it to use `this.get('property')` here for safety.
75+
// The `no-get` rule can then convert it to ES5 getters (`this.property`) if safe.
76+
const fixed = useImportedGet
77+
? `(get(this, ${nodePropertySourceText}) === undefined ? ${nodeDefaultSourceText} : get(this, ${nodePropertySourceText}))`
78+
: `(this.get(${nodePropertySourceText}) === undefined ? ${nodeDefaultSourceText} : this.get(${nodePropertySourceText}))`;
79+
80+
return fixer.replaceText(node, fixed);
81+
}

tests/lib/rules/no-get-with-default.js

Lines changed: 49 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -17,25 +17,70 @@ ruleTester.run('no-get-with-default', rule, {
1717
"getWithDefault.testMethod(testClass, 'key', [])",
1818
],
1919
invalid: [
20+
// this.getWithDefault
2021
{
21-
code: "const test = this.getWithDefault('key', []);",
22+
code: "const test = this.getWithDefault('key', []);", // With a string property.
23+
output: "const test = (this.get('key') === undefined ? [] : this.get('key'));",
2224
errors: [
2325
{
2426
message: ERROR_MESSAGE,
2527
type: 'CallExpression',
2628
},
2729
],
28-
output: null,
2930
},
3031
{
31-
code: "const test = getWithDefault(this, 'key', []);",
32+
code: 'const test = this.getWithDefault(SOME_VARIABLE, []);', // With a variable property.
33+
output:
34+
'const test = (this.get(SOME_VARIABLE) === undefined ? [] : this.get(SOME_VARIABLE));',
35+
errors: [
36+
{
37+
message: ERROR_MESSAGE,
38+
type: 'CallExpression',
39+
},
40+
],
41+
},
42+
{
43+
code: "this.getWithDefault('name', '').trim()",
44+
output: "(this.get('name') === undefined ? '' : this.get('name')).trim()", // Parenthesis matter here.
45+
errors: [
46+
{
47+
message: ERROR_MESSAGE,
48+
type: 'CallExpression',
49+
},
50+
],
51+
},
52+
53+
// getWithDefault (imported)
54+
{
55+
code: "const test = getWithDefault(this, 'key', []);", // With a string property.
56+
output: "const test = (get(this, 'key') === undefined ? [] : get(this, 'key'));",
57+
errors: [
58+
{
59+
message: ERROR_MESSAGE,
60+
type: 'CallExpression',
61+
},
62+
],
63+
},
64+
{
65+
code: 'const test = getWithDefault(this, SOME_VARIABLE, []);', // With a variable property.
66+
output:
67+
'const test = (get(this, SOME_VARIABLE) === undefined ? [] : get(this, SOME_VARIABLE));',
68+
errors: [
69+
{
70+
message: ERROR_MESSAGE,
71+
type: 'CallExpression',
72+
},
73+
],
74+
},
75+
{
76+
code: "getWithDefault(this, 'name', '').trim()",
77+
output: "(get(this, 'name') === undefined ? '' : get(this, 'name')).trim()", // Parenthesis matter here.
3278
errors: [
3379
{
3480
message: ERROR_MESSAGE,
3581
type: 'CallExpression',
3682
},
3783
],
38-
output: null,
3984
},
4085
],
4186
});

0 commit comments

Comments
 (0)