Just like a gardener prunes and shapes plants to follow a specific design, developers use linting tools to enforce architectural guidelines and ensure their code follows best practices. Both require ongoing care to identify weaknesses – whether it’s unhealthy plants or inconsistent code – and take action to correct them. With the right tools, developers can secure a solid code foundation, ensuring the codebase grows in a healthy, maintainable, and scalable way, just as a well-planned garden thrives over time. 🌱

You don’t need a PhD in machine learning to build something awesome with GenAI. If you’re a developer with a fun idea, that’s more than enough. Case in point: we built a free app named Lint-E that lets you generate custom ESLint rules using one of OpenAI’s models, GPT-4o. And yes – you can even run and validate those rules right in the UI.

Bring your own OpenAI key, craft a rule, hit “Lint Code”, and boom: you’re testing your custom AI-generated linting rule live in the UI. We even added Angular support to start playing with framework-specific rules.

So how does all this work? Let’s break it down.

Linting and ESLint 101

Linting is the process of analyzing source code to flag programming errors, stylistic issues, or suspicious constructs. Think of it like spellcheck for your code – except way smarter. It helps teams write cleaner, more consistent code. Linting is one of those things that quietly saves your life 50 times a day as a developer. Here are some practical use cases where linting really shines:

Catch Bugs Early
Description: Detect common mistakes before they cause runtime errors.
Example: Accidentally using assignment instead of comparison in a condition.
Enforce Code Style Consistency
Ensure all developers follow the same formatting and coding style.
Example: Consistent use of quotes, indentation, spacing, or semicolons.
Prevent Use of Bad Practices
Disallow risky, insecure, or outdated coding patterns.
Example: Blocking console.log, deprecated APIs, or unsafe functions.
Improve Code Quality
Encourage cleaner, more readable, and more maintainable code.
Example: Suggesting simpler expressions or consistent naming.
Custom Project-Specific Rules
Apply lint rules based on the needs of your specific application or team.
Example: Enforcing naming conventions, requiring documentation, or framework-specific rules.
Automated Checks in CI/CD
Prevent bad or non-compliant code from being merged into main branches.
Example: Blocking pull requests that fail linting during continuous integration.
Teaching and Onboarding
Help new or junior developers learn best practices by catching mistakes early.
Example: Flagging common anti-patterns or non-idiomatic code.
Enforce Architectural Boundaries
Maintain proper structure and separation of concerns in large codebases.
Example: Restricting imports between layers, preventing circular dependencies, or enforcing modular boundaries.

ESLint1 is the go-to linter for JavaScript and TypeScript. It works by parsing your code into something called an Abstract Syntax Tree (AST) 🌳, which is a structured, tree-like representation of the code. Instead of treating your code as just text, ESLint looks at the structure – like where functions start and end, what variables are declared, and what expressions are used.

An AST of any programming language is basically like grammar for natural languages – yes, the thing with conjunctions and adverbs. The sentences can be really different in terms of content like “Honey resists spoilage.” 🐝 (Archaeologists have found 3,000-year-old honey in Egyptian tombs that’s still edible!) and “Banana contains radiation.” 🍌 (Bananas contain a radioactive isotope called potassium-40). Still both sentences consist of a noun, a verb and an object. In JavaScript the function:

private function sum(a, b): number {
     return a + b;
}

Would look something like this as AST in JSON-format:

If you want to explore your program just try out the public AST Explorer2. Your program to explore can also be written in other general-purpose programming languages (GPLs) like Rust or Python or domain-specific languages (DSLs) like CSS. ESTree is a specification for representing JavaScript syntax trees in a standard JSON format, commonly used by tools like linters, compilers, and formatters to analyze and manipulate code. Python for example has its own ast module and Java for example does not have a single universal specification like ESTree. Instead, tools like Eclipse JDT3, ANTLR4, and JavaParser define their own AST structures for Java code.

ASTs also facilitate code transformation, such as transpiling newer JavaScript syntax into older versions. They support automated refactoring, code optimization, and error detection during compilation. Many IDEs use ASTs for features like auto-completion and real-time error highlighting. Additionally, ASTs help generate documentation, detect security vulnerabilities, and enable static type checking in languages like TypeScript. They are also used in test coverage tools to identify untested code. Overall, ASTs play a vital role in improving code quality, performance, and enabling cross-language tools like Prettier and ESLint, making them indispensable in modern development workflows.

ESLint works by parsing the source code into an AST, which represents the code structure. It then applies a set of predefined or custom rules to the AST to identify potential issues or style violations. Finally, it reports any errors or warnings, helping developers maintain consistent and error-free code.

Now you probably think… I don’t care. I have these rules in my project and they work because someone once configured ESLint. And yes, ESLint comes with some great core rules and a default setting for each coding guideline that should be considered best practice basically anywhere. There are further plugins like the Angular ESLint Plugin5, that does not only provide you with framework specific functionality – e. g. @Component is usually not part of the JavaScript grammar – but also use case specific sets of rules. Obviously you will also need the Typescript ESLint Plugin6 when linting TypeScript since it is a superset of JavaScript. Therefore, the grammar differs here, too. Some fancy add-on-rule-sets to your lint rule collection are provided by plugins like the ESLint Security Plugin7 or the Unicorn ESLint Plugin8. 🦄 Whereas the latter does not lead to more unicorns within your code base but provides a nice wish bag of useful linting rules. 🌈

Though we have all these handy core rules and plugins, you are individual, and so is your project, team, architecture and codebase. Custom naming conventions, big changes or architectural constraints might demand for a tailored set of rules according to your needs. And of course you can simply create your own plugin. Which has the nice side effect of not being dependend on the plugins of others pulling you deeper into the vortex of updates, version mismatches, rule conflicts, breaking changes, performance impacts… 🌪️(That’s the tornado emoticon).

To write a custom ESLint rule, you will need to understand how ESLint parses code into an Abstract Syntax Tree and how to navigate this tree to detect specific patterns. You’ll also need to know JavaScript’s syntax and how ESLint’s rule API works to define the logic for your rule. It can be challenging because writing effective rules requires both: a deep understanding of the language’s structure and the ESLint framework, as well as dealing with edge cases and ensuring your rule is both accurate and efficient. So yeah, let’s go back to these plugins….

Ooor we call upon GenAI! 🤖🪄

Sorry for the Buzzword!

We are doing GenAI. Just sounds super fancy. To sales execs.

For most devs the “doing” of Generative Artificial Intelligence really refers to the use of a model available on the market by directly or indirectly prompting it. If you get lucky the developer can reason why she decided to specifically go with that certain model, prompt-UI, IDE-integration or setup. Knowing the general fundamentals on GenAI, models, architectures and tasks they excell in as well as basic knowledge in prompt engineering is already a major win, which most devs require privately by scraping the internet rather than by being trained by their companies. We are amateur readers, too. 🤓

As a software developer what we do is:

  • Having a great idea
  • Writing awesome software
  • Picking the adequate model and attach it to our software to solve precisely specified tasks

We decided to use GPT-4o. It is a large language model, specifically a decoder-only transformer. Decoder-only models, like GPT-4o, are popular because they are well-suited for text generation, and their unified architecture makes them efficient at inference time. Unlike encoder-decoder models (such as T5), which use a separate encoder to process input and a decoder to generate output, decoder-only models handle both tasks within the same mechanism — generating output token by token while attending to the input context. This design is often simpler and faster for generation tasks, though not necessarily smaller in model size.

This efficiency is one reason why decoder-only models are especially effective for tasks like writing custom ESLint rules. They generate code step by step, predicting each part based on the prompt and previous tokens — without needing a separate comprehension stage like encoder-decoder models do. This makes them ideal for open-ended generation tasks, offering a great balance between capability and cost-effectiveness. GPTs aren’t the answer to everything, but in this case, they’re hitting the nail on the head.

The Tool: Lint-E

Lint-E (soon available for public usage here lint-ai.com) is a tool that helps you write custom ESLint rules as well as validate and export them within seconds. The app consists of a Node.js backend and Angular frontend. You bring your OpenAI API key which will be used in the API request to the GPT. Your API key will be stored in the NGXS store9. In NGXS, data stored in the store is kept in JavaScript memory on the client-side, which means it’s stored in the browser’s memory (specifically, within the JavaScript runtime environment). By default, the state is non-persistent, meaning when the page is reloaded or the browser is closed, your API key will not be remembered. There is no additional persistence implemented (e.g., localStorage, sessionStorage, or a custom persistence mechanism). You get a full UI with fields to fill out:

Description (mandatory): describe what the rule should do and why you need it. The more and the more precisely the better.

Failure Example: Give an example of when the rule would throw an error (e.g. Rule: “Do not use eval!”, bad example: “eval(anyCode)”).

Framework: You can of course also create rules specifically for e.g. React or Vue. Right now we simply do not support linting them for verification. Angular is fully supported.

Category: If you write your own ESLint plugins you might want to sort rules by concerns to improve code maintainability, readability, and organization. The category will be placed in the docs-section in the rule’s metadata. To check the structure of a rule check “Custom Rules – ESLint – Pluggable JavaScript Linter”10.

Type: In ESLint, rules can be categorized as problem (rules that detect code issues requiring fixes), suggestion (rules that recommend improvements without enforcing them), or layout (rules that enforce code formatting and style consistency). The type is also part of the rule’s metadata.

Fixable: Check the box if you want the rule to enable an automatic fix option.

Tools: You might be using a build tool like Nx. In that case you need a a link to the ESLint setup for Nx monorepos shown in the blue box below.

Depending on what you selected above the green and blue ressource boxes below will provide you with links to some documentation on how to setup ESLint with your specific build tool or framework. 🛠️

ESLint Version: We advise the GPT to keep the ESLint version in mind, while we only distinguish between major versions v8.x an v9.x.

Filetype: So far we support JavaScript and TypeScript linting. No JavaScript XML or HTML. We are working on optimizing that before making it public.

Just press Create Rule to have GPT create your rule and some bad example code to test your new rule on. With Lint Code you can lint the code example to lint and see if your rule works as intended. In case you asked Lint-E to add a fixable you can test it with Apply Auto Fix. Afterwards you can of course lint again and see if the applied fix made the errors go away by linting once again. We cannot garuantee that a perfect rule and / or fixable will be created immediately. We left the code editors open to adjust manually as needed.

°˖𓍢ִ໋🍃✿°.💚⋆ Writing a rule with Lint-E demo (…btw an absolute nonsense rule 🙈)

Why This is Fun (and Powerful)

With our app, all you need is a public OpenAI key and a custom rule in mind. You can:

  • Prompt GPT to generate custom rules
  • See the rule’s AST-powered logic
  • Test and validate it live in the UI
  • Copy, export and embedd the rule within seconds

It’s not just about GenAI – it’s about using GenAI as a creative tool. And when you combine that with useful ideas, knowledge and developer tools like ESLint and ASTs, the possibilities are WILD! 🐻🧸🐻‍❄️🐼 (personal obession with bears)

What’s Next?

A garden without maintenance is a jungle. 🦥 Tailored linting can help your code garden grow as intended. Want to help us make Lint-E support other frameworks or even languages? Try it out and give us feedback. Or just bring your greatest lint rule idea and see what the GPT comes up with.

  1. https://eslint.org/ ↩︎
  2. https://astexplorer.net/ ↩︎
  3. https://projects.eclipse.org/projects/eclipse.jdt ↩︎
  4. https://www.antlr.org/ ↩︎
  5. https://www.npmjs.com/package/@angular-eslint/eslint-plugin ↩︎
  6. @typescript-eslint/eslint-plugin – npm ↩︎
  7. https://www.npmjs.com/package/eslint-plugin-security ↩︎
  8. eslint-plugin-unicorn – npm ↩︎
  9. https://www.ngxs.io/ ↩︎
  10. https://eslint.org/docs/latest/extend/custom-rules ↩︎