A ESLint rule contains 2 main parts:

However, before doing all that, let's start by addressing the meta section of our rule.

To build the ESLint rule, we need to get the represention of the expression CHECK && yup.validateSync(); in a AST and let the create function return an error everytime the tree for the given expression does not match the valid tree. To find the AST representation of our expression you can use AST Explorer , which was very helpful for me.

an AST is simplified and condensed tree representation of the structure of source code written in a given programming language. It is "abstract" as it does not represent every detail appearing in the real syntax but just the content or structural details.

You might have seen or heard about ASTs in the past but here's a definition just in case:

We can see above that we've added 2 important fields: messages and docs. The string under messages.unexpected is the message that will be displayed when the rule will fail. The one under docs.description provides a short description of the rule which can be display by some text editors like VSCode.

Let's start by adding the basic structure of our rule and the meta to check-before-type-validation.js

Create

For this part, let's first go to AST explorer and write our statement to see how it translates into AST. By entering CHECK && yup.validateSync() we should get the following output:

AST representation of our expression json Copy 1 { 2 "type" : "Program" , 3 "start" : 0 , 4 "end" : 27 , 5 "body" : [ 6 { 7 "type" : "ExpressionStatement" , 8 "start" : 0 , 9 "end" : 27 , 10 "expression" : { 11 "type" : "LogicalExpression" , 12 "start" : 0 , 13 "end" : 27 , 14 "left" : { 15 "type" : "Identifier" , 16 "start" : 0 , 17 "end" : 5 , 18 "name" : "CHECK" 19 } , 20 "operator" : "&&" , 21 "right" : { 22 "type" : "CallExpression" , 23 "start" : 9 , 24 "end" : 27 , 25 "callee" : { 26 "type" : "MemberExpression" , 27 "start" : 9 , 28 "end" : 25 , 29 "object" : { 30 "type" : "Identifier" , 31 "start" : 9 , 32 "end" : 12 , 33 "name" : "yup" 34 } , 35 "property" : { 36 "type" : "Identifier" , 37 "start" : 13 , 38 "end" : 25 , 39 "name" : "validateSync" 40 } , 41 "computed" : false 42 } , 43 "arguments" : [ ] 44 } 45 } 46 } 47 ] , 48 "sourceType" : "module" 49 }

Note: You can check the resulting tree yourself here.

To write our rule, we can start by highlighting yup.validateSync() . We see from the AST tree that this expression is a CallExpression :

Here we're highlighting the yup.validateSync() expression to see its AST equivalent

We'll first need ESLint to find that specific node with the object name yup and a property name validateSync in a CallExpression . If found, we can check one of the parents of that node to see if CHECK && is present. Hence, we can start by writing the following code:

Writing the rule (step 1) javascript Copy 1 create : function ( context ) { 2 return { 3 4 CallExpression : function ( node ) { 5 const callee = node . callee ; 6 7 if ( 8 callee . object && 9 callee . object . name === 'yup' && 10 callee . property && 11 callee . property . name === 'validateSync' 12 ) { 13 14 } 15 } 16 } 17 }

The next part of the AST tree that we're looking for is a LogicalExpression . We can see from the screenshot above that it's present 2 levels up the tree. We can deduct from this that if this parent were not to be a LogicalExpression , our ESLint rule should report an error. We can then continue writing our code snippet above by adding the following:

Writing the rule (step 2) javascript Copy 1 if ( 2 callee . object && 3 callee . object . name === 'yup' && 4 callee . property && 5 callee . property . name === 'validateSync' 6 ) { 7 8 9 const calleeLogicalExpression = callee . parent . parent ; 10 11 if ( calleeLogicalExpression . type !== 'LogicalExpression' ) { 12 13 14 15 context . report ( { node , messageId : 'unexpected' } ) ; 16 } 17 }

As you can see above, in order to have ESLint reporting the error, we need to call the context.report function. We pass the messageId that we specified in the meta of our rule instead of typing the full message as it is advised in the ESLint documentation.

Next, we have to check that if it is a LogicalExpression the operator of that expression is actually a "AND" and not a "OR":

Writing the rule (step 3) javascript Copy 1 if ( 2 callee . object && 3 callee . object . name === 'yup' && 4 callee . property && 5 callee . property . name === 'validateSync' 6 ) { 7 8 9 const calleeLogicalExpression = callee . parent . parent ; 10 11 if ( calleeLogicalExpression . type !== 'LogicalExpression' ) { 12 13 14 15 context . report ( { node , messageId : 'unexpected' } ) ; 16 } else { 17 18 19 if ( calleeLogicalExpression . operator !== '&&' ) { 20 context . report ( { node , messageId : 'unexpected' } ) ; 21 } 22 } 23 }

With this code our ESLint rule will report an error for the following:

1 yup . validateSync ( ) ; 2 CHECK || yup . validateSync ( ) ;

However if we have something like the following:

1 TEST && yup . validateSync ( ) ;

our rule will not catch any error. So let's go back to our AST tree to see what we can do here. We can see that a LogicalExpression has 3 main parts:

the left part: CHECK

the operator: && or ||

or the right right: yup.validateSync()

so for the last part of our rule we want to check whether the name of the left part of our LogicalExpression is CHECK :