Close Menu
    DevStackTipsDevStackTips
    • Home
    • News & Updates
      1. Tech & Work
      2. View All

      Sunshine And March Vibes (2025 Wallpapers Edition)

      May 21, 2025

      The Case For Minimal WordPress Setups: A Contrarian View On Theme Frameworks

      May 21, 2025

      How To Fix Largest Contentful Paint Issues With Subpart Analysis

      May 21, 2025

      How To Prevent WordPress SQL Injection Attacks

      May 21, 2025

      Google DeepMind’s CEO says Gemini’s upgrades could lead to AGI — but he still thinks society isn’t “ready for it”

      May 21, 2025

      Windows 11 is getting AI Actions in File Explorer — here’s how to try them right now

      May 21, 2025

      Is The Alters on Game Pass?

      May 21, 2025

      I asked Copilot’s AI to predict the outcome of the Europa League final, and now I’m just sad

      May 21, 2025
    • Development
      1. Algorithms & Data Structures
      2. Artificial Intelligence
      3. Back-End Development
      4. Databases
      5. Front-End Development
      6. Libraries & Frameworks
      7. Machine Learning
      8. Security
      9. Software Engineering
      10. Tools & IDEs
      11. Web Design
      12. Web Development
      13. Web Security
      14. Programming Languages
        • PHP
        • JavaScript
      Featured

      Celebrating GAAD by Committing to Universal Design: Equitable Use

      May 21, 2025
      Recent

      Celebrating GAAD by Committing to Universal Design: Equitable Use

      May 21, 2025

      GAAD and Universal Design in Healthcare – A Deeper Look

      May 21, 2025

      GAAD and Universal Design in Pharmacy – A Deeper Look

      May 21, 2025
    • Operating Systems
      1. Windows
      2. Linux
      3. macOS
      Featured

      Google DeepMind’s CEO says Gemini’s upgrades could lead to AGI — but he still thinks society isn’t “ready for it”

      May 21, 2025
      Recent

      Google DeepMind’s CEO says Gemini’s upgrades could lead to AGI — but he still thinks society isn’t “ready for it”

      May 21, 2025

      Windows 11 is getting AI Actions in File Explorer — here’s how to try them right now

      May 21, 2025

      Is The Alters on Game Pass?

      May 21, 2025
    • Learning Resources
      • Books
      • Cheatsheets
      • Tutorials & Guides
    Home»Development»How JavaScript Lint Rules Work (and Why Abstract Syntax Trees Matter)

    How JavaScript Lint Rules Work (and Why Abstract Syntax Trees Matter)

    May 21, 2025

    Before I started to contribute to eslint-plugin-react, I didn’t think too deeply about the linters I used every day while writing code. Like many developers, I installed them at the start of a project, appreciated the red underlines or auto-fixes, and moved on.

    But behind those helpful messages is a powerful system of rules and structure that most of us rarely explore.

    Linters are everywhere – across languages, frameworks, and workflows. They help catch errors, enforce consistent formatting, and promote best practices. They’re among the first tools we install in a new project, and yet they’re also some of the most underrated and least understood.

    In this article, I’m going to take you under the hood. We’ll look at how JavaScript lint rules work, why ASTs (Abstract Syntax Trees) are such a big deal, and how you can use this understanding to write or contribute to a linter yourself.

    📚 Table of Contents

    • What Even Is a Linter?

    • From Code to Tree: Enter the AST

    • Why ASTs Matter for Linting

    • How ESLint Uses ASTs Under the Hood

    • Anatomy of a Lint Rule

    • Helpful Tools for Exploring ASTs

    • Wrapping Up: Why You Should Understand This

    🧹What Even Is a Linter?

    A linter is a tool that automatically analyzes your code to flag errors, enforce style rules, and catch potential bugs. Think of it as the Grammarly of the coding world – helping you write cleaner, more consistent code by pointing out problems early.

    A popular example is ESLint, an open-source linter for JavaScript and TypeScript that checks code for issues and can even auto-fix some of them.

    Linters are often:

    • Integrated into your text editor or IDE

    • Run as part of a CI pipeline or pre-commit hook

    • Used alongside formatters like Prettier for even stricter consistency

    But how do they decide what to flag as an issue? That’s where lint rules come in.

    🧱 Lint Rules: The Brains Behind the Linter

    Lint rules are the building blocks of any linter. Each rule defines:

    1. What to look for: a specific pattern in your code.

    2. What to do about it: a warning, an error, or an auto-fix.

    There are many types of rules, often grouped into categories like:

    • Error prevention: Catching bugs, like using undeclared variables.

    • Code style: Enforcing consistent formatting and naming conventions.

    • Best practices: Encouraging safer or more readable coding patterns.

    • Security: Flagging risky code, like direct eval() calls or unsafe regex.

    If you’ve ever seen an ESLint message like this:

    Unexpected console.log
    
    Missing semicolon
    
    'myVar' is assigned a value but never used
    

    …you’ve seen lint rules in action.

    They’re not just “style police.” Linters help reduce mental overhead by catching little issues early, so you can focus on the bigger picture of what your code is trying to do.

    🌳 From Code to Tree: Enter the AST

    To understand how lint rules work under the hood, we need to talk about the Abstract Syntax Tree (AST) – the data structure at the heart of every linter.

    An AST is a structured, tree-like representation of your code. Instead of reading your code as raw text, a linter converts it into a tree where each part of your code (a variable, a string, a function, and so on) becomes a node in the tree.

    Here’s an example.

    Paste this code into AST Explorer, a tool that lets you view the AST for code in real time:

    const name = "Tilda";
    

    Set the language to JavaScript, and choose one of the ESLint parsers like Espree. You’ll see something like this in the right panel:

    AST (Abstract Syntax Tree) showing a VariableDeclaration node for a constant declaration. Inside it is a VariableDeclarator that assigns a Literal node with the string value 'Tilda' to an Identifier named 'name'.

    In the image above from AST Explorer, you can see how the tree is structured:

    • Program:

      • The root node of the AST. It wraps the entire code.

      • Contains a body, which is an array of statements.

    • VariableDeclaration

      • Type: "VariableDeclaration"

      • Represents a declaration using the const keyword.

      • Has a kind of "const" and a list of declarations.

    • VariableDeclarator

      • Type: "VariableDeclarator"

      • Represents a single variable being declared.

      • Contains two key parts:

        • Identifier

          • Type: "Identifier"

          • Name: "name"

          • This is the variable being declared.

        • Literal

          • Type: "Literal"

          • Value: "Tilda"

          • This is the string being assigned to the variable.

    This nesting is what makes the structure “tree-like”. Each node is a parent to smaller pieces (its children), which helps linters navigate code reliably.

    So while your eyes see a short line of JavaScript, the linter sees a detailed map of what that line means structurally. This hierarchy allows tools like ESLint to pinpoint exactly what kind of code is being used – and where – so rules can target patterns like:

    • “Flag all const variables”

    • “Warn when a variable is named name“

    • “Disallow hardcoded strings like Tilda“

    🤖 Why ASTs Matter for Linting

    Now, here’s the key idea: lint rules don’t work by reading your code like text. They work by matching specific node patterns in the AST.

    That matters a lot because there are dozens of ways to write the same logic in JavaScript. Let’s take two versions of the same logic: one written as a function declaration, and one as an arrow function.

    function greet() {
      return "hello";
    }
    
    const greet = () => "hello";
    

    At a glance, they look different. But when we examine their ASTs, we see that both follow similar structural patterns. This is what allows a linter to recognize what your code is doing, no matter how it’s written.

    🌳 The Tree Behind the Function Declaration

    Abstract Syntax Tree (AST) showing a FunctionDeclaration node with an Identifier for the function name. The function contains a BlockStatement with a ReturnStatement node. Inside the ReturnStatement is a Literal node returning the string 'hello'

    Here’s what ESLint sees in the AST tree when you write a function declaration:

    • It starts with a FunctionDeclaration node.

    • That node contains:

      • An Identifier (the function name: greet)

      • A BlockStatement representing the function body

      • Inside the BlockStatement, there’s a ReturnStatement

      • The ReturnStatement returns a Literal — the string "hello"

    🌳 The Tree Behind the Arrow Function

    Abstract Syntax Tree (AST) showing a VariableDeclaration node for a const arrow function. Inside it is a VariableDeclarator assigning an ArrowFunctionExpression to an identifier. The ArrowFunctionExpression contains a body with a Literal node returning the string 'hello'.

    Here’s what ESLint sees when you write the same logic using an arrow function:

    • A VariableDeclaration with kind: "const"

      • Inside it, a VariableDeclarator, which assigns a value to the greet variable

      • The value is an ArrowFunctionExpression

      • The body of the arrow function is a Literal — the string "hello"

    Even though the syntax is different, both paths eventually lead to a Literal node containing "hello" – which is all your linter needs to care about.

    💡 Let’s Bring It Home with an Example

    Imagine your team has a rule: functions shouldn’t return hardcoded strings like "hello". You want a linter that flags this.

    With ASTs, you can write one lint rule that matches a ReturnStatement or an ArrowFunctionExpression whose body is a Literal.

    Here’s the basic idea:

    ReturnStatement(node) {
      if (node.argument?.type === "Literal" && node.argument.value === "hello") {
        context.report({ node, message: "Avoid returning static 'hello' strings." });
      }
    }
    

    And for arrow functions with expression bodies:

    ArrowFunctionExpression(node) {
      if (node.body?.type === "Literal" && node.body.value === "hello") {
        context.report({ node, message: "Avoid returning static 'hello' strings." });
      }
    }
    

    Even though the code styles are different, the structure of the AST is similar enough that both functions will trigger the rule, because the linter isn’t looking at how the code is written, only what the structure of the AST actually is.

    This is what makes ASTs so useful: they let linters ignore surface-level differences and focus on the actual meaning and structure of your code. As a result, you can write smarter, more flexible rules that catch patterns across different styles, no matter how someone wrote their JavaScript.

    🔨 How ESLint Uses ASTs Under the Hood

    ESLint relies on a standardized format called ESTree (ECMAScript Tree) to represent JavaScript code as an Abstract Syntax Tree (AST). ESTree isn’t a parser itself – it’s a specification that defines how JavaScript code should be represented as a tree. This makes it possible for ESLint (and similar tools) to understand code in a consistent, structured way.

    When you run ESLint on your code, here’s what’s happening under the hood:

    1. Your Code Is Parsed into an AST

    ESLint converts your code into an AST that follows the ESTree format. This tree is made up of nodes, each representing a piece of your code (like a variable, function, or expression). The resulting structure is what every lint rule will analyze.

    2. Lint Rules “Subscribe” to Specific Node Types

    Each lint rule tells ESLint which node types it wants to listen to. For example, a rule might care about:

    • Identifier

    • CallExpression

    • VariableDeclaration

    These node types match the structure you’d see in tools like AST Explorer.

    3. ESLint Traverses the Tree and Triggers Rules

    ESLint walks through the AST, visiting one node at a time. When it reaches a node type that a rule has subscribed to, it triggers the corresponding function in that rule.

    This process is efficient and declarative, you don’t have to worry about manually scanning through every line of code. ESLint does the walking, your rule just listens.

    4. Rules Inspect Nodes and Report Problems

    Inside each rule, you receive the node ESLint has passed in. You can look at its properties – like name, value, or surrounding structure – and decide whether it violates your intended pattern.

    If it does, you use context.report() to tell ESLint to flag it as an issue. ESLint can also fix the issue automatically if you provide a fix() function inside context.report().

    context.report({
        node: node,
        message: "Missing semicolon".
        fix: function(fixer) {
            return fixer.insertTextAfter(node, ";");
        }
    });
    

    🩻 Anatomy of a Lint Rule

    Let’s look at a very simple custom ESLint rule. This one flags any variable named any:

    module.exports = {
      meta: {
        type: "problem",
        docs: {
          description: "Disallow variables named 'any'",
        },
      },
    
      create(context) {
        return {
          Identifier(node) {
            if (node.name === 'any') {
              context.report({
                node,
                message: "Don't use 'any' as a variable name."
              });
            }
          }
        };
      }
    };
    

    🔎 What’s happening here:

    • The meta section provides info about the rule (used in ESLint docs and tooling).

    • The create() function defines which node types the rule listens for.

    • Identifier(node) is triggered every time an identifier is found in the code.

    • If the identifier’s name is any, the rule calls context.report() to raise a warning.

    🛠 Helpful Tools for Exploring ASTs

    Understanding ASTs can feel abstract at first, but some tools make the learning curve much smoother. These are especially helpful when you’re trying to visualize how your code translates into tree structures, or when debugging a custom rule.

    1. AST Explorer

    This is the most beginner-friendly and powerful tool for working with ASTs. You can:

    • Paste in any JavaScript code

    • Choose an ESLint-compatible parser (like Espree)

    • Instantly see the AST structure on the right-hand side

    • Hover over tree nodes and see how they map to specific parts of your code

    If you’re writing a custom rule, AST Explorer will likely become your best friend. It helps you figure out exactly which node type you need to target and what properties are available on that node.

    2. ESLint’s Rule Examples and Tests

    Sometimes the best way to learn is to read real code. ESLint’s core rules (or rules from popular plugins like eslint-plugin-react) include:

    • Rule definitions

    • Test files showing code that should and shouldn’t trigger the rule

    • Fix examples (if the rule is auto-fixable)

    Browsing these helps you understand how real-world rules are structured and how the test setup works.

    Tip: Look in the tests/lib/rules/ or lib/rules/ folders of ESLint or plugin repositories.

    3. ESLint’s Documentation

    ESLint has comprehensive documentation about working with rules:

    • ESLint: Working with Rules

    • ESLint: Custom Rules

    ✅ Wrapping Up: Why You Should Understand This

    Understanding how ASTs work gives you superpowers when it comes to customizing and contributing to linting tools. Whether you’re trying to enforce a specific pattern in your team’s codebase or want to contribute to a plugin like eslint-plugin-react, this knowledge will help you:

    • 🔧 Contribute to existing rules by understanding what they’re checking and how

    • 🐛 Debug confusing linter behavior when rules fire unexpectedly (or not at all)

    • 🛠 Write your own custom rules to enforce specific coding standards, project conventions, or best practices

    You don’t need to be a compiler expert or fully grasp every node type in the spec. You just need to recognize patterns, explore trees, and get comfortable identifying the nodes your rule cares about.

    Source: freeCodeCamp Programming Tutorials: Python, JavaScript, Git & More 

    Facebook Twitter Reddit Email Copy Link
    Previous ArticleWill “Vibe Coders” Take Our Dev Jobs?
    Next Article Sam Altman’s $6.5 billion purchase might deliver an “iPhone of artificial intelligence” from OpenAI before Apple. Here’s how.

    Related Posts

    Security

    Nmap 7.96 Launches with Lightning-Fast DNS and 612 Scripts

    May 22, 2025
    Common Vulnerabilities and Exposures (CVEs)

    CVE-2025-4094 – “Acunetix DIGITS WordPress OTP Brute Force Vulnerability”

    May 22, 2025
    Leave A Reply Cancel Reply

    Continue Reading

    How Small Businesses Can Leverage React Native for Big Growth📈

    Web Development

    Can ADHD meds help with anxiety?

    Web Development

    Distribution Release: Clonezilla Live 3.2.1-9

    News & Updates

    How Data Breach Laws Impact Loan Terms for Businesses: A Study from UWA

    Development

    Highlights

    Luigi builds complex pipelines of batch jobs

    April 20, 2025

    Luigi is a Python package that helps you build complex pipelines of batch jobs. The…

    Rilasciata Chimera Linux 20250214: Un Passo Avanti Verso la Stabilità

    February 15, 2025

    How to Seamlessly Migrate from Appium 1.x to Appium 2.x: A Comprehensive Guide

    May 15, 2024

    Bilt Rewards now lets you pay your student loans with points

    April 23, 2025
    © DevStackTips 2025. All rights reserved.
    • Contact
    • Privacy Policy

    Type above and press Enter to search. Press Esc to cancel.