Skip to content

Commit 5b02780

Browse files
authored
Merge pull request #52 from tmquinn/no-broken-super-chain
Add new rule: no-broken-super-chain
2 parents f3f7aac + 8e45774 commit 5b02780

File tree

4 files changed

+329
-1
lines changed

4 files changed

+329
-1
lines changed

config/recommended.js

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ module.exports = {
1717
'ember-best-practices/require-dependent-keys': 2,
1818
'ember-best-practices/no-lifecycle-events': 2,
1919
'ember-best-practices/no-attrs-snapshot': 2,
20-
'ember-best-practices/no-global-jquery': 2
20+
'ember-best-practices/no-global-jquery': 2,
21+
'ember-best-practices/no-broken-super-chain': 2
2122
}
2223
};

guides/rules/no-broken-super-chain.md

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
# Don't break the super chain
2+
3+
## Rule name: `no-broken-super-chain`
4+
5+
If you are overriding the `init` lifecycle hook in Ember classes like Component, Mixin, etc. it is necessary that you include a call to `_super`.
6+
7+
```javascript
8+
// GOOD
9+
10+
export default Ember.Component.extend({
11+
init() {
12+
this._super(...arguments);
13+
this.alias = this.concrete;
14+
});
15+
```
16+
17+
```javascript
18+
// BAD
19+
20+
export default Ember.Component.extend({
21+
init() {
22+
this.alias = this.concrete;
23+
}
24+
});
25+
```

lib/rules/no-broken-super-chain.js

Lines changed: 96 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,96 @@
1+
/**
2+
* @fileOverview Prevent the absence of this._super() in init() calls or the use of this prior to this._super()
3+
* @author Quinn C Hoyer
4+
*/
5+
6+
const MESSAGES = {
7+
noSuper: '\'this._super(...arguments)\' must be called in init()',
8+
noThisBeforeSuper: 'Must call \'this._super(...arguments)\' before accessing `this`',
9+
tooManySupers: 'Only call `this._super(...arguments)` once per init()'
10+
};
11+
12+
// TODO: Make this configurable
13+
const EMBER_MODULES_WITH_SUPER_CHAIN = {
14+
Component: true,
15+
Mixin: true,
16+
Route: true,
17+
Controller: true,
18+
View: true
19+
};
20+
21+
/**
22+
* Determines if this is an init method in an extension of Ember[EMBER_MODULES_WITH_SUPER_CHAIN.*]
23+
* @param {Node} node
24+
*/
25+
function isInit(node) {
26+
if (node.type === 'FunctionExpression' && node.parent && node.parent.key && node.parent.key.name === 'init') {
27+
28+
if (node.parent.parent
29+
&& node.parent.parent.parent
30+
&& node.parent.parent.parent.callee
31+
&& node.parent.parent.parent.callee.object
32+
&& node.parent.parent.parent.callee.object.object
33+
&& node.parent.parent.parent.callee.object.object.name === 'Ember') {
34+
return (node.parent.parent.parent.callee.object.property
35+
&& EMBER_MODULES_WITH_SUPER_CHAIN[node.parent.parent.parent.callee.object.property.name]);
36+
}
37+
}
38+
39+
return false;
40+
}
41+
42+
module.exports = {
43+
meta: {
44+
docs: {
45+
description: 'Prevent the absence of `this._super(...arguments)` in `init()` calls or the use of `this` prior to `this._super()`',
46+
category: 'Best Practices',
47+
recommended: true
48+
},
49+
messages: MESSAGES
50+
},
51+
create(context) {
52+
let initOverride = null;
53+
54+
return {
55+
onCodePathStart(codePath, node) {
56+
if (isInit(node)) {
57+
initOverride = {
58+
superCalled: false,
59+
superCalledFirst: false
60+
};
61+
}
62+
},
63+
onCodePathEnd(codePath, node) {
64+
if (initOverride && isInit(node)) { // TODO: Maybe check against codepath.name
65+
if (!initOverride.superCalled) {
66+
context.report({
67+
message: MESSAGES.noSuper,
68+
node
69+
});
70+
}
71+
72+
initOverride = null;
73+
}
74+
return;
75+
},
76+
'CallExpression:exit'(node) {
77+
if (initOverride) {
78+
const property = node.callee.property;
79+
if (property && property.type === 'Identifier' && property.name === '_super') {
80+
if (initOverride.superCalled) {
81+
context.report({
82+
message: MESSAGES.tooManySupers,
83+
node
84+
});
85+
} else {
86+
initOverride.superCalled = true;
87+
}
88+
}
89+
}
90+
},
91+
'Program:exit'() {
92+
initOverride = null;
93+
}
94+
};
95+
}
96+
};
Lines changed: 206 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,206 @@
1+
const rule = require('../../../lib/rules/no-broken-super-chain');
2+
const RuleTester = require('eslint').RuleTester;
3+
4+
const { noSuper, tooManySupers } = rule.meta.messages;
5+
const ruleTester = new RuleTester();
6+
7+
ruleTester.run('no-broken-super-chain', rule, {
8+
valid: [
9+
{
10+
code: `
11+
export default Ember.Component.extend({
12+
init() {
13+
this._super(...arguments);
14+
this.alias = this.concrete;
15+
},
16+
somethingNotInit() {
17+
this.alias = this.concrete;
18+
}
19+
});`,
20+
parserOptions: {
21+
ecmaVersion: 6,
22+
sourceType: 'module'
23+
}
24+
},
25+
{
26+
code: `
27+
export default Ember.Route.extend({
28+
init() {
29+
this._super(...arguments);
30+
this.get('foo');
31+
}
32+
});`,
33+
parserOptions: {
34+
ecmaVersion: 6,
35+
sourceType: 'module'
36+
}
37+
},
38+
{
39+
code: `
40+
export default Ember.Component.extend({
41+
init() {
42+
function foo () {
43+
return true;
44+
}
45+
this._super(...arguments);
46+
this.alias = this.concrete;
47+
},
48+
somethingNotInit() {
49+
this.alias = this.concrete;
50+
}
51+
});`,
52+
parserOptions: {
53+
ecmaVersion: 6,
54+
sourceType: 'module'
55+
}
56+
},
57+
{
58+
code: `
59+
export default Ember.Service.extend({
60+
init() {
61+
this.alias = this.concrete;
62+
}
63+
});`,
64+
parserOptions: {
65+
ecmaVersion: 6,
66+
sourceType: 'module'
67+
}
68+
},
69+
{
70+
code: `
71+
export default Ember.Component.extend({
72+
didInsertElement() {
73+
this.updateBlurHandler(true);
74+
}
75+
});`,
76+
parserOptions: {
77+
ecmaVersion: 6,
78+
sourceType: 'module'
79+
}
80+
},
81+
{
82+
code: `
83+
export default MyComponent.extend({
84+
didInsertElement() {
85+
this._super(...arguments);
86+
this.updateBlurHandler(true);
87+
}
88+
});`,
89+
parserOptions: {
90+
ecmaVersion: 6,
91+
sourceType: 'module'
92+
}
93+
},
94+
{
95+
code: `
96+
const foo = Ember.Component.extend({
97+
init() {
98+
this._super(...arguments);
99+
this.alias = this.concrete;
100+
}
101+
});
102+
103+
export default foo;`,
104+
parserOptions: {
105+
ecmaVersion: 6,
106+
sourceType: 'module'
107+
}
108+
}
109+
],
110+
invalid: [
111+
{
112+
code: `
113+
export default Ember.Component.extend({
114+
init() {
115+
this.alias = this.concrete;
116+
}
117+
});`,
118+
parserOptions: {
119+
ecmaVersion: 6,
120+
sourceType: 'module'
121+
},
122+
errors: [{
123+
message: noSuper
124+
}]
125+
},
126+
{
127+
code: `
128+
export default Ember.Route.extend({
129+
init() {
130+
this.get('foo');
131+
}
132+
});`,
133+
parserOptions: {
134+
ecmaVersion: 6,
135+
sourceType: 'module'
136+
},
137+
errors: [{
138+
message: noSuper
139+
}]
140+
},
141+
// TODO
142+
// {
143+
// code: `
144+
// export default Ember.Component.extend({
145+
// init() {
146+
// this._super(); // missing '...arguments'
147+
// this.alias = this.concrete;
148+
// }
149+
// });`,
150+
// parserOptions: {
151+
// ecmaVersion: 6,
152+
// sourceType: 'module'
153+
// }
154+
// },
155+
// TODO
156+
// {
157+
// code: `
158+
// export default Ember.Component.extend({
159+
// init() {
160+
// this.alias = this.concrete;
161+
// this._super(...arguments);
162+
// }
163+
// });`,
164+
// parserOptions: {
165+
// ecmaVersion: 6,
166+
// sourceType: 'module'
167+
// },
168+
// errors: [{
169+
// message: noThisBeforeSuper
170+
// }]
171+
// },
172+
{
173+
code: `
174+
export default Ember.Component.extend({
175+
init() {
176+
this._super(...arguments);
177+
this.alias = this.concrete;
178+
this._super(...arguments);
179+
}
180+
});`,
181+
parserOptions: {
182+
ecmaVersion: 6,
183+
sourceType: 'module'
184+
},
185+
errors: [{
186+
message: tooManySupers
187+
}]
188+
}
189+
// TODO
190+
// {
191+
// code: `
192+
// export default MyComponent.extend({
193+
// didInsertElement() {
194+
// this.updateBlurHandler(true);
195+
// }
196+
// });`,
197+
// parserOptions: {
198+
// ecmaVersion: 6,
199+
// sourceType: 'module'
200+
// },
201+
// errors: [{
202+
// message: noSuper
203+
// }]
204+
// }
205+
]
206+
});

0 commit comments

Comments
 (0)