Skip to content

Commit 522a93c

Browse files
committed
feat: add catchSafeObjects and catchUnsafeObjects options to no-get-with-default rule
1 parent fcdb389 commit 522a93c

File tree

4 files changed

+87
-8
lines changed

4 files changed

+87
-8
lines changed

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

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,13 @@ const test = this.key === undefined ? [] : this.key;
4040
const test = this.key || [];
4141
```
4242

43+
## Configuration
44+
45+
This rule takes an optional object containing:
46+
47+
- `boolean` -- `catchSafeObjects` -- whether the rule should catch non-`this` imported usages like `getWithDefault(person, 'name', '')` (default `false`, TODO: enable in next major release)
48+
- `boolean` -- `catchUnsafeObjects` -- whether the rule should catch non-`this` usages like `person.getWithDefault('name', '')` even though we don't know for sure if `person` is an Ember object (default `false`, TODO: enable in next major release)
49+
4350
## References
4451

4552
- [RFC](https://github.com/emberjs/rfcs/pull/554/) to deprecate `getWithDefault`

docs/rules/no-get.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -94,8 +94,8 @@ This rule takes an optional object containing:
9494
* `boolean` -- `ignoreGetProperties` -- whether the rule should ignore `getProperties` (default `false`)
9595
* `boolean` -- `ignoreNestedPaths` -- whether the rule should ignore `this.get('some.nested.property')` (default `false`)
9696
* `boolean` -- `useOptionalChaining` -- whether the rule should use the [optional chaining operator](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Optional_chaining) `?.` to autofix nested paths such as `this.get('some.nested.property')` to `this.some?.nested?.property` (when this option is off, these nested paths won't be autofixed at all) (default `false`)
97-
* `boolean` -- `catchSafeObjects` -- whether the rule should catch `get(foo, 'bar')` (default `true`)
98-
* `boolean` -- `catchUnsafeObjects` -- whether the rule should catch `foo.get('bar')` even though we don't know for sure if `foo` is an Ember object (default `false`)
97+
* `boolean` -- `catchSafeObjects` -- whether the rule should catch non-`this` imported usages like `get(foo, 'bar')` (default `true`)
98+
* `boolean` -- `catchUnsafeObjects` -- whether the rule should catch non-`this` usages like `foo.get('bar')` even though we don't know for sure if `foo` is an Ember object (default `false`)
9999
100100
## Related Rules
101101

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

Lines changed: 37 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -17,12 +17,30 @@ module.exports = {
1717
'https://github.com/ember-cli/eslint-plugin-ember/tree/master/docs/rules/no-get-with-default.md',
1818
},
1919
fixable: 'code',
20-
schema: [],
20+
schema: [
21+
{
22+
type: 'object',
23+
properties: {
24+
catchSafeObjects: {
25+
type: 'boolean',
26+
default: false,
27+
},
28+
catchUnsafeObjects: {
29+
type: 'boolean',
30+
default: false,
31+
},
32+
},
33+
additionalProperties: false,
34+
},
35+
],
2136
},
2237
create(context) {
2338
let importedGetName;
2439
let importedGetWithDefaultName;
2540

41+
const catchSafeObjects = context.options[0] && context.options[0].catchSafeObjects;
42+
const catchUnsafeObjects = context.options[0] && context.options[0].catchUnsafeObjects;
43+
2644
return {
2745
ImportDeclaration(node) {
2846
if (node.source.value === '@ember/object') {
@@ -36,7 +54,7 @@ module.exports = {
3654
CallExpression(node) {
3755
if (
3856
types.isMemberExpression(node.callee) &&
39-
types.isThisExpression(node.callee.object) &&
57+
(types.isThisExpression(node.callee.object) || catchUnsafeObjects) &&
4058
types.isIdentifier(node.callee.property) &&
4159
node.callee.property.name === 'getWithDefault' &&
4260
node.arguments.length === 2
@@ -50,6 +68,7 @@ module.exports = {
5068
fixer,
5169
context,
5270
node,
71+
nodeObject: node.callee.object,
5372
nodeProperty: node.arguments[0],
5473
nodeDefault: node.arguments[1],
5574
isImported: false,
@@ -63,7 +82,7 @@ module.exports = {
6382
types.isIdentifier(node.callee) &&
6483
node.callee.name === importedGetWithDefaultName &&
6584
node.arguments.length === 3 &&
66-
types.isThisExpression(node.arguments[0])
85+
(types.isThisExpression(node.arguments[0]) || catchSafeObjects)
6786
) {
6887
// Example: getWithDefault(this, 'foo', 'bar');
6988
context.report({
@@ -74,6 +93,7 @@ module.exports = {
7493
fixer,
7594
context,
7695
node,
96+
nodeObject: node.arguments[0],
7797
nodeProperty: node.arguments[1],
7898
nodeDefault: node.arguments[2],
7999
isImported: true,
@@ -91,23 +111,34 @@ module.exports = {
91111
* @param {fixer} fixer
92112
* @param {context} context
93113
* @param {node} node - node with: this.getWithDefault('foo', 'bar');
114+
* @param {node} nodeObject - node with: 'this'
94115
* @param {node} nodeProperty - node with: 'foo'
95116
* @param {node} nodeDefault - node with: 'bar'
96117
* @param {boolean} isImported - whether we are dealing with the imported version of `getWithDefault`
97118
* @param {string} importedGetName - name that `get` is imported under (if at all)
98119
*/
99-
function fix({ fixer, context, node, nodeProperty, nodeDefault, isImported, importedGetName }) {
120+
function fix({
121+
fixer,
122+
context,
123+
node,
124+
nodeObject,
125+
nodeProperty,
126+
nodeDefault,
127+
isImported,
128+
importedGetName,
129+
}) {
100130
const sourceCode = context.getSourceCode();
101131

132+
const nodeObjectSourceText = sourceCode.getText(nodeObject);
102133
const nodePropertySourceText = sourceCode.getText(nodeProperty);
103134
const nodeDefaultSourceText = sourceCode.getText(nodeDefault);
104135

105136
// We convert it to use `this.get('property')` here for safety in case of nested paths.
106137
// The `no-get` rule can then convert it to ES5 getters (`this.property`) if safe.
107138
const getName = importedGetName ? importedGetName : 'get';
108139
const fixed = isImported
109-
? `(${getName}(this, ${nodePropertySourceText}) === undefined ? ${nodeDefaultSourceText} : ${getName}(this, ${nodePropertySourceText}))`
110-
: `(this.get(${nodePropertySourceText}) === undefined ? ${nodeDefaultSourceText} : this.get(${nodePropertySourceText}))`;
140+
? `(${getName}(${nodeObjectSourceText}, ${nodePropertySourceText}) === undefined ? ${nodeDefaultSourceText} : ${getName}(${nodeObjectSourceText}, ${nodePropertySourceText}))`
141+
: `(${nodeObjectSourceText}.get(${nodePropertySourceText}) === undefined ? ${nodeDefaultSourceText} : ${nodeObjectSourceText}.get(${nodePropertySourceText}))`;
111142

112143
return !isImported || importedGetName
113144
? fixer.replaceText(node, fixed)

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

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,20 @@ ruleTester.run('no-get-with-default', rule, {
1919
"testClass.getWithDefault('key', [])",
2020
"import { getWithDefault } from '@ember/object'; getWithDefault.testMethod(testClass, 'key', [])",
2121
"getWithDefault(this, 'key', []);", // Missing import
22+
23+
// With catchSafeObjects: false
24+
"import { getWithDefault } from '@ember/object'; getProperties('person', 'name', '');",
25+
{
26+
code: "import { getWithDefault } from '@ember/object'; getProperties('person', 'name', '');",
27+
options: [{ catchSafeObjects: false }],
28+
},
29+
30+
// With catchUnsafeObjects: false
31+
"person.getWithDefault('name', '');",
32+
{
33+
code: "person.getWithDefault('name', '');",
34+
options: [{ catchUnsafeObjects: false }],
35+
},
2236
],
2337
invalid: [
2438
// this.getWithDefault
@@ -117,5 +131,32 @@ import { getWithDefault } from '@ember/object'; (get(this, 'name') === undefined
117131
},
118132
],
119133
},
134+
135+
// With catchSafeObjects: true
136+
{
137+
code: "import { getWithDefault } from '@ember/object'; getWithDefault(person, 'name', '');",
138+
options: [{ catchSafeObjects: true }],
139+
output: `import { get } from '@ember/object';
140+
import { getWithDefault } from '@ember/object'; (get(person, 'name') === undefined ? '' : get(person, 'name'));`,
141+
errors: [
142+
{
143+
message: ERROR_MESSAGE,
144+
type: 'CallExpression',
145+
},
146+
],
147+
},
148+
149+
// With catchUnsafeObjects: true
150+
{
151+
code: "person.getWithDefault('name', '');",
152+
options: [{ catchUnsafeObjects: true }],
153+
output: "(person.get('name') === undefined ? '' : person.get('name'));",
154+
errors: [
155+
{
156+
message: ERROR_MESSAGE,
157+
type: 'CallExpression',
158+
},
159+
],
160+
},
120161
],
121162
});

0 commit comments

Comments
 (0)