If you are from Javascript/Typescript background then you must be familiar with Linters. The soldiers that work tirelessly to keep everyone in the team on same page (I mean code styling wise). There are lot of useful ESlint rules are out there to be consumed but since every project is a unique beautiful snowflake ❄️, the styling demands can be very specific to individual projects. To adhere these custom styling demands, we need to create custom eslint rules and that is what we are going to do today.
For those who have directly came to this blog, I would like to tell you that this is the third blog in the series of “Abstract Syntax Tree” which you can read to gain more insights on how code analysers work and some open-source tools for this kind of analysis. But if you only want to know just how to write eslint rules? then reading this blog will be suffice.
So let’s start-
Assume that there is code convention that everyone should follow in the team which is "The name of the variables that are used to declare and define constant values must be capitalise to avoid confusion from other kind of variables".
But all of us know that just telling this to the team members is not enough. You have to always be ready for some human errors. To tackle that we will write a custom eslint rule to produce a warning if such a case is found.
A very basic rule can be created by writing an npm package with only two files. A package.json file and a javascript file that contains the actual logic behind the rule. You can also use ESLint generator for Yeoman, for rapid bootstrap and better directory structure (best practices stuff😎). But in this post we will use the former approach i.e. creating eslint-rule with without using Yeoman.
To create an eslint-rule, we have to create something called an eslint plugin. Eslint plugin is nothing but a node module whose name starts with eslint-plugin(note that this is by convention, you don’t have much of an option here 🤷). So if your plugin is called awesome-rules then your node module should be called eslint-plugin-awesome-rules. Now create an directory called awesome-rules and under that create a file called package.json with content below-
As you already know that how package.json works in a NodeJs, I don’t think I have to explain anything here. Now create another file in the same folder with name index.js. Note that this is the main file where actual rule logic exist.
Above file contains the logic of an eslint plugin with only one rule which is constant capitalization. Each rule in a plugin contains two mandatory properties - ‘meta’ and ‘create’.
As the name implies ‘meta’ contain generic information of the rule such as type of the rule (this is displayed when you run eslint command), also a little description to tell what this rule is about. There are other fields too but here in this post I am just covering a very basic eslint-rule. You can read more about different properties of meta from official eslint website.
Another field here is called create, it's a visitor function that will visit the whole code’s syntax tree node by node and will let us operate on node. As you can see it has a context object which provides us contextual information about the code. We will use report function of this object to publish rules applicability information (on running eslint command). You can read more on this here.
Now back to our capitalisation rule, As we know we have to find a variable declaration of kind const with a Literal value and where the node’s name is lower cased. For that we can visit VariableDeclarator node with node.parent.kind === 'const' && hasLowerCase(node.id.name) && node.init.type === ‘Literal'
condition.
Trick: I use console logger to print full node to know on what properties I can put my check. In my case running lint rule against const abc=123
gives below output:
Node {
type: 'VariableDeclarator',
start: 6,
end: 15,
loc: SourceLocation {
start: Position { line: 1, column: 6 },
end: Position { line: 1, column: 15 }
},
range: [ 6, 15 ],
id: Node {
type: 'Identifier',
start: 6,
end: 9,
loc: SourceLocation { start: [Position], end: [Position] },
range: [ 6, 9 ],
name: 'abc',
parent: [Circular *1]
},
init: Node {
type: 'Literal',
start: 12,
end: 15,
loc: SourceLocation { start: [Position], end: [Position] },
range: [ 12, 15 ],
value: 123,
raw: '123',
parent: [Circular *1]
},
parent: Node {
type: 'VariableDeclaration',
start: 0,
end: 16,
loc: SourceLocation { start: [Position], end: [Position] },
range: [ 0, 16 ],
declarations: [ [Circular *1] ],
kind: 'const',
parent: Node {
type: 'Program',
start: 0,
end: 16,
loc: [SourceLocation],
range: [Array],
body: [Array],
sourceType: 'module',
comments: [],
tokens: [Array],
parent: null
}
}
}
After performing above steps our plugin is complete. Now it’s time to test this plugin in some sample project. For that create some random NodeJs project. In my case I’ve created awesome-plugin-sample. In this project install eslint and configure eslint. Now to test locally we need to link our eslint-plugin module. For that goto terminal, navigate to awesome-rules plugin project directory and run yarn link
command there (if you are using npm, you can find similar commands). Running this command should give below similar output-
Now goto you sample project directory and type-
yarn link eslint-plugin-awesome-rules
This will link your plugin module to current project, so whenever you use ‘eslint-plugin-awesome-rules’ module it will use the local copy of your module. Now run below command to add package.json dependency for your module. Note that there is a link keyword that tells yarn that it is a linked module.
yarn add eslint-plugin-awesome-rules@link:1.0.0
After this open .eslintrc file in the sample project and add the plugin which we created just now and then add a rule which is constant captalization. See below-
Now create an actual code file (like src/index.js) and for just testing purpose put below line of code.
const abc = 123;
Add package.json script-
And run yarn lint
, It should give you the below output-
yarn run v1.21.1
$ eslint src/**/*
/Users/XXXX/Code/custom-eslint-rules/awesome-rule-sample/src/index.js
1:7 warning Please use capitalized casing for constants awesome-rules/constant-capitalization
1:7 error 'abc' is assigned a value but never used no-unused-vars
1:17 error Newline required at end of file but not found eol-last
✖ 3 problems (2 errors, 1 warning)
1 error and 0 warnings potentially fixable with the `--fix` option.
error Command failed with exit code 1.
info Visit https://yarnpkg.com/en/docs/cli/run for documentation about this command.
And tada!! Our very simple eslint-plugin for custom-eslint-rule is done. You can use this technique to even write very complex rules, I have just touched the tip of the iceberg here. You can read more about all the properties and functions available for writing eslint rule on the official website.
So what’s next ??
As we have seen a lot of analysis, on the fly code fix and a bit of lint-ing using AST. So next we will find out how we can write our own dynamic code generators using AST and NodeJs. To find other blogs in the series follow below links -
- (Prev) Into the world of Abstract Syntax Tree
- (Prev) Writing a Python Slayer in Javascript using AST
- (Next) Dynamically generating code using AST
And yeah for any comment or feedback, please use my in-house serverless comment system.
…Over and Out.
Attributions:
Cover image by Joshua Miranda from Pexels