Skip to content

Commit a3b12b3

Browse files
Merge pull request #14 from ember-codemods/suchita/readOnlyComputed
Add support for readOnly computed property conversion
2 parents 625343b + e08d5da commit a3b12b3

File tree

7 files changed

+210
-32
lines changed

7 files changed

+210
-32
lines changed

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ The codemod accepts the following options:
2929
## Transforms
3030

3131
<!--TRANSFORMS_START-->
32-
* [ember-tracked-properties-codemod](transforms/tracked-properties/README.md)
32+
* [tracked-properties](transforms/tracked-properties/README.md)
3333
<!--TRANSFORMS_END-->
3434

3535
## Contributing

transforms/tracked-properties/README.md

Lines changed: 85 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -9,14 +9,16 @@ npx ember-tracked-properties-codemod path/of/files/ or/some**/*glob.js
99
## Input / Output
1010

1111
<!--FIXTURES_TOC_START-->
12-
* [basic-with-prefix-false](#basic-with-prefix-false)
13-
* [basic](#basic)
14-
* [chained-complex-computed](#chained-complex-computed)
15-
* [chained-computed](#chained-computed)
16-
* [complex](#complex)
17-
* [non-computed-decorators](#non-computed-decorators)
18-
* [with-tracked](#with-tracked)
19-
<!--FIXTURES_TOC_END-->
12+
13+
- [basic-with-prefix-false](#basic-with-prefix-false)
14+
- [basic](#basic)
15+
- [chained-complex-computed](#chained-complex-computed)
16+
- [chained-computed](#chained-computed)
17+
- [complex](#complex)
18+
- [non-computed-decorators](#non-computed-decorators)
19+
- [read-only-computed-decorators](#read-only-computed-decorators)
20+
- [with-tracked](#with-tracked)
21+
<!--FIXTURES_TOC_END-->
2022

2123
## <!--FIXTURES_CONTENT_START-->
2224

@@ -292,9 +294,9 @@ export default class Foo extends Component {
292294
return get(this, 'isFoo') ? `Name: ${get(this, 'baz')}` : 'Baz';
293295
}
294296

295-
@computed('bar', 'isFoo').readOnly()
297+
@(computed('bar', 'isFoo').readOnly())
296298
get barInfo() {
297-
return get(this, 'isFoo') ? `Name: ${get(this, 'bab')}` : 'Bar';
299+
return get(this, 'isFoo') ? `Name: ${get(this, 'bar')}` : 'Bar';
298300
}
299301
}
300302
```
@@ -308,7 +310,7 @@ import { computed, get } from '@ember/object';
308310
import { alias } from '@ember/object/computed';
309311

310312
export default class Foo extends Component {
311-
bar;
313+
@tracked bar;
312314
// baz class property
313315
@tracked baz = 'barBaz';
314316

@@ -320,9 +322,79 @@ export default class Foo extends Component {
320322
return get(this, 'isFoo') ? `Name: ${get(this, 'baz')}` : 'Baz';
321323
}
322324

323-
@computed('bar', 'isFoo').readOnly()
325+
@(computed('isFoo').readOnly())
326+
get barInfo() {
327+
return get(this, 'isFoo') ? `Name: ${get(this, 'bar')}` : 'Bar';
328+
}
329+
}
330+
```
331+
332+
---
333+
334+
<a id="read-only-computed-decorators">**read-only-computed-decorators**</a>
335+
336+
**Input** (<small>[read-only-computed-decorators.input.js](__testfixtures__/read-only-computed-decorators.input.js)</small>):
337+
338+
```js
339+
import Component from '@ember/component';
340+
import { computed, get } from '@ember/object';
341+
import { alias } from '@ember/object/computed';
342+
343+
export default class Foo extends Component {
344+
bar;
345+
// baz class property
346+
baz = 'barBaz';
347+
348+
@alias('model.isFoo')
349+
isFoo;
350+
351+
@computed('baz', 'bar')
352+
get barBazInfo() {
353+
return `Bar: ${get(this, 'bar')}, Baz: ${get(this, 'baz')}`;
354+
}
355+
356+
@(computed('bar', 'isFoo').readOnly())
324357
get barInfo() {
325-
return get(this, 'isFoo') ? `Name: ${get(this, 'bab')}` : 'Bar';
358+
return get(this, 'isFoo') ? `Name: ${get(this, 'bar')}` : 'Bar';
359+
}
360+
361+
// This should not remove the 'blah' decorator since its not a computed property.
362+
@blah('bar')
363+
get barData() {
364+
return get(this, 'bar');
365+
}
366+
}
367+
```
368+
369+
**Output** (<small>[read-only-computed-decorators.output.js](__testfixtures__/read-only-computed-decorators.output.js)</small>):
370+
371+
```js
372+
import { tracked } from '@glimmer/tracking';
373+
import Component from '@ember/component';
374+
import { computed, get } from '@ember/object';
375+
import { alias } from '@ember/object/computed';
376+
377+
export default class Foo extends Component {
378+
@tracked bar;
379+
// baz class property
380+
@tracked baz = 'barBaz';
381+
382+
@alias('model.isFoo')
383+
isFoo;
384+
385+
get barBazInfo() {
386+
return `Bar: ${get(this, 'bar')}, Baz: ${get(this, 'baz')}`;
387+
}
388+
389+
@(computed('isFoo').readOnly())
390+
get barInfo() {
391+
return get(this, 'isFoo') ? `Name: ${get(this, 'bar')}` : 'Bar';
392+
}
393+
394+
// This should not remove the 'blah' decorator since its not a computed property.
395+
@blah('bar')
396+
get barData() {
397+
return get(this, 'bar');
326398
}
327399
}
328400
```

transforms/tracked-properties/__testfixtures__/non-computed-decorators.input.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,6 @@ export default class Foo extends Component {
1717

1818
@computed('bar', 'isFoo').readOnly()
1919
get barInfo() {
20-
return get(this, 'isFoo') ? `Name: ${get(this, 'bab')}` : 'Bar';
20+
return get(this, 'isFoo') ? `Name: ${get(this, 'bar')}` : 'Bar';
2121
}
2222
}

transforms/tracked-properties/__testfixtures__/non-computed-decorators.output.js

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ import { computed, get } from '@ember/object';
44
import { alias } from '@ember/object/computed';
55

66
export default class Foo extends Component {
7-
bar;
7+
@tracked bar;
88
// baz class property
99
@tracked baz = 'barBaz';
1010

@@ -16,8 +16,8 @@ export default class Foo extends Component {
1616
return get(this, 'isFoo') ? `Name: ${get(this, 'baz')}` : 'Baz';
1717
}
1818

19-
@computed('bar', 'isFoo').readOnly()
19+
@computed('isFoo').readOnly()
2020
get barInfo() {
21-
return get(this, 'isFoo') ? `Name: ${get(this, 'bab')}` : 'Bar';
21+
return get(this, 'isFoo') ? `Name: ${get(this, 'bar')}` : 'Bar';
2222
}
2323
}
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
import Component from '@ember/component';
2+
import { computed, get } from '@ember/object';
3+
import { alias } from '@ember/object/computed';
4+
5+
export default class Foo extends Component {
6+
bar;
7+
// baz class property
8+
baz = 'barBaz';
9+
10+
@alias('model.isFoo')
11+
isFoo;
12+
13+
@computed('baz', 'bar')
14+
get barBazInfo() {
15+
return `Bar: ${get(this, 'bar')}, Baz: ${get(this, 'baz')}`;
16+
}
17+
18+
@(computed('bar', 'isFoo').readOnly())
19+
get barInfo() {
20+
return get(this, 'isFoo') ? `Name: ${get(this, 'bar')}` : 'Bar';
21+
}
22+
23+
// This should not remove the 'blah' decorator since its not a computed property.
24+
@blah('bar')
25+
get barData() {
26+
return get(this, 'bar');
27+
}
28+
}
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
import { tracked } from '@glimmer/tracking';
2+
import Component from '@ember/component';
3+
import { computed, get } from '@ember/object';
4+
import { alias } from '@ember/object/computed';
5+
6+
export default class Foo extends Component {
7+
@tracked bar;
8+
// baz class property
9+
@tracked baz = 'barBaz';
10+
11+
@alias('model.isFoo')
12+
isFoo;
13+
14+
get barBazInfo() {
15+
return `Bar: ${get(this, 'bar')}, Baz: ${get(this, 'baz')}`;
16+
}
17+
18+
@(computed('isFoo').readOnly())
19+
get barInfo() {
20+
return get(this, 'isFoo') ? `Name: ${get(this, 'bar')}` : 'Bar';
21+
}
22+
23+
// This should not remove the 'blah' decorator since its not a computed property.
24+
@blah('bar')
25+
get barData() {
26+
return get(this, 'bar');
27+
}
28+
}

transforms/tracked-properties/index.js

Lines changed: 64 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,46 @@ const DEFAULT_OPTIONS = {
1111
alwaysPrefix: 'true',
1212
};
1313

14+
/**
15+
* Return true if the computed property is readOnly.
16+
* @param {*} nodeItem
17+
*/
18+
function _isReadOnlyComputedProperty(nodeItem) {
19+
return (
20+
_isComputedProperty(nodeItem) &&
21+
nodeItem.expression.callee.property &&
22+
nodeItem.expression.callee.property.name === 'readOnly'
23+
);
24+
}
25+
26+
/**
27+
* Return true if the nodeItem is a computed property. It could either
28+
* be a regular or readOnly computed property.
29+
* @param {*} nodeItem
30+
*/
31+
function _isComputedProperty(nodeItem) {
32+
return (
33+
nodeItem.expression.callee &&
34+
(nodeItem.expression.callee.name === 'computed' ||
35+
(nodeItem.expression.callee.object &&
36+
nodeItem.expression.callee.object.callee.name === 'computed'))
37+
);
38+
}
39+
40+
/**
41+
* If the nodeItem is a computed property, then return an array of argument values.
42+
* @param {*} nodeItem
43+
*/
44+
function _getArgValues(nodeItem) {
45+
if (_isComputedProperty(nodeItem)) {
46+
const nodeArguments = _isReadOnlyComputedProperty(nodeItem)
47+
? nodeItem.expression.callee.object.arguments
48+
: nodeItem.expression.arguments;
49+
50+
return nodeArguments.map(item => item.value);
51+
}
52+
}
53+
1454
module.exports = function transformer(file, api) {
1555
const configOptions = Object.assign({}, DEFAULT_OPTIONS, getOptions());
1656
const classProps = [];
@@ -24,8 +64,15 @@ module.exports = function transformer(file, api) {
2464
.forEach(path => {
2565
path.node.body.forEach(classItem => {
2666
// Collect all the class properties in the file and add it to the
27-
// classProps array.
28-
if (classItem.type === 'ClassProperty' && !classItem.decorators) {
67+
// classProps array. If there is a decorator associated with a class
68+
// property, then only add it to the array if it is a @tracked property.
69+
if (
70+
classItem.type === 'ClassProperty' &&
71+
(!classItem.decorators ||
72+
classItem.decorators.every(
73+
item => item.expression.name === 'tracked'
74+
))
75+
) {
2976
classProps.push(classItem.key.name);
3077
}
3178
// Collect all the dependent keys of the computed properties present in the file
@@ -36,13 +83,8 @@ module.exports = function transformer(file, api) {
3683
classItem.decorators
3784
) {
3885
classItem.decorators.forEach(decoratorItem => {
39-
if (
40-
decoratorItem.expression.callee &&
41-
decoratorItem.expression.callee.name === 'computed'
42-
) {
43-
const argValues = decoratorItem.expression.arguments.map(
44-
item => item.value
45-
);
86+
const argValues = _getArgValues(decoratorItem);
87+
if (argValues) {
4688
computedPropsMap[classItem.key.name] = argValues;
4789
computedProps = computedProps.concat(argValues);
4890
}
@@ -63,7 +105,7 @@ module.exports = function transformer(file, api) {
63105
if (!path.node.decorators && computedProps.includes(path.node.key.name)) {
64106
shouldImportBeAdded = true;
65107
const trackedDecorator = buildTrackedDecorator(path.node.key.name, j);
66-
108+
67109
// @TODO: Determine if @tracked can be prefixed alongside other decorators in a property,
68110
// if yes, then change this code to push the trackedDecorator along with the
69111
// others.
@@ -86,20 +128,28 @@ module.exports = function transformer(file, api) {
86128
.filter(path => {
87129
return (
88130
path.node.expression.type === 'CallExpression' &&
89-
path.node.expression.callee &&
90-
path.node.expression.callee.name === 'computed'
131+
_isComputedProperty(path.node)
91132
);
92133
})
93134
.forEach(path => {
135+
const isReadOnlyProperty = _isReadOnlyComputedProperty(path.node);
136+
const computedPropArguments = isReadOnlyProperty
137+
? path.node.expression.callee.object.arguments
138+
: path.node.expression.arguments;
139+
94140
const dependentKeys = getDependentKeys(
95-
path.node.expression.arguments,
141+
computedPropArguments,
96142
computedPropsMap,
97143
classProps
98144
);
99145
if (!dependentKeys.length) {
100146
path.replace();
101147
} else {
102-
path.node.expression.arguments = dependentKeys;
148+
if (isReadOnlyProperty) {
149+
path.node.expression.callee.object.arguments = dependentKeys;
150+
} else {
151+
path.node.expression.arguments = dependentKeys;
152+
}
103153
}
104154
});
105155

0 commit comments

Comments
 (0)