Skip to content

Commit

Permalink
Add 'no-actions-hash' rule
Browse files Browse the repository at this point in the history
  • Loading branch information
laurmurclar committed Oct 30, 2019
1 parent 074e7d5 commit d36bcbc
Show file tree
Hide file tree
Showing 7 changed files with 237 additions and 0 deletions.
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -114,6 +114,7 @@ The `--fix` option on the command line automatically fixes problems reported by
| | [classic-decorator-no-classic-methods](./docs/rules/classic-decorator-no-classic-methods.md) | Prevent usage of classic APIs such as get/set in classes that aren't explicitly decorated with @classic |
| | [computed-property-getters](./docs/rules/computed-property-getters.md) | Enforce the consistent use of getters in computed properties |
| | [no-proxies](./docs/rules/no-proxies.md) | Disallows using array or object proxies |
| | [no-actions-hash](./docs/rules/no-actions-hash.md) | Disallows the actions hash in components, controllers and routes |


### Possible Errors
Expand Down
48 changes: 48 additions & 0 deletions docs/rules/no-actions-hash.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
# Disallows the actions hash in components and controllers (no-actions-hash)

Ember Octane includes a rethink of event handling in Ember. The `actions` hash and `{{action}}` modifier and helper are no longer needed. To provide the correct context to functions (binding), you should now use the `@action` decorator. In templates, the `{{on}}` modifier can be used to set up event handlers and the `{{fn}}` helper can be used for partial application.


## Rule Detail

Use the `@action` decorator or `foo: action(function() {}))` syntax instead of an `actions` hash.

Examples of **incorrect** code for this rule:

```js
// Bad
export default Component.extend({
actions: {
foo() {
}
},
});

export class MyComponent extends Component {
actions = {
foo() {
}
}
}
```

Examples of **correct** code for this rule:

```js
// Good
export default Component.extend({
foo: action(function() {
})
});

export class MyComponent extends Component {
@action
foo() {
}
}
```

## Further Reading
- [`{{on}}` Modifier RFC](https://github.com/emberjs/rfcs/pull/471)
- [`{{fn}}` Helper RFC](https://github.com/emberjs/rfcs/pull/470)
- [Ember Octane Update: What's up with `@action`?](https://www.pzuraq.com/ember-octane-update-action/)
1 change: 1 addition & 0 deletions lib/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ module.exports = {
'jquery-ember-run': require('./rules/jquery-ember-run'),
'named-functions-in-promises': require('./rules/named-functions-in-promises'),
'new-module-imports': require('./rules/new-module-imports'),
'no-actions-hash': require('./rules/no-actions-hash'),
'no-arrow-function-computed-properties': require('./rules/no-arrow-function-computed-properties'),
'no-attrs-in-components': require('./rules/no-attrs-in-components'),
'no-attrs-snapshot': require('./rules/no-attrs-snapshot'),
Expand Down
1 change: 1 addition & 0 deletions lib/recommended-rules.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ module.exports = {
"ember/jquery-ember-run": "error",
"ember/named-functions-in-promises": "off",
"ember/new-module-imports": "error",
"ember/no-actions-hash": "off",
"ember/no-arrow-function-computed-properties": "error",
"ember/no-attrs-in-components": "error",
"ember/no-attrs-snapshot": "error",
Expand Down
60 changes: 60 additions & 0 deletions lib/rules/no-actions-hash.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
const ember = require('../utils/ember');

const ERROR_MESSAGE = 'Use the @action decorator instead of declaring an actions hash';

module.exports = {
ERROR_MESSAGE,
meta: {
docs: {
description: 'Disallows the actions hash in components, controllers and routes',
category: 'Ember Object',
recommended: false,
},
fixable: null, // or "code" or "whitespace"
url:
'https://github.com/ember-cli/eslint-plugin-ember/tree/master/docs/rules/no-actions-hash.md',
},

create: context => {
const filePath = context.getFilename();
let inClassWhichCanContainActions = false;

function _inClassWhichCanContainActions(node, filePath) {
return (
inClassWhichCanContainActions ||
ember.isEmberComponent(node, filePath) ||
ember.isEmberController(node, filePath) ||
ember.isEmberRoute(node, filePath)
);
}

return {
ClassDeclaration(node) {
inClassWhichCanContainActions = _inClassWhichCanContainActions(node, filePath);
},
CallExpression(node) {
inClassWhichCanContainActions = _inClassWhichCanContainActions(node, filePath);
},
ObjectExpression(node) {
if (!inClassWhichCanContainActions) {
return;
}

node.properties.forEach(property => {
if (property.key.name === 'actions') {
context.report(node, ERROR_MESSAGE);
}
});
},
ClassProperty(node) {
if (!inClassWhichCanContainActions) {
return;
}

if (node.value.type === 'ObjectExpression' && node.key.name === 'actions') {
context.report(node, ERROR_MESSAGE);
}
},
};
},
};
1 change: 1 addition & 0 deletions lib/utils/ember.js
Original file line number Diff line number Diff line change
Expand Up @@ -137,6 +137,7 @@ function isTestFile(fileName) {

function isEmberObject(node) {
return (
node.callee &&
node.callee.property &&
(node.callee.property.name === 'extend' || node.callee.property.value === 'extend')
);
Expand Down
125 changes: 125 additions & 0 deletions tests/lib/rules/no-actions-hash.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,125 @@
//------------------------------------------------------------------------------
// Requirements
//------------------------------------------------------------------------------

const rule = require('../../../lib/rules/no-actions-hash');
const RuleTester = require('eslint').RuleTester;

const { ERROR_MESSAGE } = rule;

//------------------------------------------------------------------------------
// Tests
//------------------------------------------------------------------------------

const ruleTester = new RuleTester({
parser: require.resolve('babel-eslint'),
parserOptions: { ecmaVersion: 6, sourceType: 'module' },
});
ruleTester.run('no-actions-hash', rule, {
valid: [
`
export default Component.extend({
foo: action(function() {})
});
`,
`
export default class MyComponent extends Component {
@action
foo() {}
}
`,
`
export default Controller.extend({
foo: action(function() {})
});
`,
`
export default class MyController extends Controller {
@action
foo() {}
}
`,
`
export default Route.extend({
foo: action(function() {})
});
`,
`
export default class MyRoute extends Route {
@action
foo() {}
}
`,
`
export default class MyLovelyClass extends LovelyClass {
actions = {
foo() {
}
}
}
`,
],

invalid: [
{
code: `
export default Component.extend({
actions: {
},
});
`,
errors: [{ message: ERROR_MESSAGE }],
},
{
code: `
export default class MyComponent extends Component {
actions = {
foo() {
}
}
}
`,
errors: [{ message: ERROR_MESSAGE }],
},
{
code: `
export default Controller.extend({
actions: {
},
});
`,
errors: [{ message: ERROR_MESSAGE }],
},
{
code: `
export default class MyController extends Controller {
actions = {
foo() {
}
}
}
`,
errors: [{ message: ERROR_MESSAGE }],
},
{
code: `
export default Route.extend({
actions: {
},
});
`,
errors: [{ message: ERROR_MESSAGE }],
},
{
code: `
export default class MyRoute extends Route {
actions = {
foo() {
}
}
}
`,
errors: [{ message: ERROR_MESSAGE }],
},
],
});

0 comments on commit d36bcbc

Please sign in to comment.