Behind the ESLint Architecture

Following our last post, we will discuss the architecture of ESLint. Architectural views, styles and design patterns will be discussed. Also, deployment and non-functional properties will be covered. This post gives an overview of the architecture of ESLint.

Relevant architectural views

In the book, Software Systems Architecture 1, Rozanski and Woods describe the concept of using viewpoints and views to aid in the architectural design process. For basic systems, it is possible to create a single architectural design for the complete system. However, for larger complex systems, this is not feasible. You can, however, model smaller parts of the system which together describe the architecture of the complete system. This is what Rozanski and Woods called an architecture view on your system.

“An architectural view is a way to portray those aspects or elements of the architecture that are relevant to the concerns the view intends to address—and, by implication, the stakeholders for whom those concerns are important.” - Software Systems Architecture by N. Rozanski and E. Woods 1

In the book of Rozanski and Woods, a couple of views for a system are presented, we will use these views for our analysis. However, every architect is, of course, free to choose what views to use for their system, First, we will describe these views shortly, based on the description given in the book 1:

  • Functional: Describes the system’s functional elements, their responsibilities, interfaces, and primary interactions.
  • Information: Describes the way that the architecture stores, manipulates, manages, and distributes information.
  • Concurrency: Describes the concurrency structure of the system that identifies the parts of the system that can be executed concurrently and how this is coordinated and controlled.
  • Development: Describes the architecture that supports the software development process.
  • Deployment: Describes the environment into which the system will be deployed, including capturing the dependencies the system has on its runtime environment.
  • Operational: Describes how the system will be operated, administered, and supported when it is running in its production environment.

For each of these views we will analyze how relevant these views are for ESLint:

  • Functional: The functional view is an essential part of the architectural model. The functional view will always be important in systems, since the goal and use of the system should be clear to all.
  • Information: ESLint is focussed on processing source code. It does not need expansive databases or distributed storage systems to function correctly. Therefore this view is a less important one for ESLint.
  • Concurrency: This is an important view since a vast amount of files are to be scanned. If it was possible to be done in parallel, this could be a nice improvement. However, it is not detrimental if this is not done since it is not weird to expect a system that scans all source files to take a bit of time to complete. Therefore this view is relevant, up to a point.
  • Development: Since the ESLint project is open-source, the development view is definitely one to be focussed on. ESLint provides excellent documentation for developers on how to contribute to the ESLint project.2
  • Deployment: The deployment view is reasonably important since ESLint has to be easily usable. Without a good view on how and why users use ESLint, making decisions about the deployment of ESLint will be much harder.
  • Operational: The operational view is less relevant for ESLint since it is deployed as a package. Once it is installed, users can update the package to a newer version, apart from that, it ‘just works’.

Architectural Style and Design Patterns

On the first inspection of the organization of the system, it is clear that ESLint is rigorously based on the architectural style of componentization. This is also documented by themselves3 and will be elaborated upon in the next section(s). The figure below shows the separation of components. Components such as Rules and Source-Code do not have dependencies, because other components, like Linter, tie these together. The isolated modules allow for better testing, higher code quality and lower the threshold for new contributors since they can get up to speed rapidly. These are important properties for a widely used system that is open-source.

[The architecture of ESLint, as supplied by the team themselves[^architecture-documentation].](images/eslint/eslint_architecture.png "The architecture of ESLint, as supplied by the team themselves")

The architecture of ESLint, as supplied by the team themselves3.

If we venture a step lower, we can’t ignore the strategy design pattern4. This pattern solves the problem of adding and using new rules with custom linting behavior, without changing the other components such as the actual Linter engine or source code parser Source-Code. This is closely related to the open-closed principle, that states:

“software entities (classes, modules, functions, etc.) should be open for extension, but closed for modification”5

Implementing a rule requires the implementation of the (very specific) interface that ESLint has provided. Since Javascript does not have interfaces, it is rather enforced through documentation, test-suites and manual checking by maintainers.

Example of the strategy pattern to plug-in two rules.

Overview of the ESLint system

In this section the development view will be discussed of ESLint. This describes the architecture that supports the software development process. The stakeholders who have the most use for this point of view are the software developers and testers.

First, we start by having a look at the required tools to develop in ESLint and the general structure of the modules in the repository. To develop in ESLint, 2 external tools are required:

  • Node.JS
  • npm

The directory structure of the repository looks as follows6:

  • bin: executable files that are available when ESLint is installed
  • conf: default configuration information
  • docs: documentation for the project
  • lib: contains the source code
    • formatters: all source files defining formatters
    • rules: all source files defining rules
  • tests: the main unit test folder
    • lib: tests for the source code
      • formatters: tests for the formatters
      • rules: tests for the rules

Looking at the directory structure, it is obvious that the setup of ESLint is pretty basic. The files in which the developers and testers will primarily work in is the libs and test folders. Here they can develop the rules7, formatters8 and write unit tests in the corresponding test folder. Additionally if the developers want to configure the parsers9 they need to work in the conf folder. External parsers can also be imported into plugins. Plugins10 serve as the export module for npm which contains the rules and formats that are used in JavaScript.

ESLint consists primarily of unit tests. For most files in the source folder there is a corresponding file in the test folder. To develop unit tests the npm module Mocha is required. Mocha provides a skeleton code for unit tests and thus are needed if developers intend to make contributions in ESLint. Finally the tests are run through npm.

ESLint architecture during run-time

ESLint provides a CLI interface that is used to execute ESLint [^eslintcli]. Once installed, this binary file, called eslint can be found in the ./node_modules/bin/. The user uses this binary eslint file to execute ESLint. Integrations with ESLint, like with text editors, also use this binary file to execute ESLint.

The flow of the ESLint project during run-time is as shown in the following diagram from the ESLint architecture documentation3:

[The architecture of ESLint, as supplied by the team themselves [^architecture-documentation].](images/eslint/eslint_architecture.png "The architecture of ESLint, as supplied by the team themselves")

We can see that this flow starts from the eslint component. We will now explain the flow of ESLint and each of its components during run-time3, starting from the eslint binary:

  • Execute eslint binary with options and the patterns (a format that allows the user to specify certain files or folders) for all files. This file then calls cli.js while passing on all arguments.
  • cli.js parses all options and the patterns. Next, performs some logging when specified by the user and validates all options specified by the user. Next a cli-engine` instance is created using the options and file patterns.
  • cli-engine iterates all files and uses a linter instance to get all the results. This linter instance is created using the options passed along to the cli-engine instance.
  • linter is the final ‘major’ component. The linter inspects the source code, execute rules on the source code and reports the results. If the user specified the code to be fixed, then the linter also fixes the problems in the source code.

Deployment of ESLint

As explained in the section “Relevant architectural views”, the deployment view describes the environment into which ESLint will be deployed including the dependencies it has on its runtime environment, according to Rozanski and Woods1. In this section, we will discuss how ESLint is deployed. Before deployment, the system should be tested and should be ready to go live. Otherwise, a potentially broken product will be delivered to the users.

First, we discuss the deployment requirements. The hardware requirements of ESLint are straightforward. A user needs a computer with internet connection that can run NodeJS (version ^8.10.0, ^10.13.0, or >=11.10.1, retrieved March 14th11). Apart from hardware, there are also third-party software requirements. The key player is the NPM packaging system, which is a software registry system that end-users can use to deploy packages into their projects. The ESLint team deploys a new package every week12, which is done by making the new changes available in their NPM package. NPM takes care of a large number of deployment concerns, such as the installation of the tool, storage and resolving potential dependency conflicts between ESLint and other packages13.

The complete deployment process is shown in the image below. After changes are made, Jenkins is used to scheduling a release build. Jenkins is an open-source automation server that helps to build, deploying and automating projects14. At some point, a six-digit 2FA code has to be entered from an authenticator app12. After deployment, the ESLint maintainers keep watching the release to verify that it functions as intended. The deployment is cross-platform, ESLint can be deployed to any operating system12.

A (simplified) view of the main deployment process the developers and users encounter.

ESLint itself is lightweight, runs on its own and does not have many requirements. For example, it does not need an internet connection to function properly. All it needs is a bit of storage space and processing power. Users can customize the configuration of ESLint to adapt the system to their needs15.

Non-functional properties

ESLint is designed to run for JavaScript. So on any platform JavaScript is running ESLint can also run. ESLint is easily installed through npm and has a fast runtime environment through Node.js. There is a lot of freedom for the user to choose their coding style. Everything is pluggable, the Formatter API and Rule API are deployed through command lines. Finally, ESLint makes sure that the project is as transparent as possible, so that everyone knows how to contribute to ESLint and what the requirements are to merge it in the project 16.

  1. Nick Rozanski and Eoin Woods. Software Systems Architecture: Working with Stakeholders Using Viewpoints and Perspectives. Addison-Wesley, 2012, 2nd edition.  2 3 4

  2. https://eslint.org/docs/developer-guide/ 

  3. https://eslint.org/docs/developer-guide/architecture  2 3 4

  4. https://en.wikipedia.org/wiki/Strategy_pattern 

  5. Meyer, B. (1988). “Object Oriented Software Construction”, Printice Hall. Inc., Upper Saddle River, NJ, USA. 

  6. https://eslint.org/docs/developer-guide/source-code 

  7. https://eslint.org/docs/developer-guide/working-with-rules 

  8. https://eslint.org/docs/developer-guide/working-with-custom-formatters 

  9. https://eslint.org/docs/developer-guide/working-with-custom-parsers 

  10. https://eslint.org/docs/developer-guide/working-with-plugins 

  11. https://eslint.org/docs/user-guide/getting-started 

  12. https://eslint.org/docs/maintainer-guide/releases  2 3

  13. guide/releaseshttps://docs.npmjs.com/cli/npm 

  14. https://jenkins.io 

  15. https://eslint.org/docs/user-guide/configuring 

  16. https://eslint.org/docs/about/ 

ESLint
Authors
Paul van der Laan
Brian Planje
Mika Kuijpers
Kabilan Gnanavarothayan