In an Ember app, I personally dislike seeing inconsistent placement of the @action
decorator. For example, some may write it like this:
class MyComponent extends Component {
@action foo() { // do stuff
}
}
and others may write it like this:
class MyComponent extends Component {
@action foo() { // do stuff
}
}
I figured an ESLint rule would be a perfect way to solve this. However, I wasn't able to find an ESLint plugin that worked. Therefore, I decided to write one myself to ensure the @action
decorator wasn't placed on the same line as the method.
If you've never written a custom ESLint rule, I recommend going through the Abstract Syntax Forestry workshop by Simplabs for EmberConf2020.
I started by creating an ESLint plugin in my Ember app under lib/eslint-plugin-acme
:
{
"name": "eslint-plugin-acme",
"description": "Custom ESLint rules",
"main": "index.js"
}
Next, I created the index.js
file that is referenced under the main
key in the above code. In this file, I declared my new ESLint rule which I called no-inline-action
.
module.exports = {
rules: {
"no-inline-action": require("./no-inline-action"),
},
};
I then created lib/eslint-plugin-acme/no-inline-action.js
. The code below checks to see if the placement of the @action
decorator is on the same line as the method, and if it is, it'll publish a warning or error (depending on the configuration being used).
module.exports = {
create(context) {
const sourceCode = context.getSourceCode();
return {
Decorator(node) {
const { name } = node.expression;
if (name === "action" && node.parent.type === "MethodDefinition") {
const actionDecoratorTokens = sourceCode.getTokens(node);
const methodToken = sourceCode.getTokenAfter(node);
const actionDecoratorStartLine =
actionDecoratorTokens[1].loc.start.line;
const methodStartLine = methodToken.loc.start.line;
if (actionDecoratorStartLine === methodStartLine) {
context.report({
node,
message: `
The @action decorator and ${methodToken.value} method
are on the same line. Place the @action decorator
above the method declaration.
`.trim(),
});
}
}
},
};
},
};
At the time when I wrote this ESLint rule, I noticed that the AST wasn't taking into account spaces and thus the line number of the decorator and the line number of the method weren't what I expected. Thus, I had to use the context
object to access the source code to see if the line numbers were the same or not. To be honest, I don't know if this had to do with the specific Ember app I was working in or what I stated previously is how the AST parsing typically works.
To wire up my custom ESLint plugin to the Ember app, I referenced this local package from the Ember app's package.json
. I have intentionally left out all of other package.json
keys in the code below so it is easier to understand.
{
"devDependencies": {
"eslint-plugin-acme": "file:lib/eslint-plugin-acme"
},
}
Lastly, I declared how I wanted my new rule to be reported in .eslintrc.js
.
module.exports = {
plugins: ["ember", "acme"],
rules: {
"acme/no-inline-action": "error",
},
};