
Research
SANDWORM_MODE: Shai-Hulud-Style npm Worm Hijacks CI Workflows and Poisons AI Toolchains
An emerging npm supply chain attack that infects repos, steals CI secrets, and targets developer AI toolchains for further compromise.
@tony.ganchev/eslint-plugin-header
Advanced tools
The native ESLint 9/10 header plugin. A zero-bloat, drop-in replacement for 'eslint-plugin-header' with first-class Flat Config & TypeScript support. Auto-fix Copyright, License, and banner comments in JavaScrip and TypeScript files.
The native ESLint 9/10 standard header-validating plugin. A zero-bloat, drop-in replacement for eslint-plugin-header with first-class Flat Config & TypeScript support. Auto-fix copyright, license, and banner comments in JavaScript and TypeScript files.
The plugin started as a fork of eslint-plugin-header to address missing ESLint 9 compatibility.
Today it addresses the following issues:
Multiple other projects took from where eslint-plugin-header left off. A comparison of the current project to these alternatives is available in a dedicated section.
The plugin supports ESLint 7 / 8 / 9 / 10. Both flat config and legacy, hierarchical config can be used.
The NPM package provides TypeScript type definitions and can be used with
TypeScript-based ESLint flat configuration without the need for @ts-ignore
statements.
The plugin and its header rule goes through evolution of its configuration in the 3.2.x release. We introduced a new single object-based configuration format that is easier to evolve in the future to add more capabilities.
The legacy configuration format inherited from eslint-plugin-header is still supported and you can learn how to use it in a dedicated document. For information on how to switch from the legacy configuration format to the new style, follow our migration guide. The current document from this point on will cover only the new configuration format.
This header rule takes a single object as configuration, after the severity
level. At the very least, the object should contain a header field describing
the expected header to match in the source files.
For TypesScript-based flat ESLint configuration, two types are provided:
HeaderRuleConfig defines the overall rule configuration for the header
rule and includes severity level and supports both the modern object-based
configuration and the legacy array-based configuration.HeaderOptions helper type that defines the structure of the configuration
object used in the modern configuration style that is used in this document.
It can be used to either simplify auto-completion since this type is not mixed
with a large number of named tuple types, or it can be used when the config
object is defined outside of the definition of a specific rule.In this configuration mode, the header template is read from a file.
eslint.config.ts:
import header, {
HeaderOptions, HeaderRuleConfig
} from "@tony.ganchev/eslint-plugin-header";
import { defineConfig } from "eslint/config";
export default defineConfig([
{
files: ["**/*.js"],
plugins: {
"@tony.ganchev": header
},
rules: {
"@tony.ganchev/header": [
"error",
{
header: {
file: "config/header.js"
}
} as HeaderOptions
] as HeaderRuleConfig
}
}
]);
config/header.js:
// Copyright 2015
// My company
Due to limitations in ESLint plugins, the file is read relative to the working directory that ESLint is executed in. If you run ESLint from elsewhere in your tree then the header file will not be found.
In this configuration mode, the matching rules for the header are given inline.
The header field should contain the following nested properties:
commentType which is either "block" or "line" to indicate what style
of comment should be used.line which defines the lines of the header. It can be either a
single multiline string / regular expression with the full contents of the
header comment or an array with comment lines or regular expressions matching
each line. It can also include template replacement strings to enable ESLint's
auto-fix capabilities.Suppose we want our header to look like this:
/*
* Copyright (c) 2015
* My Company
*/
All of the following configurations will match the header:
Single string:
import header, { HeaderOptions } from "@tony.ganchev/eslint-plugin-header";
import { defineConfig } from "eslint/config";
export default defineConfig([
{
files: ["**/*.js"],
plugins: {
"@tony.ganchev": header
},
rules: {
"@tony.ganchev/header": [
"error",
{
header: {
commentType: "block",
lines: ["\n * Copyright (c) 2015\n * My Company\n "]
}
} as HeaderOptions
]
}
}
]);
Note that the above would work for both Windows and POSIX systems even
though the EOL in the header content was specified as \n.
Also, notice how we have an empty space before each line. This is because
the plugin only strips the leading // characters from a line comment.
Similarly, for a block comment, only the opening /* and closing */ will
be preserved with all new lines and whitespace preserved. Keep this in mind
as this can lead to poorly configured header matching rules that never
pass. In a future release error messages would be more detailed and show
exactly where header validation failed.
Single regular expression:
You can match the whole header with a regular expression. To do it, simply
pass a RegExp object in place of a string.
import header, { HeaderOptions } from "@tony.ganchev/eslint-plugin-header";
import { defineConfig } from "eslint/config";
export default defineConfig([
{
files: ["**/*.js"],
plugins: {
"@tony.ganchev": header
},
rules: {
"@tony.ganchev/header": [
"error",
{
header: {
commentType: "block",
lines: [
/\n \* Copyright \(c\) 2015\n \* Company\n /
]
}
} as HeaderOptions
]
}
}
]);
If you still use hierarchical configuration, you can define the regular expression as a string.
import header, { HeaderOptions } from "@tony.ganchev/eslint-plugin-header";
import { defineConfig } from "eslint/config";
export default defineConfig([
{
files: ["**/*.js"],
plugins: {
"@tony.ganchev": header
},
rules: {
"@tony.ganchev/header": [
"error",
{
header: {
commentType: "block",
lines: [
{ pattern: "\\n \\* Copyright \\(c\\) 2015"
+ "\\n \\* My Company\\n "}
]
}
} as HeaderOptions
]
}
}
]);
Notice the double escaping of the braces. Since these pattern strings into
RegExp objects, the backslashes need to be present in the string instead
of disappear as escape characters.
You can pass a RegExp object to the pattern field. This is necessary if
you want to add an aut-fix for the line as we will explain further in this
document.
import header, { HeaderOptions } from "@tony.ganchev/eslint-plugin-header";
import { defineConfig } from "eslint/config";
export default defineConfig([
{
files: ["**/*.js"],
plugins: {
"@tony.ganchev": header
},
rules: {
"@tony.ganchev/header": [
"error",
{
header: {
commentType: "block",
lines: [
{ pattern: /Copyright \(c\) 20\d{2}/ }
]
}
} as HeaderOptions
]
}
}
]);
Array of strings:
import header, { HeaderOptions } from "@tony.ganchev/eslint-plugin-header";
import { defineConfig } from "eslint/config";
export default defineConfig([
{
files: ["**/*.js"],
plugins: {
"@tony.ganchev": header
},
rules: {
"@tony.ganchev/header": [
"error",
{
header: {
commentType: "block",
lines: [
"",
" * Copyright (c) 2015",
" * My Company",
" "
]
}
} as HeaderOptions
]
}
}
]);
Array of strings and/or patterns:
import header, { HeaderOptions } from "@tony.ganchev/eslint-plugin-header";
import { defineConfig } from "eslint/config";
export default defineConfig([
{
files: ["**/*.js"],
plugins: {
"@tony.ganchev": header
},
rules: {
"@tony.ganchev/header": [
"error",
{
header: {
commentType: "block",
lines: [
"",
/ \* Copyright \(c\) 2015/,
" * My Company",
" "
]
}
} as HeaderOptions
]
}
}
]);
Regular expressions allow for a number of improvements in the maintainability of the headers. Given the example above, what is clear is that new sources may have been created later than 2015 and a comment with a different year should be perfectly valid, such as:
/*
* Copyright 2020
* My company
*/
Moreover, suppose your legal department expects that the year of first and last change be added except if all changes happen in the same year, we also need to support:
/*
* Copyright 2017-2022
* My company
*/
We can use a regular expression to support all of these cases for your header:
import header, { HeaderOptions } from "@tony.ganchev/eslint-plugin-header";
import { defineConfig } from "eslint/config";
export default defineConfig([
{
files: ["**/*.js"],
plugins: {
"@tony.ganchev": header
},
rules: {
"@tony.ganchev/header": [
"error",
{
header: {
commentType: "block",
lines: [
"",
/ \* Copyright \(c\) (\d{4}-)?\d{4}/,
" * My Company",
" "
]
}
} as HeaderOptions
]
}
}
]);
Note on auto-fixes i.e. eslint --fix: whenever strings are used to define the
header - counting in file-based configuration - the same strings would be used
to replace a header comment that did not pass validation. This is not possible
with regular expressions. For regular expression pattern-objects, a second
property template adds a replacement string.
import header, { HeaderOptions } from "@tony.ganchev/eslint-plugin-header";
import { defineConfig } from "eslint/config";
export default defineConfig([
{
files: ["**/*.js"],
plugins: {
"@tony.ganchev": header
},
rules: {
"@tony.ganchev/header": [
"error",
{
header: {
commentType: "line",
lines: [
{
pattern: / Copyright \(c\) (\d{4}-)?\d{4}/,
template: " Copyright 2025",
},
" My Company"
]
}
} as HeaderOptions
]
}
}
]);
There are a number of things to consider:
A common request across similar plugins is to provide for {year} variable to
not change the ESLint configuration every year. While such special requests were
relevant to old JSON-based configuration, this can be handled with JavaScript in
the flat configuration format:
import header, { HeaderOptions } from "@tony.ganchev/eslint-plugin-header";
import { defineConfig } from "eslint/config";
export default defineConfig([
{
files: ["**/*.js"],
plugins: {
"@tony.ganchev": header
},
rules: {
"@tony.ganchev/header": [
"error",
{
header: {
commentType: "line",
lines: [
{
pattern: / Copyright \(c\) (\d{4}-)?\d{4}/,
template: ` Copyright ${new Date().getFullYear()}`,
},
" My Company"
]
}
} as HeaderOptions
]
}
}
]);
The third argument of the rule configuration which defaults to 1 specifies the number of newlines that are enforced after the header.
Zero newlines:
import header, { HeaderOptions } from "@tony.ganchev/eslint-plugin-header";
import { defineConfig } from "eslint/config";
export default defineConfig([
{
files: ["**/*.js"],
plugins: {
"@tony.ganchev": header
},
rules: {
"@tony.ganchev/header": [
"error",
{
header: {
commentType: "block",
lines: [
" Copyright now",
"My Company "
],
},
trailingEmptyLines: {
minimum: 0
}
} as HeaderOptions
]
}
}
]);
/* Copyright now
My Company */ console.log(1)
One newline (default):
import header, { HeaderOptions } from "@tony.ganchev/eslint-plugin-header";
import { defineConfig } from "eslint/config";
export default defineConfig([
{
files: ["**/*.js"],
plugins: {
"@tony.ganchev": header
},
rules: {
"@tony.ganchev/header": [
"error",
{
header: {
commentType: "block",
lines: [
" Copyright now",
"My Company "
],
},
trailingEmptyLines: {
minimum: 1
}
} as HeaderOptions
]
}
}
]);
/* Copyright now
My Company */
console.log(1)
Two newlines:
import header, { HeaderOptions } from "@tony.ganchev/eslint-plugin-header";
import { defineConfig } from "eslint/config";
export default defineConfig([
{
files: ["**/*.js"],
plugins: {
"@tony.ganchev": header
},
rules: {
"@tony.ganchev/header": [
"error",
{
header: {
commentType: "block",
lines: [
" Copyright now",
"My Company "
]
},
trailingEmptyLines: {
minimum: 2
}
} as HeaderOptions
]
}
}
]);
/* Copyright now
My Company */
console.log(1)
The rule works with both Unix/POSIX and Windows line endings. For ESLint
--fix, the rule will use the line ending format of the current operating
system (via Node's os package). This setting can be overwritten as follows:
import header, { HeaderOptions } from "@tony.ganchev/eslint-plugin-header";
import { defineConfig } from "eslint/config";
export default defineConfig([
{
files: ["**/*.js"],
plugins: {
"@tony.ganchev": header
},
rules: {
"@tony.ganchev/header": [
"error",
{
header: {
commentType: "block",
lines: [
"Copyright 2018",
"My Company"
]
},
lineEndings: "windows"
} as HeaderOptions
]
}
}
]);
Possible values are "unix" for \n and "windows" for \r\n line endings.
The default value is "os" which means assume the system-specific line endings.
The following examples are all valid.
import header, { HeaderOptions } from "@tony.ganchev/eslint-plugin-header";
import { defineConfig } from "eslint/config";
export default defineConfig([
{
files: ["**/*.js"],
plugins: {
"@tony.ganchev": header
},
rules: {
"@tony.ganchev/header": [
"error",
{
header: {
commentType: "block",
lines: ["Copyright 2015, My Company"]
}
} as HeaderOptions
]
}
}
]);
/*Copyright 2015, My Company*/
console.log(1);
import header, { HeaderOptions } from "@tony.ganchev/eslint-plugin-header";
import { defineConfig } from "eslint/config";
export default defineConfig([
{
files: ["**/*.js"],
plugins: {
"@tony.ganchev": header
},
rules: {
"@tony.ganchev/header": [
"error",
{
header: {
commentType: "line",
lines: [
"Copyright 2015",
"My Company"
]
}
} as HeaderOptions
]
}
}
]);
//Copyright 2015
//My Company
console.log(1)
import header, { HeaderOptions } from "@tony.ganchev/eslint-plugin-header";
import { defineConfig } from "eslint/config";
export default defineConfig([
{
files: ["**/*.js"],
plugins: {
"@tony.ganchev": header
},
rules: {
"@tony.ganchev/header": [
"error",
{
header: {
commentType: "line",
lines: [
/^Copyright \d{4}$/,
/^My Company$/
]
}
} as HeaderOptions
]
}
}
]);
//Copyright 2017
//My Company
console.log(1)
With more decoration:
import header, { HeaderOptions } from "@tony.ganchev/eslint-plugin-header";
import { defineConfig } from "eslint/config";
export default defineConfig([
{
files: ["**/*.js"],
plugins: {
"@tony.ganchev": header
},
rules: {
"@tony.ganchev/header": [
"error",
{
header: {
commentType: "block",
lines: [
"************************",
" * Copyright 2015",
" * My Company",
" ************************"
]
}
} as HeaderOptions
]
}
}
]);
/*************************
* Copyright 2015
* My Company
*************************/
console.log(1);
A number of projects have been aiming to solve problems similar to @tony.ganchev/eslint-plugin-header - mainly with respect to providing ESLint 9 support. The section below tries to outline why developers may choose either. The evaluation is based on the versions of each project as of the time of publishing the current version of @tony.ganchev/eslint-plugin-header.
Further iterations of this document would add migration information.
@tony.ganchev/eslint-plugin-header is a drop-in replacement for eslint-plugin-header and all plugins that already use the latter can migrate to the fork right away. At the same time, it provides improved user experience and windows support.
eslint-plugin-headers is not a drop-in replacement. It offers additional
features. Some of them, such as support for Vue templates do not have an
analogue in the current version of @tony.ganchev/eslint-plugin-header while
others such as {year} variable placeholders are redundant in the world of
ESLint 9's flat, JavaScript-based configuration as already pointed out in this
document.
The configuration format philosophy of the two plugin differs. @tony.ganchev/eslint-plugin-header supports both the legacy model inherited from eslint-plugin-header and a new object-based configuration that is easy to adopt and offers both a lot of power to the user as to what the headers should look like, and keeps the configuration compact - just a few lines defining the content inside the comment. At the same time, the configuration is structured in a way that can evolve without breaking compatibility, which is critical for a tool that is not differentiating for the critical delivery of teams.
eslint-plugin-headers also offers an object-based format, but the content is
flat and may need breaking changes to be kept concise as new features come
about. Further, it makes assumption that then need to be corrected such as a
block comment starting with /** instead of /* by default. The correction
needs to happen not by adjusting the header template but through a separate
confusing configuration properties.
Overall, the configuration tends to be noisier nad harder to read than that of
@tony.ganchev/eslint-plugin-header.
@tony.ganchev/eslint-plugin-header - snyk.io, socket.dev
eslint-plugin-headers - snyk.io, socket.dev
At the time of the publishing of the current version of @tony.ganchev/eslint-plugin-header, the latter has a slight edge in both scans against eslint-plugin-headers.
eslint-plugin-license-header per its limited documentation does not have a lot of features including not matching arbitrary from-years in the copyright notice. This on one hand leads to it having a nice, dead-simple configuration, but means no complex multi-year project would be happy with it. Surprisingly, given the limited feature set, the plugin has more peer dependencies than the competition.
The project follows standard NPM semantic versioning policy.
The following guidelines apply:
Two concepts are important when going over the above guidelines and we will go over them in the next sections.
We keep the distinction between a feature and a non-feature improvement / bug fix as simple as possible:
Backward compatibility in the context of this plugin relates to how the plugin consistently passes or fails one and the same code in between upgrades to newer backward-compatible versions. This guarantees that plugin updates can be done without breaking CI/CD pipeline linting.
Backward-compatibility does not cover the following functional aspects:
MIT
FAQs
The native ESLint 9/10 header plugin. A zero-bloat, drop-in replacement for 'eslint-plugin-header' with first-class Flat Config & TypeScript support. Auto-fix Copyright, License, and banner comments in JavaScrip and TypeScript files.
The npm package @tony.ganchev/eslint-plugin-header receives a total of 9,130 weekly downloads. As such, @tony.ganchev/eslint-plugin-header popularity was classified as popular.
We found that @tony.ganchev/eslint-plugin-header demonstrated a healthy version release cadence and project activity because the last version was released less than a year ago. It has 1 open source maintainer collaborating on the project.
Did you know?

Socket for GitHub automatically highlights issues in each pull request and monitors the health of all your open source dependencies. Discover the contents of your packages and block harmful activity before you install or update your dependencies.

Research
An emerging npm supply chain attack that infects repos, steals CI secrets, and targets developer AI toolchains for further compromise.

Company News
Socket is proud to join the OpenJS Foundation as a Silver Member, deepening our commitment to the long-term health and security of the JavaScript ecosystem.

Security News
npm now links to Socket's security analysis on every package page. Here's what you'll find when you click through.