Home

Awesome

<!-- markdownlint-disable -->

 

<p align="center"> <picture> <source media="(prefers-color-scheme: dark)" srcset="https://cdn.adguard.com/website/github.com/AGLint/aglint_logo_darkmode.svg"> <img alt="AGLint" src="https://cdn.adguard.com/website/github.com/AGLint/aglint_logo_lightmode.svg" width="350px"> </picture> </p> <h3 align="center">Universal adblock filter list linter.</h3> <p align="center"> Supported syntaxes: </p> <p align="center"> <a href="https://adguard.com"><img src="https://cdn.adguard.com/website/github.com/AGLint/adg_logo.svg" width="14px"> AdGuard</a> | <a href="https://github.com/gorhill/uBlock"><img src="https://cdn.adguard.com/website/github.com/AGLint/ubo_logo.svg" width="14px"> uBlock Origin</a> | <a href="https://getadblock.com"><img src="https://cdn.adguard.com/website/github.com/AGLint/ab_logo.svg" width="14px"> AdBlock</a> | <a href="https://adblockplus.org"><img src="https://cdn.adguard.com/website/github.com/AGLint/abp_logo.svg" width="14px"> Adblock Plus</a> </p> <p align="center"> <a href="https://www.npmjs.com/package/@adguard/aglint"><img src="https://img.shields.io/npm/v/@adguard/aglint" alt="NPM version" /></a> <a href="https://www.npmjs.com/package/@adguard/aglint"><img src="https://img.shields.io/npm/dm/@adguard/aglint" alt="NPM Downloads" /></a> <a href="https://github.com/AdguardTeam/AGLint/blob/master/LICENSE"><img src="https://img.shields.io/npm/l/@adguard/aglint" alt="License" /></a> </p> <!-- markdownlint-restore -->

Table of Contents:

Introduction

AGLint is a universal adblock filter list linter. It supports all popular syntaxes currently in use:

AGLint can be used as a command-line tool or as a TS/JS library in the Node.js or browser environment.

Our goal is to provide a tool that can be used by everyone who is interested in adblock filters. We want to make it easy to create and maintain filter lists.

Generally the philosophy of AGLint are inspired by ESLint. If you are familiar with ESLint, you will find it easy to use AGLint as well.

Features

Getting started

Mainly AGLint is a CLI tool, but it can also be used programmatically. Here is a very short instruction on how to use it as a CLI tool with the default configuration.

Pre-requisites

Installation & Usage

  1. Install AGLint to your project:
    • NPM: npm install -D @adguard/aglint
    • Yarn: yarn add -D @adguard/aglint
  2. Initialize the configuration file for AGLint:
    • NPM: npx aglint init
    • Yarn: yarn aglint init
  3. Run AGLint:
    • NPM: npx aglint
    • Yarn: yarn aglint

That's all! :hugs: The linter will check all filter lists in your project and print the results to the console.

[!NOTE] You can also install AGLint globally, so you can use it without npx or yarn, but we recommend to install it locally to your project.

[!NOTE] If you want to lint just some specific files, you can pass them as arguments: aglint path/to/file.txt path/to/another/file.txt

[!NOTE] To see all available options, run aglint --help.

To customize the default configuration, see Configuration for more info. If you want to use AGLint programmatically, see Use programmatically.

Integrate AGLint into your project

If you would like to integrate AGLint into your project / filter list, please read our detailed Integration guide for more info.

VSCode extension

We have created a VSCode extension that fully covers adblock filter list syntax. It is available here.

This extension enables syntax highlighting, and it's compatible with AGLint. Typically, it means that this extension will detect all syntax errors and show them in the editor, and on top of that, it will also show some warnings and hints, because it also runs AGLint under the hood.

GitHub Linguist also uses this extension to highlight adblock filter lists.

We strongly recommend using this extension if you are working with adblock filter lists.

Special comments (inline configuration)

You may not want to lint some adblock rules, so you can add special inline comments to disable linting for a single adblock rule or for the rest of the file. To do that, you need to add special comments to your adblock filter list, which can be used to change the linter's behavior. Generally these "control comments" begins with the ! aglint prefix.

In the following sections you can find more info about these comments.

Ignore adblock rules

Ignore single adblock rule

You can completely disable linting for an adblock rule by adding ! aglint-disable-next-line comment before the adblock rule. For example, example.com##.ad will be ignored in the following case:

! aglint-disable-next-line
example.com##.ad
example.net##.ad

This lets you disable linting for a single adblock rule, but it doesn't disable linting for the rest of the file. If you want to disable linting for the rest of the file, you can add ! aglint-disable comment before the first adblock rule or add the file path to the ignore list (.aglintignore file). See Ignoring files or folders for more info.

Ignore multiple adblock rules

If you want to ignore multiple adblock rules, you can add ! aglint-disable comment before the first adblock rule and ! aglint-enable comment after the last adblock rule. For example, example.com##.ad and example.net##.ad will be ignored in the following case:

! aglint-disable
example.com##.ad
example.net##.ad
! aglint-enable
example.org##.ad

Disable some linter rules

In some cases, you may want to disable some linter rules for a single adblock rule or for multiple adblock rules. Here is how you can do it:

Change linter rules configuration

In some cases, you may want to change the configuration of some linter rules during linting. Here is how you can do it:

! aglint "rule-1": ["warn", { "option1": "value1" }], "rule-2": "off"
example.com##.ad
example.net##.ad

After the ! aglint comment, you should specify the list of the rules that you want to change. It will applied to all lines after the comment until the end of the file or until the next ! aglint comment. The syntax is the same as in the configuration file.

Ignoring files or folders

You can ignore files or folders by creating an "ignore file" named .aglintignore in any directory. The syntax and behavior of this file is the same as .gitignore file. Learn more about .gitignore here if you are not familiar with it.

If you have a config file in an ignored folder, it will be ignored as well.

Default ignores

Some "problematic" paths are ignored by default in order to avoid linting files that are not related to adblock filter lists. These paths are:

Configuration

AGLint requires a configuration file to work. If you don't have a configuration file, the CLI will throw an error and ask you to create one.

Create a configuration file

If you don't have a configuration file, you can create it by running aglint init in the root directory of your project. This command will create a .aglintrc.yaml file in the current directory.

You can also create a configuration file manually, please check the section below for more info.

[!NOTE] We are planning to add a configuration wizard in the future, so you will be able to create a configuration file by answering a few questions.

Configuration file name and format

Configuration file is a JSON or YAML file that contains the configuration for the linter and should be named as one of the following:

We also plan to support .aglintrc.js (JavaScript) in the future.

We recommend using .aglintrc.yaml or .aglintrc.yml because YAML is more compact and easier to read, and it supports comments.

[!WARNING] If you have multiple configuration files in the same directory, the CLI will throw an error and ask you to fix it.

[!WARNING] If your configuration file is syntactically invalid or contains unknown / invalid options, the CLI will throw an error and ask you to fix it.

[!WARNING] If your configuration file is not named in one of the ways listed above, the CLI will ignore it (since it cannot recognize it as a configuration file).

Configuration file structure

The configuration file should be a valid JSON or YAML file. The following options are available:

<a name="configuration-rule-structure"></a> Configuration rule structure

A rule basically has the following structure:

<!-- TODO: use real rules as an examples -->
Examples

You can disable the rule-1 rule by adding the following configuration:

{
    "rules": {
        "rule-1": "off"
    }
}

but an array also can be used as well:

{
    "rules": {
        "rule-1": ["off"]
    }
}

You can change the severity of the rule-2 rule to warn:

{
    "rules": {
        "rule-2": ["warn"]
    }
}

or change the severity of the rule-3 rule to error and add a configuration for it:

{
    "rules": {
        "rule-3": ["error", { "option-3": "value-3" }]
    }
}

Configuration presets

Configuration presets are basically configuration files that you can use to extend in your configuration. Currently, there are two built-in presets available (click on the name to see the source code):

[!NOTE] Presets contain syntax and rules which shall be overridden if they are specified in the config.

[!NOTE] All presets have syntax property set to Common a default value. You may need to specify it in your configuration file for better linting, e.g. modifiers validation.

[!NOTE] We are planning to add more presets in the future, and also allow users to create their own presets but currently it is not possible.

Default configuration file

This configuration file is the same as created by aglint init command. It simply extends the aglint:recommended preset and specifies the root option.

[!NOTE] JavaScript configuration files aren't supported at the moment but we plan to add support for them in the future (CJS and ESM syntaxes).

Configuration cascading and hierarchy

AGLint follows the same configuration file search algorithm as ESLint (learn more), so if you are familiar with ESLint, this section will be easy to understand.

If you call AGLint in a directory (lets call it current directory / current working directory), it will search for a configuration file in this directory and all parent directories until it finds one configuration file with the root option set to true or reaches the root directory (the most top directory, which doesn't have a parent directory). If the linter doesn't find any configuration file at all, it will throw an error and ask you to fix it, because it cannot work without a configuration file.

If the linter finds multiple configuration files in the same directory, it will also throw an error and ask you to fix it, because it is an inconsistent state, since the linter doesn't know which configuration file to use. ESLint uses a name-based priority system to resolve this issue, but AGLint throws an error instead, to keep things simple and clear.

Why the root option is important

Suppose you store your projects in the my-projects directory, and you have the following directory structure:

my-projects
├── .aglintrc.yaml
├── project-1
│   ├── dir1
│   │   ├── list1.txt
│   │   ├── list2.txt
│   ├── dir2
│   │   ├── .aglintrc.yaml
│   │   ├── dir3
│   │   │   ├── list3.txt
│   │   │   ├── list4.txt
│   ├── list5.txt
│   ├── .aglintrc.yaml
├── project-2
│   ├── ...
├── ...

As you can see, the my-projects directory contains a configuration file, and the project-1 directory also contains some configuration files.

Let's assume that my-projects/project-1/.aglintrc.yaml doesn't have the root option set to true.

If you call AGLint in the project-1 directory, it finds the configuration file in the project-1, but since it doesn't specify the root property, therefore the linter will continue to search for a configuration file in the parent directories. As a result, it will find the configuration file in the my-projects directory and merge these two configuration files into one configuration. This is a bad practice, since if you move your project to another directory, linting results may change, because my-projects/.aglintrc loses its effect. Projects should be handled as a single unit, and the root option is designed to solve this problem. If you set the root option to true in the configuration file from the project-1 directory, the linter will stop searching for configuration files right after it finds the configuration file from the project-1 directory, and will ignore the configuration file from the my-projects directory. This is how the root option works and why it is important.

However, merging configurations is useful within a single project, so if you specify the main configuration in your project's root directory, but if you want to override some rules in some subdirectories, you can do it by creating a configuration file in this subdirectory. For example, if you want to disable the rule-1 rule in the dir2 directory, you can create the following configuration file in the dir2 directory:

# project-1/dir2/.aglintrc.yaml
rules:
  rule-1: "off"

And of course, at the top of this hierarchy, you can specify inlined configuration comments in your adblock filter list files, which will override the configuration from the configuration files but only if allowInlineConfig option is enabled.

<a name="linter-rules"></a> Linter rules

The linter parses your filter list files with the AGTree parser, then it checks them against the linter rules. If a linter rule is violated, the linter will report an error or warning. If an adblock rule is syntactically incorrect (aka it cannot be parsed), the linter will report a fatal error and didn't run any other linter rules for that adblock rule, since it is not possible to check it without AST. The rest of the file (valid rules) will be checked with the linting rules.

The linter rules documentation is written in the following schema:

Currently, the following linter rules are available (we will add more rules in the future):

if-closed

Checks if the if statement is closed and no unclosed endif or unopened else statements are present. It also checks whether else and endif statements are used correctly since they can only be used alone without other parameters or statements.

single-selector

Checks element hiding rules to make sure that they contain only one selector.

duplicated-modifiers

Checks if the same modifier is used multiple times in a single network rule.

unknown-preprocessor-directives

Checks if the used preprocessor directives are known.

[!IMPORTANT] Preprocessor directives are case-sensitive, so !#IF is to be considered as invalid.

duplicated-hint-platforms

Checks if the same platform is used multiple times in a single hint.

duplicated-hints

Checks if the same hint is used multiple times within a single comment.

unknown-hints-and-platforms

Checks if the hints and platforms are known.

invalid-domain-list

Checks for invalid domains in cosmetic rules.

invalid-modifiers

Checks for invalid modifiers in basic (network) rules.

inconsistent-hint-platforms

Check if the hint platforms are targeted inconsistently. This means that the same platform is targeted in the PLATFORM hint, but excluded in the NOT_PLATFORM hint at the same time (or vice versa).

no-short-rules

Check if the rule length is less than the specified minimum threshold value, i.e. if the rule is too short.

no-excluded-rules

Check if a rule is excluded. With this linter rule, we can "ban" certain patterns from the filter lists. Requested in https://github.com/AdguardTeam/AGLint/issues/214

no-invalid-css-syntax

Check if a rule contains syntactically invalid CSS. It uses the ECSSTree parser internally to check the CSS syntax.

no-invalid-css-declaration

Checks whether a CSS injection rule contains any unknown properties or invalid values. This process utilizes the CSSTree lexer, which is based on the MDN Data database.

Compatibility

The linter is compatible with all modern browsers and Node.js versions. Minimum required versions are:

NameMinimum version
Node.js✅ 14
Chrome✅ 88
Firefox✅ 84
Edge✅ 88
Opera✅ 80
Safari✅ 14
Internet Explorer

Minimum required versions are determined by the Node.js LTS schedule and the ES6 compatibility table.

Maybe the linter will work in older browsers and Node.js versions, but it's not guaranteed we don't recommend using such old versions.

Use programmatically

You can use several parts of AGLint programmatically, but it is only recommended for advanced users who are familiar with Node.js, JavaScript, TypeScript and the basics of software development. Generally, the API are well documented with a lot of examples, but you can open a discussion if you have any questions, we will be happy to help you.

The linter is a tool that checks the rules for errors and bad practices. It is based on the parser, so it can parse all ADG, uBO and ABP rules currently in use. The linter API has two main parts:

Please keep in mind that the CLI only can be used in Node.js (because it uses the fs module for file management), but the linter can be used in both Node.js and browsers.

Example usage:

import { Linter } from "@adguard/aglint";

// Create a new linter instance and add default rules (make first parameter true
// to add default rules)
const linter = new Linter(true);

// Add custom rules (optional). Rules are following LinterRule interface.
// linter.addRule("name", { data });

// Lint a content (file content - you can pass new lines as well)
// If you want to enable the fixer, pass true as the second parameter
const report = linter.lint("example.com##.ad, #ad");

// Do something with the report

The LinterRule interface has the following structure:

Every event has a context parameter, which makes it possible to get the current filter list content, the current rule, report, etc.

You can check the src/linter/rules directory for detailed examples.

You can find the detailed linter rule documentation here.

Development & Contribution

Please read the CONTRIBUTING.md file for details on how to contribute to this project.

Ideas & Questions

If you have any questions or ideas for new features, please open an issue or a discussion. We will be happy to discuss it with you.

License

AGLint is licensed under the MIT License. See the LICENSE file for details.

References

Here are some useful links to help you write adblock rules. This list is not exhaustive, so if you know any other useful resources, please let us know.

<!--markdownlint-disable MD013--> <!--markdownlint-enable MD013-->