Skip to content

Commit 66aae10

Browse files
authored
Merge pull request #691 from nlfurniss/no-mixins-rule
Add new rule `no-mixins`
2 parents e1dff32 + ccfa44d commit 66aae10

File tree

7 files changed

+179
-0
lines changed

7 files changed

+179
-0
lines changed

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,7 @@ Each rule has emojis denoting what configuration it belongs to and/or a :wrench:
7373
| :car::wrench: | [no-get](./docs/rules/no-get.md) | require using ES5 getters instead of Ember's `get` / `getProperties` functions |
7474
| :white_check_mark: | [no-global-jquery](./docs/rules/no-global-jquery.md) | disallow usage of global jQuery object |
7575
| :car: | [no-jquery](./docs/rules/no-jquery.md) | disallow any usage of jQuery |
76+
| | [no-mixins](./docs/rules/no-mixins.md) | disallow the usage of mixins |
7677
| :white_check_mark: | [no-new-mixins](./docs/rules/no-new-mixins.md) | disallow the creation of new mixins |
7778
| :white_check_mark: | [no-observers](./docs/rules/no-observers.md) | disallow usage of observers |
7879
| :white_check_mark::wrench: | [no-old-shims](./docs/rules/no-old-shims.md) | disallow usage of old shims for modules |

docs/rules/no-mixins.md

Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
# no-mixins
2+
3+
Using mixins to share code appears easy at first. But they add significant complexity as a project grows. Furthermore, the [Octane programming model](https://guides.emberjs.com/release/upgrading/current-edition/) eliminates the need to use them in favor of native class semantics and other primitives.
4+
5+
For practical strategies on removing mixins see [this discourse thread](https://discuss.emberjs.com/t/best-way-to-replace-mixins/17395/2).
6+
7+
## Rule Details
8+
9+
This rule disallows importing a mixin. This is different than [no-new-mixins](no-new-mixins.md), which disallows creating a mixin (e.g. `Mixin.create({})`). Both should be used in an app that wants to disallow the use of mixins.
10+
11+
## Examples
12+
13+
Examples of **incorrect** code for this rule:
14+
15+
```javascript
16+
// my-octane-component.js
17+
import Component from '@ember/component';
18+
import FooMixin from '../utils/mixins/foo';
19+
20+
export default class FooComponent extends Component.extend(FooMixin) {
21+
// ...
22+
}
23+
```
24+
25+
```javascript
26+
// my-component.js
27+
import myMixin from 'my-mixin';
28+
29+
export default Component.extend(myMixin, {
30+
aComputedProperty: computed('obj', function() {
31+
return this.isValidClassName(obj.className);
32+
})
33+
});
34+
```
35+
36+
Examples of **correct** code for this rule:
37+
38+
```javascript
39+
// my-utils.js
40+
export function isValidClassName(classname) {
41+
return Boolean(className.match('-class'));
42+
}
43+
44+
export function hideModal(obj, value) {
45+
set(obj, 'isHidden', value);
46+
}
47+
```
48+
49+
```javascript
50+
// my-component.js
51+
import { isValidClassName } from 'my-utils';
52+
53+
export default Component.extend({
54+
aComputedProperty: computed('obj', function() {
55+
return isValidClassName(get(obj, 'className'));
56+
})
57+
});
58+
```
59+
60+
## References
61+
62+
* [Mixins Considered Harmful](https://reactjs.org/blog/2016/07/13/mixins-considered-harmful.html)
63+
* [Why Are Mixins Considered Harmful?](http://raganwald.com/2016/07/16/why-are-mixins-considered-harmful.html)
64+
65+
## Related Rules
66+
67+
* [no-new-mixins](no-new-mixins.md)

docs/rules/no-new-mixins.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,3 +60,7 @@ export default Component.extend({
6060
})
6161
});
6262
```
63+
64+
## Related Rules
65+
66+
* [no-mixins](no-mixins.md)

lib/index.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@ module.exports = {
3535
'no-invalid-debug-function-arguments': require('./rules/no-invalid-debug-function-arguments'),
3636
'no-jquery': require('./rules/no-jquery'),
3737
'no-legacy-test-waiters': require('./rules/no-legacy-test-waiters'),
38+
'no-mixins': require('./rules/no-mixins'),
3839
'no-new-mixins': require('./rules/no-new-mixins'),
3940
'no-observers': require('./rules/no-observers'),
4041
'no-old-shims': require('./rules/no-old-shims'),

lib/recommended-rules.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@ module.exports = {
3838
"ember/no-invalid-debug-function-arguments": "error",
3939
"ember/no-jquery": "off",
4040
"ember/no-legacy-test-waiters": "off",
41+
"ember/no-mixins": "off",
4142
"ember/no-new-mixins": "error",
4243
"ember/no-observers": "error",
4344
"ember/no-old-shims": "error",

lib/rules/no-mixins.js

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
'use strict';
2+
3+
//------------------------------------------------------------------------------
4+
// General rule - Don't use mixins
5+
//------------------------------------------------------------------------------
6+
7+
const ERROR_MESSAGE = "Don't use a mixin";
8+
const mixinPathRegex = /\/mixins\//;
9+
10+
module.exports = {
11+
meta: {
12+
type: 'suggestion',
13+
docs: {
14+
description: 'disallow the usage of mixins',
15+
category: 'Best Practices',
16+
recommended: false,
17+
url: 'https://github.com/ember-cli/eslint-plugin-ember/tree/master/docs/rules/no-mixins.md',
18+
},
19+
fixable: null,
20+
schema: [],
21+
},
22+
23+
ERROR_MESSAGE,
24+
25+
create(context) {
26+
return {
27+
ImportDeclaration(node) {
28+
const importPath = node.source.value;
29+
if (importPath.match(mixinPathRegex)) {
30+
context.report(node, ERROR_MESSAGE);
31+
}
32+
},
33+
};
34+
},
35+
};

tests/lib/rules/no-mixins.js

Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
// ------------------------------------------------------------------------------
2+
// Requirements
3+
// ------------------------------------------------------------------------------
4+
5+
const rule = require('../../../lib/rules/no-mixins');
6+
const RuleTester = require('eslint').RuleTester;
7+
8+
// ------------------------------------------------------------------------------
9+
// Tests
10+
// ------------------------------------------------------------------------------
11+
12+
const { ERROR_MESSAGE } = rule;
13+
const eslintTester = new RuleTester({
14+
parserOptions: { ecmaVersion: 6, sourceType: 'module' },
15+
});
16+
eslintTester.run('no-mixins', rule, {
17+
valid: [
18+
`
19+
import NoMixinRule from "some/addon/mixin";
20+
export default mixin;
21+
`,
22+
`
23+
import NameIsMixin from "some/addon";
24+
export default Component.extend({});
25+
`,
26+
`
27+
import * as Mixins from "some/addon/mixins";
28+
export default Component.extend({});
29+
`,
30+
`
31+
import Mixin from '@ember/object/mixin';
32+
const newMixin = Mixin.create({});
33+
`,
34+
],
35+
invalid: [
36+
{
37+
code: `
38+
import BadCode from "my-addon/mixins/bad-code";
39+
40+
export default Component.extend(BadCode, {});
41+
`,
42+
output: null,
43+
errors: [{ message: ERROR_MESSAGE, type: 'ImportDeclaration' }],
44+
},
45+
{
46+
code: `
47+
import EmberObject from '@ember/object';
48+
import EditableMixin from '../mixins/editable';
49+
50+
const Comment = EmberObject.extend(EditableMixin, {
51+
post: null
52+
});
53+
`,
54+
output: null,
55+
errors: [{ message: ERROR_MESSAGE, type: 'ImportDeclaration' }],
56+
},
57+
{
58+
code: `
59+
import Component from '@ember/component';
60+
import FooMixin from '../utils/mixins/foo';
61+
62+
export default class FooComponent extends Component.extend(FooMixin) {
63+
// ...
64+
}
65+
`,
66+
output: null,
67+
errors: [{ message: ERROR_MESSAGE, type: 'ImportDeclaration' }],
68+
},
69+
],
70+
});

0 commit comments

Comments
 (0)