Skip to content

Commit 9715f95

Browse files
scalvertchadhietala
authored andcommitted
Adding rule for no-unguarded-document + tests (#15)
* Adding rule for no-unguarded-document + tests
1 parent 33bccc9 commit 9715f95

File tree

5 files changed

+157
-1
lines changed

5 files changed

+157
-1
lines changed

config/recommended.js

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,8 @@ module.exports = {
1515
'ember-best-practices/no-attrs': 2,
1616
'ember-best-practices/no-observers': 2,
1717
'ember-best-practices/require-dependent-keys': 2,
18-
'ember-best-practices/no-lifecycle-events': 2
18+
'ember-best-practices/no-lifecycle-events': 2,
19+
'ember-best-practices/no-timers': 2,
20+
'ember-best-practices/no-unguarded-document': 2
1921
}
2022
};

guides/rules/no-timers.md

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,38 @@
11
# No Timers
22

3+
**TL;DR: You should avoid using timers in favor of Ember.later**
4+
5+
In general, it's best to avoid the use of `setTimeout` and `setInterval` in Ember.
6+
7+
From the Ember documentation:
8+
9+
> You should use this method whenever you need to run some action after a period of time instead of
10+
using setTimeout(). This method will ensure that items that expire during the same script execution
11+
cycle all execute together, which is often more efficient than using a real setTimeout.
12+
13+
While `setTimeout` maps to `Ember.run.later`, there isn't a direct equivalent of `setInterval`. You
14+
can accomplish the rough equivalent of `setInterval` by using a recursive `setTimeout`.
15+
16+
Example:
17+
18+
```js
19+
Ember.Component.extend({
20+
init() {
21+
this._super(...arguments);
22+
this.intervalToken = this.schedule(() => alert('Alert'));
23+
this.interval = 1000;
24+
}
25+
26+
schedule(fn) {
27+
return Ember.run.later(() => {
28+
fn()
29+
this.set('intervalToken', this.schedule(fn));
30+
}, this.interval);
31+
}
32+
33+
willDestroy() {
34+
this._super(...arguments);
35+
Ember.run.cancel(this.intervalToken);
36+
}
37+
});
38+
```

guides/rules/no-unguarded-document.md

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
# No Unguarded Document References
2+
3+
**TL;DR: Ensure that all document references are guarded inside an if block with the condition `environment.isBrowser()`**
4+
5+
Unguarded document references will break when used with FastBoot. This is due to the Node environment
6+
not having a global `document` object.
7+
8+
To protect against errors during server side rendering, you should ensure you're guarding the usages
9+
of `document` with a check to `environment.isBrowser()`.
10+
11+
Example:
12+
13+
```js
14+
import Ember from 'ember';
15+
16+
Ember.component.extend({
17+
init() {
18+
if (environment.isBrowser()) {
19+
this.element = document.querySelector('.my-element');
20+
}
21+
}
22+
});
23+
```

lib/rules/no-unguarded-document.js

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
const { isGuarded } = require('../utils/fast-boot');
2+
3+
const MESSAGE = 'Do not use unguarded document references. Please see the following guide for more infromation: http://github.com/chadhietala/ember-best-practices/files/guides/no-unguarded-document.md';
4+
5+
function isDocumentReference(node) {
6+
return node.callee.object.name === 'document';
7+
}
8+
9+
module.exports = {
10+
meta: {
11+
message: MESSAGE
12+
},
13+
create(context) {
14+
return {
15+
CallExpression(node) {
16+
if (isDocumentReference(node) && !isGuarded(node)) {
17+
context.report(node, MESSAGE);
18+
}
19+
}
20+
};
21+
}
22+
};
Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
const rule = require('../../../lib/rules/no-unguarded-document');
2+
const MESSAGE = rule.meta.message;
3+
const RuleTester = require('eslint').RuleTester;
4+
const ruleTester = new RuleTester();
5+
6+
ruleTester.run('no-timers', rule, {
7+
valid: [
8+
{
9+
code: `
10+
export default Ember.Component.extend({
11+
actions: {
12+
foo() {
13+
if (environment.isBrowser()) {
14+
const node = document.querySelector('blah');
15+
}
16+
}
17+
}
18+
});`,
19+
parserOptions: {
20+
ecmaVersion: 6,
21+
sourceType: 'module'
22+
}
23+
},
24+
{
25+
code: `
26+
export default Ember.Component.extend({
27+
guarded() {
28+
if (environment.isBrowser()) {
29+
const node = document.querySelector('blah');
30+
}
31+
}
32+
});`,
33+
parserOptions: {
34+
ecmaVersion: 6,
35+
sourceType: 'module'
36+
}
37+
}
38+
],
39+
invalid: [
40+
{
41+
code: `
42+
export default Ember.Component({
43+
actions: {
44+
bar() {
45+
const node = document.querySelector('blah');
46+
}
47+
}
48+
});`,
49+
parserOptions: {
50+
ecmaVersion: 6,
51+
sourceType: 'module'
52+
},
53+
errors: [{
54+
message: MESSAGE
55+
}]
56+
},
57+
{
58+
code: `
59+
export default Ember.Component({
60+
baz() {
61+
const node = document.querySelector('blah');
62+
}
63+
});`,
64+
parserOptions: {
65+
ecmaVersion: 6,
66+
sourceType: 'module'
67+
},
68+
errors: [{
69+
message: MESSAGE
70+
}]
71+
}
72+
]
73+
});

0 commit comments

Comments
 (0)