惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

Cloudbric
Cloudbric
T
Threat Research - Cisco Blogs
Simon Willison's Weblog
Simon Willison's Weblog
AWS News Blog
AWS News Blog
P
Privacy & Cybersecurity Law Blog
H
Help Net Security
云风的 BLOG
云风的 BLOG
G
GRAHAM CLULEY
Spread Privacy
Spread Privacy
Cyber Security Advisories - MS-ISAC
Cyber Security Advisories - MS-ISAC
A
Arctic Wolf
Project Zero
Project Zero
Engineering at Meta
Engineering at Meta
P
Privacy International News Feed
Blog — PlanetScale
Blog — PlanetScale
Stack Overflow Blog
Stack Overflow Blog
M
MIT News - Artificial intelligence
The Register - Security
The Register - Security
Recorded Future
Recorded Future
cs.AI updates on arXiv.org
cs.AI updates on arXiv.org
C
Cisco Blogs
PCI Perspectives
PCI Perspectives
Recent Announcements
Recent Announcements
Martin Fowler
Martin Fowler
A
About on SuperTechFans
W
WeLiveSecurity
GbyAI
GbyAI
V
Vulnerabilities – Threatpost
The GitHub Blog
The GitHub Blog
D
Darknet – Hacking Tools, Hacker News & Cyber Security
C
Check Point Blog
Y
Y Combinator Blog
月光博客
月光博客
Scott Helme
Scott Helme
奇客Solidot–传递最新科技情报
奇客Solidot–传递最新科技情报
Google DeepMind News
Google DeepMind News
F
Fortinet All Blogs
U
Unit 42
G
Google Developers Blog
钛媒体:引领未来商业与生活新知
钛媒体:引领未来商业与生活新知
T
Threatpost
Application and Cybersecurity Blog
Application and Cybersecurity Blog
Google Online Security Blog
Google Online Security Blog
Recent Commits to openclaw:main
Recent Commits to openclaw:main
OSCHINA 社区最新新闻
OSCHINA 社区最新新闻
Cisco Talos Blog
Cisco Talos Blog
博客园 - 三生石上(FineUI控件)
Hugging Face - Blog
Hugging Face - Blog
MongoDB | Blog
MongoDB | Blog
博客园 - 司徒正美

Maxime Heckel's Blog

On Rendering the Sky, Sunsets, and Planets - The Blog of Maxime Heckel Shades of Halftone - The Blog of Maxime Heckel Field Guide to TSL and WebGPU - The Blog of Maxime Heckel On Shaping Light: Real-Time Volumetric Lighting with Post-Processing and Raymarching for the Web - The Blog of Maxime Heckel Speaking at Figma Config 2025 - The Blog of Maxime Heckel Post-Processing Shaders as a Creative Medium - The Blog of Maxime Heckel On Crafting Painterly Shaders - The Blog of Maxime Heckel The Art of Dithering and Retro Shading for the Web - The Blog of Maxime Heckel Moebius-style post-processing and other stylized shaders - The Blog of Maxime Heckel Shining a light on Caustics with Shaders and React Three Fiber - The Blog of Maxime Heckel Real-time dreamy Cloudscapes with Volumetric Raymarching - The Blog of Maxime Heckel Painting with Math: A Gentle Study of Raymarching - The Blog of Maxime Heckel Building a magical AI-powered semantic search from scratch - The Blog of Maxime Heckel Beautiful and mind-bending effects with WebGL Render Targets - The Blog of Maxime Heckel Refraction, dispersion, and other shader light effects - The Blog of Maxime Heckel The magical world of Particles with React Three Fiber and Shaders - The Blog of Maxime Heckel The Study of Shaders with React Three Fiber - The Blog of Maxime Heckel Building a Design System from scratch - The Blog of Maxime Heckel Everything about Framer Motion layout animations - The Blog of Maxime Heckel Building a Vaporwave scene with Three.js - The Blog of Maxime Heckel Cubic Bézier: from math to motion - The Blog of Maxime Heckel First steps with GPT-3 for frontend developers - The Blog of Maxime Heckel Building the perfect GitHub CI workflow for your frontend team - The Blog of Maxime Heckel Migrating to Next.js - The Blog of Maxime Heckel Static Tweets with MDX and Next.js - The Blog of Maxime Heckel Advanced animation patterns with Framer Motion - The Blog of Maxime Heckel Scrollspy demystified - The Blog of Maxime Heckel The Power of Composition with CSS Variables - The Blog of Maxime Heckel My first failed SwiftUI project - The Blog of Maxime Heckel Guide to creating animations that spark joy with Framer Motion - The Blog of Maxime Heckel Using Shortcuts and serverless to build a personal Apple Health API - The Blog of Maxime Heckel SEO mistakes I've made and how I fixed them - The Blog of Maxime Heckel Going native: SwiftUI from the perspective of a React developer - The Blog of Maxime Heckel Build your own preview deployment service - The Blog of Maxime Heckel The little guide to CI/CD for frontend developers - The Blog of Maxime Heckel Immigrating to the US - The Blog of Maxime Heckel The physics behind spring animations - The Blog of Maxime Heckel Generate screenshots of your code with a serverless function - The Blog of Maxime Heckel How to use Framer Motion with Emotion styled-components - The Blog of Maxime Heckel Data Fetching with NextJS: What I learned - The Blog of Maxime Heckel Learning in public - The Blog of Maxime Heckel Fixing the dark mode flash issue on server rendered websites - The Blog of Maxime Heckel How to fix NPM link duplicate dependencies issues - The Blog of Maxime Heckel Running scheduled cross-browser end-to-end tests on Github CI - The Blog of Maxime Heckel React Lazy: a take on preloading views - The Blog of Maxime Heckel Automated UI accessibility testing with Cypress - The Blog of Maxime Heckel Building a GraphQL wrapper for the Docker API - The Blog of Maxime Heckel Switching off the lights - Adding dark mode to your React app - The Blog of Maxime Heckel Getting started with Typescript on Gatsby - The Blog of Maxime Heckel Rebuilding Redux with Hooks and Context - The Blog of Maxime Heckel Asynchronous rendering with React - The Blog of Maxime Heckel Using Flow generics to type generic React components - The Blog of Maxime Heckel How to efficiently type your styled-components with Flow - The Blog of Maxime Heckel How I got started with Kubernetes on GKE - The Blog of Maxime Heckel React sub-components Part 3: Whitelisting sub-components with flow - The Blog of Maxime Heckel React sub-components Part 2: Using the new Context API - The Blog of Maxime Heckel React sub-components - The Blog of Maxime Heckel Running Golang tests with Jest - The Blog of Maxime Heckel No title No title
How I built my first custom ESLint rule - The Blog of Maxime Heckel
Maxime Heckel · 2019-11-19 · via Maxime Heckel's Blog

When I work with React or more generally with Javascript, I always use ESLint for linting. Although I've been very familiar with how to use and configure this tool, I've never actually written a custom ESLint rule from scratch until recently. At first, it sounded like a daunting task, but it ended up teaching me quite a few things. This is what this article is about: how I built this specific rule and how I learned about "Abstract Syntax Tree". Let's dive in together!

A simple rule

The rule I had to implement stated the following: when using the validate method from the yup package, we want yup.validateSync() to be preceeded by CHECK &&; hence the following snippets will show an error

1

yup.validateSync() && CHECK;

and the next code snippets are valid:

1

CHECK && yup.validateSync();

1

CHECK && yup.validateSync() && SOMETHINGELSE;

Setting up our ESLint plugin

To create our custom ESLint rule, we'll need to build a ESLint plugin. Creating a ESLint plugin is similar to creating any other NPM project, except that the name of the package needs to start with eslint-plugin-. Let's create our new project from scratch and install ESLint as a dev dependency:

Commands to initialize our ESLint plugin

1

mkdir eslint-plugin-custom

When it comes to organizing the different files and folder of the project, ESLint has a standard way of doing so. For this post, we can follow what is adivised in the official documentation about working with rules, so we'll create a file called check-before-type-validation.js where we will implement our rule.

How to implement our rule

A ESLint rule contains 2 main parts:

  • meta: an object where we will specify the usage of our rule.

  • create: a function that will return an object with all the methods that ESLint will use to parse our statement. Each method returned is an AST node.

What is an AST (Abstract Syntax Tree)

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

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.

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.

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

Meta

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

Basic structure of our ESLint rule

2

'type-check-before-yup': {

5

description: '"yup.validateSync()" needs to be preceded by “CHECK &&”',

10

'"yup.validateSync()" is found but is not preceded "CHECK &&"',

13

create: function (context) {

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.

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

7

"type": "ExpressionStatement",

11

"type": "LogicalExpression",

22

"type": "CallExpression",

26

"type": "MemberExpression",

39

"name": "validateSync"

48

"sourceType": "module"

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

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)

1

create: function(context) {

4

CallExpression: function(node) {

5

const callee = node.callee;

9

callee.object.name === 'yup' &&

11

callee.property.name === 'validateSync'

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)

3

callee.object.name === 'yup' &&

5

callee.property.name === 'validateSync'

9

const calleeLogicalExpression = callee.parent.parent;

11

if (calleeLogicalExpression.type !== 'LogicalExpression') {

15

context.report({ node, messageId: 'unexpected' });

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)

3

callee.object.name === 'yup' &&

5

callee.property.name === 'validateSync'

9

const calleeLogicalExpression = callee.parent.parent;

11

if (calleeLogicalExpression.type !== 'LogicalExpression') {

15

context.report({ node, messageId: 'unexpected' });

19

if (calleeLogicalExpression.operator !== '&&') {

20

context.report({ node, messageId: 'unexpected' });

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

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 ||

  • 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:

Writing the rule (step 4)

3

callee.object.name === 'yup' &&

5

callee.property.name === 'validateSync'

9

const calleeLogicalExpression = callee.parent.parent;

11

if (calleeLogicalExpression.type !== 'LogicalExpression') {

15

context.report({ node, messageId: 'unexpected' });

16

} else if (calleeLogicalExpression.left.name !== 'TYPE_CHECK') {

17

context.report({ node, messageId: 'unexpected' });

21

if (calleeLogicalExpression.operator !== '&&') {

22

context.report({ node, messageId: 'unexpected' });

How to test our rule

Now that we wrote all the cases we want our rule to handle, it's time to test it. We're lucky, because ESLint comes with its own tool for testing rules called RuleTester. With this tool, we can specify all the cases we want to run the rule against and whether these cases are expected to pass or be reported as errors. Our test will live in tests/lib and will import the rule we just wrote in the previous part:

2

const rules = require('../../lib/check-before-type-validation');

3

const RuleTester = require('eslint').RuleTester;

5

const ruleTester = new RuleTester();

8

const errors = [{ messageId: 'unexpected' }];

10

const typeCheckRule = rules['type-check-before-yup'];

13

ruleTester.run('type-check', typeCheckRule, {

16

code: 'CHECK && yup.validateSync()',

20

code: 'yup.someOtherCommand()',

26

code: 'yup.validateSync()',

30

code: 'OTHER && yup.validateSync()',

34

code: 'CHECK || yup.validateSync()',

In the previous code snippet we can see that we're going to test our rule in 5 different cases:

  • an error is not reported if we have the statements CHECK && yup.validate or yup.someOtherCommand()

  • an error is reported if we have the following statements: yup.validateSync() (missing LogicalExpression) or OTHER && yup.validateSync (wrong left part of the LogicalExpression) or CHECK || yup.validateSync() (wrong operator).

We can then run this test with Jest or any other test runner and we should get an output similar as this:

5

✓ OTHER && CHECK && yup.validateSync() (45ms)

7

✓ CHECK && yup.validateSync() (3ms)

9

✓ yup.someOtherCommand() (1ms)

13

✓ yup.validateSync() (3ms)

15

✓ OTHER && yup.validateSync() (1ms)

17

✓ CHECK || yup.validateSync() (2ms)

Now that we've ensured that the rule is working as expected, we can publish it as an NPM package and add it as a plugin to any ESLint configuration we want.

This whole process might seem like a lot at first, especially since it involves dealing with AST which isn't the most accessible thing to learn. But, now that we know what the anatomy of an ESLint rule is, we can appreciate even more the insane amount of work done by the community to provide us with all this linting rules that we're using on a day to day basis to make our codebase cleaner and more consistent.