< All articles

The AI-Native Universal Linter: Code Quality at Scale with AST Grep and LLMs

Aravind Putrevu - October 15, 2024

Cover image for article: The AI-Native Universal Linter: Code Quality at Scale with AST Grep and LLMs

Smart teams embrace coding standards; smarter teams make them a habit. Coding standards often take a backseat to tight deadlines and pressure to ship features quickly. However, neglecting coding standards leads to a host of problems down the road, affecting code maintainability and increasing bug rates.

Coding standards are a set of guidelines, best practices, and conventions that dictate how code is written and organized within a codebase. They cover aspects such as naming conventions, formatting, and architectural patterns. The primary goal of coding standards is to make the codebase more consistent, readable, and maintainable.

Following coding standards can feel like navigating a maze blindfolded, it’s easy to get lost and frustrated. But what if you had a guide who can assist you in that path clearly?

Enter AST Grep+ Generative AI - a powerful tool in the hands of developers to automate and simplify the process of maintaining code quality.

In this blog, we will explore how you can leverage Coding Standards as Code using AST Grep + Generative AI, and streamline the code quality for code written in any programming language.

Linters, the first line of defense

Linters are the unsung heroes of code quality (minus the noise!). The static code analysis tools have been the first line of defense against code quality for many decades. Static Analysis tools scan the codebase, potentially flagging problems and enforcing coding standards ranging from security impact to style and conventions.

Noisy nags or Helpful guides?

Linters have a reputation for being noisy, flagging every minor issue and style inconsistency with a relentless fervor. Developers often find themselves in a love-hate relationship with these tools, appreciating the responses from these scanners, while simultaneously cursing the endless stream of warnings and errors.

Image from article

Beneath the noise, linters act as a constant reminder to write clean, consistent, and maintainable code. They nudge developers towards best practices and help prevent the accumulation of tech debt.

Few examples of the powerful linters across different programming languages.

  • Biome.js: A pluggable code quality tool for JavaScript/TypeScript projects. Biome.js offers a unified interface running various lint rules, formatters, and static analyzers. Biome simplifies the setup and configuration of adding a code quality tool. To customize Biome needs a biome.config.js or biome.config.ts file.
  • Ruff: A fast and user-friendly linter for Python. Ruff is built-in with a formatter, linter, and includes rules from other python-specific linters. It encompasses 800-lint rules and replaces 50+ python code quality related packages. Ruff uses a pyproject.toml or .ruff.toml as a guidance.
  • PHPStan: PHPStan performs static code analysis and finds potential bugs and promotes best practices. PHPStan can be configured with phpstan.neon or phpstan.neon.dist.

If you notice, you need to configure linters individually, with each linter having their own complexity, inconsistency or conflicting rules with org coding policies, varied granularity, and maintenance.

Linters and Generative AI

We are in the era of generating images, text, audio and even Code. Code is sacrosanct for developers, and just like that code can be generated at the press of a button now. Powerful Large Language Models (LLMs), enriched with deep reasoning capabilities, help generate contextual code that matches the need.

Image from article

Not just code, these LLMs can also generate the tooling specs like yaml, Dockerfile, including lint configurations.

With the adoption of AI coding assistants, developers are writing more features than ever. While impressive, the generated code may still contain bugs or not fully align with the specific requirements and coding standards of the organization. Similarly, generated linter configurations might not be as effective as rules that fit your organization's guidelines.

The role of Linters and Code Review remains crucial. Interestingly, this is a good problem to solve on both sides, again, using Generative AI.

AST Grep: A Smarter Approach to Code Quality

AST Grep is powerful tool that allows you to write custom rules using a simple regex-like query language to match code patterns. Let us take a look at some sample Python code and how AST Grep can detect patterns.

def authenticate(username, password):
    if username == "admin" and password == "supersecret":
       return True
    return False

AST Grep Pattern

// secret_detector.yml
id: detect-hardcoded-credentials
language: python
rule:
 pattern: |-
   def $FUNC_NAME($USERNAME, $PASSWORD):
     if $USERNAME == "$USER_VALUE" and $PASSWORD == "$PASS_VALUE":
       return True
     return False
message: "Hardcoded credentials detected in authentication function"
severity: warning

Run AST Grep

sg --rule-file secret_detector.yml /path/to/your/python/files
sample_code.py: Potential hardcoded secret detected
      if username == "admin" and password == "supersecret":
                                 ^^^^^^^^^^^^^^^^^^^^^^^^

Note: You can run this rule live in the AST Grep playground.

You can write more complicated AST Grep patterns to enforce coding standards and best practices specific to your repository or organization.

Discovering Code Intent with AST Grep

Beyond enforcing coding standards, AST Grep can be used to gain deeper insight or logical reasoning behind code flow. At organizations, various stakeholders make critical business decisions that are translated into code due to past incidents. By crafting targeted patterns, you can codify architectural decisions, or potential performance bottlenecks.

For example, let’s say you want to detect whether a function is making a network call.

id: detect-requests-get
language: Python
rule:
  pattern: |-
    (call
      function: (attribute
        object: (identifier) @MODULE
        attribute: (identifier) @FUNCTION))
  constraints:
    MODULE:
      regex: ^requests$
    FUNCTION:
      regex: ^get$
message: "HTTP GET request detected. Ensure proper error handling and consider security implications."
severity: warning

The rule looks for function calls where the requests module is used to make a GET request, indicating a network call. You identify network calls across your codebase even if the code is complex or poorly documented.

Similarly, you can create AST Grep Rules to detect:

  • Usage of specific libraries or frameworks

  • Error handling patterns

  • Resource management (ex: opening and closing files)

  • Concurrency patterns (ex: usage of threads or async/await)

By understanding the intent behind code snippets, you can make more informed decisions about refactoring, optimization, or architectural changes.

Coding Standards as Code with AST Grep and Generative AI

Now that we’ve seen how AST Grep can be used to discover intent behind the code, let’s take it a step further and explore how we can combine it with Generative AI to yield better results than Lint rules.

In a typical Retrieval Augmented Generation (RAG), you supplement the LLM with more information from your retrieval and hence with context the LLM would generate a response.

The retrieved context (vectorized data) grounds the LLM with factual information, reducing possible hallucinations. Because, unlike humans, the probability of LLMs false or inconsistent information increases with the complexity of the problem.

By combining AST Grep with RAG, we can provide the LLM with highly relevant and focused context about the code being analyzed. AST Grep can extract specific code patterns, architectural decisions, and potential issues, which can then be fed into the LLM as additional context, thereby also suggesting 1-click fixes to the problems.

Now, without writing a single lint rule, you have access to understand (reason), find and suggest fixes to most complex code problems.

Let's say we use below AST Grep rule to identify all instances of database queries in a codebase:

id: parameterised-db-query
language: python
rule:
  pattern: $RESULTS = $DB.query($QUERY, $PARAM)
message: "Consider using parameterized queries for better security"
severity: WARNING
fix: |-
    $RESULTS = $DB.execute($QUERY, ($PARAM,))

AST Grep would find code snippets like:

results = db.query("SELECT * FROM users WHERE id = ?", user_id)

These snippets, along with their surrounding context, can be passed to the LLM as part of the RAG process. The LLM can then analyze the code, understand its intent, and generate suggestions or identify potential issues based on best practices and coding standards.

In this case, the LLM might suggest using a ORM library like SQLAlchemy to make the code more maintainable and less prone to SQL Injection attacks.

user = User.query.filter_by(id=user_id).first()

This approach has several advantages over traditional linter rules in reducing noise, allowing feedback to be more nuanced and intelligent. LLMs can provide explanations and rationale for its suggestions.

Deterministic Code Quality at Scale with CodeRabbit

At CodeRabbit, we believe that the combination of RAG and AST Grep creates a powerful approach to enforcing coding standards. One of the key challenges with Generative AI models is their tendency to produce output that, while coherent and contextually relevant, may not always align with specific requirements of a given codebase. This is where deterministic grounding comes into play.

Image from article

By leveraging AST Grep to extract not just code patterns but also concrete, deterministic information about code (ex: variable names, function signatures, dependencies), we can provide LLMs with a more realistic context. This helps suppress the noise and variability in the given suggestions.

This additional context guides the LLMs to generate suggestions that are not only conceptually relevant but also syntactically and semantically valid within the project's existing setup.

For instance, going back to the database query example, the LLM-generated-suggestion to use an ORM library is contextually relevant. Developer can review the suggestion, and apply it with a single click, without the need to rewrite the code.

AST Grep + RAG: The AI-native Universal Linter

At this point, you might be thinking: "This all sounds great, but how can I actually start using these techniques in my own projects?" Enter CodeRabbit - AI Code Reviewer that works with existing code hosting platforms like GitHub, GitLab and Azure DevOps.

At CodeRabbit, we’ve taken this concept and turned it into a powerful tool for developers improving developer productivity alongside their code. Our AI-native Code Reviewer harnesses the potential of LLMs and AST Grep.

CodeRabbit also maintains unique and custom AST Grep rules under the ast-grep-essentials GitHub repository for the developer community, do consider giving us a star. Sign up to CodeRabbit and get started in your AI Code Quality journey.