-
Notifications
You must be signed in to change notification settings - Fork 37
Expand file tree
/
Copy pathindex.ts
More file actions
135 lines (123 loc) · 3.58 KB
/
index.ts
File metadata and controls
135 lines (123 loc) · 3.58 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
import * as AST from '../../../ast';
import type { DecoratorImportSpecs } from '../../../util/index';
import { LIFECYCLE_HOOKS } from '../../../util/index';
import AbstractEOProp from '../abstract';
import EOActionMethod from './method';
import EOActionProp from './property';
export interface Action {
hasInfiniteLoop: boolean;
}
/**
* Ember Object Actions Property
*
* A wrapper object for Ember Object `actions` object properties to be
* transformed into a series of `ClassMethod`s with the `@action` decorator.
*
* Each action on the object is represented either by an `EOActionMethod` or
* `EOActionProp`.
*
* @example
*
* ```
* import someActionUtil from 'some/action/util';
*
* const MyObject = EmberObject.extend({
* actions: {
* someActionUtil,
* bar() {},
* }
* });
* ```
*
* transforms into:
*
* ```
* import someActionUtil from 'some/action/util';
*
* class MyObject extends EmberObject {
* @action
* someActionUtil() {
* return someActionUtil.call(this, ...arguments);
* }
*
* @action
* bar() {}
* }
* ```
*/
export default class EOActionsProp extends AbstractEOProp<
AST.EOActionsProp,
AST.ClassMethod[]
> {
readonly isClassDecorator = false as const;
protected readonly value = this.rawProp.value;
override get decoratorImportSpecs(): DecoratorImportSpecs {
return {
...super.decoratorImportSpecs,
action: true,
};
}
build(): AST.ClassMethod[] {
return this.actions.map((action) => {
return action.build();
});
}
// eslint-disable-next-line @typescript-eslint/class-literal-property-style
protected override get needsDecorators(): boolean {
return true;
}
protected override get typeErrors(): string[] {
return [...this.lifecycleHookErrors, ...this.infiniteLoopErrors];
}
private get actions(): Array<EOActionProp | EOActionMethod> {
return this.value.properties.map((raw) =>
AST.isEOActionMethod(raw)
? new EOActionMethod(raw, this.options)
: new EOActionProp(raw, this.options)
);
}
/**
* Iterate over actions and verify that the action name does not match the lifecycle hooks
* The transformation is not supported if an action has the same name as lifecycle hook
* Reference: https://github.com/ember-codemods/ember-native-class-codemod/issues/34
*/
private get lifecycleHookErrors(): string[] {
const { actions } = this;
const errors: string[] = [];
for (const action of actions) {
const { name } = action;
if (LIFECYCLE_HOOKS.has(name)) {
errors.push(
this.makeActionError(
name,
'action name matches one of the lifecycle hooks. Rename and try again. See https://github.com/ember-codemods/ember-native-class-codemod/issues/34 for more details'
)
);
}
}
return errors;
}
/**
* Validation against pattern mentioned https://github.com/scalvert/eslint-plugin-ember-es6-class/pull/2
*/
private get infiniteLoopErrors(): string[] {
const { actions } = this;
const errors: string[] = [];
for (const action of actions) {
if (action.hasInfiniteLoop) {
const { name } = action;
errors.push(
this.makeActionError(
name,
`calling the passed action would cause an infinite loop. See https://github.com/scalvert/eslint-plugin-ember-es6-class/pull/2 for more details`
)
);
}
return errors;
}
return errors;
}
private makeActionError(actionName: string, message: string): string {
return this.makeError(`[${actionName}]: ${message}`);
}
}