Gathering detailed insights and metrics for eslint-plugin-codegen
Gathering detailed insights and metrics for eslint-plugin-codegen
Gathering detailed insights and metrics for eslint-plugin-codegen
Gathering detailed insights and metrics for eslint-plugin-codegen
An eslint plugin for inline codegen. Auto-fixes out of sync code, with presets for barrels, jsdoc to markdown and more.
npm install eslint-plugin-codegen
Typescript
Module System
Min. Node Version
Node Version
NPM Version
68.9
Supply Chain
90.4
Quality
76.8
Maintenance
100
Vulnerability
97
License
TypeScript (96.38%)
JavaScript (2.11%)
AppleScript (1.52%)
Built with Next.js • Fully responsive • SEO optimized • Open source ready
Total Downloads
943,152
Last Day
1,418
Last Week
12,879
Last Month
77,162
Last Year
472,379
Apache-2.0 License
29 Stars
86 Commits
2 Forks
3 Watchers
12 Branches
3 Contributors
Updated on Aug 19, 2025
Latest Version
0.32.1
Package Id
eslint-plugin-codegen@0.32.1
Unpacked Size
697.58 kB
Size
154.83 kB
File Count
111
NPM Version
10.9.2
Node Version
23.8.0
Published on
Jun 04, 2025
Cumulative downloads
Total Downloads
Last Day
36.9%
1,418
Compared to previous day
Last Week
16.6%
12,879
Compared to previous week
Last Month
-17.1%
77,162
Compared to previous month
Last Year
209.1%
472,379
Compared to previous year
29
An eslint plugin for inline codegen. Auto-fixes out of sync code, with presets for barrels, jsdoc to markdown and more.
Sometimes the same information is useful in multiple places - for example, jsdoc comments in code can double as markdown-formatted documentation for a library.
This allows generating code in a project using eslint, without having to incorporate any extra build tools, either for the codegen itself, or to validate that the generated code is up to date. So references to other parts of the project will always stay up to date - and your existing CI tools can enforce this just by running eslint.
Here's an example of it being used along with VSCode's eslint plugin, with auto-fix-on-save:
Before you use this, note that it's still in v0. That means:
markdownFromJsdoc
only works with export const ...
style exports. Currently most of the features implemented are ones that are specifically needed for this git repo.In an eslint-enabled project, install with
1npm install --save-dev eslint-plugin-codegen
or
1yarn add --dev eslint-plugin-codegen
Then add the plugin and rule to your eslint config, for example in eslintrc.js
:
1module.exports = { 2 //... 3 plugins: [ 4 // ... 5 'codegen', 6 ], 7 rules: { 8 // ... 9 'codegen/codegen': 'error', 10 }, 11}
You can use the rule by running eslint in a standard way, with something like this in an npm script: eslint --ext .ts,.js,.md .
In vscode, if using the eslint plugin, you may need to tell it to validate markdown files in your repo's .vscode/settings.json
file (see this repo for an example):
1{ 2 "eslint.validate": ["markdown", "javascript", "typescript"], 3 "editor.codeActionsOnSave": { 4 "source.fixAll.eslint": true 5 } 6}
To trigger the rule, add a comment line to a source file.
In markdown:
<!-- codegen:start {{ OPTIONS }} -->
In typescript/javascript:
// codegen:start {{ OPTIONS }}
Where {{ OPTIONS }}
are an inline object in the format:
{preset: presetName, key1: value1, key2: value2}
Where key1
and key2
are options passed to the codegen preset. yaml is used to parse the object, So any valid yaml that fits on one line can be passed as options. In practise, the one-line restriction means using yaml's "flow style" for collections.
See below for documentation. This repo also has lots of usage examples.
This plugin uses an ESLint processor to handle markdown and YAML files. ESLint only allows one processor per file type, so the processor from this plugin is designed to be compatible with eslint-plugin-markdown
. But to use both plugins, you need to use the process for eslint-plugin-codegen
, not eslint-plugin-markdown
. You can do this by adding the recommended config for eslint-plugin-codegen
second, e.g.
1module.exports = { 2 plugins: ['markdown', 'codegen'], 3 extends: ['plugin:markdown/recommended', 'plugin:codegen/recommended'], 4}
Or specify the processor explicitly - when you switch to flat config this will be required:
1module.exports = { 2 // 1. Add the plugin. 3 plugins: ['markdown'], 4 overrides: [ 5 { 6 // 2. Enable the Markdown processor for all .md files. 7 files: ['**/*.md'], 8 processor: 'codegen/processor', // NOT 'markdown/markdown' 9 }, 10 ], 11}
Define your own codegen function, which will receive all options specified. Import the Preset
type from this library to define a strongly-typed preset function:
1export const jsonPrinter: import('eslint-plugin-codegen').Preset<{myCustomProp: string}> = ({meta, options}) => { 2 const components = meta.glob('**\/*.tsx') // uses 'globSync' from glob package 3 const json = JSON.stringify({filename: meta.filename, customProp: options.myCustomProp, components}, null, 2) 4 return `export default ${json}` 5} 6 7// codegen:start {export: jsonPrinter}
This can be used in other files by specifying the source
option like:
<!-- codegen:start {source: ./lib/my-custom-preset.js, export: jsonPrinter, myCustomProp: hello}
Note that some helpers passed via dependencies
, such as glob
, fs
, path
, child_process
, lodash
, jsYaml
, dedent
, and readPkgUp
, corresponding to those node modules respectively. These can be useful to allow access to those libraries without them being production dependencies. This also allows your lint process to use these node-only dependencies, even in a file that is not run in node - only the calls would be included in any bundled output, not the dependencies themselves.
name | description |
---|---|
source | Relative path to the module containing the custom preset. Default: the file being linted. |
export | The name of the export. If omitted, the module's default export should be a preset function. |
require | A module to load before source . If not set, defaults to tsx/cjs or ts-node/register/transpile-only for typescript sources. |
dev | Set to true to clear the require cache for source before loading. Allows editing the function without requiring an IDE reload. Default false if the CI enviornment variable is set, true otherwise. |
Sometimes, you may want to write a custom function that takes a long time to run. To avoid having to wait for it to run every time you lint, you can set the dev
option to true
. This will clear the require cache for the custom function, so it will be run fresh each time.
1export const longRunningFunction: import('eslint-plugin-codegen').Preset = ({cache}) => { 2 const result = cache({maxAge: '1 year'}, () => { 3 // do something that takes a long time to run, but doesn't need to run too often 4 }) 5 return result 6} 7// codegen:start {preset: custom, export: longRunningFunction}
You can use some helpers that are passed to the preset function:
1export const secretaryGeneralLogStatement: import('eslint-plugin-codegen').Preset = ({cache, dependencies}) => { 2 const result = cache({maxAge: '4 weeks'}, () => { 3 const res = dependencies.fetchSync('https://en.wikipedia.org/wiki/Secretary-General_of_the_United_Nations') 4 const $ = dependencies.cheerio.load(res.text) 5 6 const secretaryGeneral = $('.infobox td:contains("Incumbent") a').text() 7 return `console.log('The UN Secretary-General is ${secretaryGeneral}')` 8 }) 9 return result 10} 11 12// codegen:start {preset: custom, export: secretaryGeneralLogStatement}
This will transform to something like:
1export const secretaryGeneralLogStatement: import('eslint-plugin-codegen').Preset = ({cache, dependencies}) => { 2 return cache({maxAge: '4 weeks'}, () => { 3 const res = dependencies.fetchSync('https://en.wikipedia.org/wiki/Secretary-General_of_the_United_Nations') 4 const $ = dependencies.cheerio.load(res.text) 5 6 const secretaryGeneral = $('.infobox td:contains("Incumbent") a').text() 7 return `console.log('The UN Secretary-General is ${secretaryGeneral}')` 8 }) 9 return result 10} 11 12// codegen:start {preset: custom, export: secretaryGeneralLogStatement} 13// codegen:hash {input: 4119892f2e6eaf56ae5c346de91be718, output: eed0d07c81b82bff1d3e4751073b0112, timestamp: 2025-03-05T18:58:13.921Z} 14console.log('The UN Secretary-General is António Guterres') 15// codegen:end
Since hitting wikipedia servers is slow and unreliable, you don't want to do it every time you lint. The codegen will be a no-op and leave the content untouched unless:
timestamp
Note that in the example above, we are using cheerio
without having to import it - you don't even need it installed in your devDependencies (it's a dependency of eslint-plugin-codegen - so it does live in your node_modules, you just don't need to manage it or worry about tree-shaking).
The helpers that are provided to the generator function via the dependencies
prop are listed below. You can use all of them in a type-safe way in your generator function, without having to add them as dependencies or even devDependencies:
fs
: https://nodejs.org/api/fs.htmlpath
: https://nodejs.org/api/path.htmlchild_process
: https://nodejs.org/api/child_process.htmllodash
: https://npmjs.com/package/lodashjsYaml
: https://npmjs.com/package/js-yamldedent
: https://npmjs.com/package/dedentglob
: https://npmjs.com/package/globreadPkgUp
: https://npmjs.com/package/read-pkg-upcheerio
: https://npmjs.com/package/cheeriomakeSynchronous
: A function for making functions synchronous by running them in a subprocess. See the code for more details. It's a simplified version of this. Note: it's strongly recommended to use this with the cache
feature to avoid slowing down your lint process.fetchSync
: A simplified fetch
wrapper that runs synchronously via makeSynchronous
. See the code for more details. Useful for fetching data from the internet without adding a production dependency. Note: it's strongly recommended to use this with the cache
feature to avoid slowing down your lint process.The plugin will attempt to run generator functions written in typescript. If tsx
or ts-node
are available, they will be used to register their respective loaders before requiring the file containing the generator function. If you have trouble, try installing tsx
even if you don't otherwise use it - or just write the generator function in a separate javascript file.
Note: to get type safety for the helpers in javascript files, use the {@type ...}
syntax:
1/** @type {import('eslint-plugin-codegen').Preset} */ 2module.exports.myGenerator = ({dependencies}) => { 3 const subpackages = dependencies.glob.sync('subpackages/**/*.package.json') 4 return `const subpackages = ${JSON.stringify(subpackages)}` 5}
Copies a whole other file. Useful for "borrowing" an implementation of a simple utility from another project, without needing to publish it. Obviously this creates duplicated code, so use judiciously!
1// codegen:start {preset: copy, source: ../../another-project/src/some-file.ts} import {z} from 'zod' export const MyObject = z.object({ foo: z.string() }) 2// codegen:end
1import {z} from 'zod/v4' // in this project we use zod v4, but we're copying from a project that uses zod v3 2// codegen:start {preset: copy, source: ../../another-project/src/some-file.ts, excludeLines: ['^import']} 3export const MyObject = z.object({foo: z.string()}) 4// codegen:end
1// copy a file from a sibling project, but only if the sibling project actually exists 2// in this case this will effectively skip the copying step on machines that don't have the sibling project installed 3// e.g. on CI runners. 4// codegen:start {preset: copy, source: ../../another-project/src/some-file.ts, onlyIfExists: ../../another-project/package.json} 5import {z} from 'zod' 6 7export const MyObject = z.object({foo: z.string()}) 8// codegen:end
1// by default, the content will perform a "simplified" comparison with existing content, so differences from tools like prettier 2// are ignored. if you care about whitespace and similar differences, you can set the comparison option to `strict`. 3// codegen:start {preset: copy, source: ../../another-project/src/some-file.ts, comparison: strict} 4import {z} from 'zod' 5 6export const MyObject = z.object({foo: z.string()}) 7// codegen:end
Bundle several modules into a single convenient one.
1// codegen:start {preset: barrel, include: some/path/*.ts, exclude: some/path/*util.ts} 2export * from './some/path/module-a' 3export * from './some/path/module-b' 4export * from './some/path/module-c' 5// codegen:end
name | description |
---|---|
include | [optional] If specified, the barrel will only include file paths that match this glob pattern |
exclude | [optional] If specified, the barrel will exclude file paths that match these glob patterns |
import | [optional] If specified, matching files will be imported and re-exported rather than directly exported with export * from './xyz' . Use import: star for import * as xyz from './xyz' style imports.Use import: default for import xyz from './xyz' style imports. |
export | [optional] Only valid if the import style has been specified (either import: star or import: default ).If specified, matching modules will be bundled into a const or default export based on this name. If set to {name: someName, keys: path} the relative file paths will be used as keys. Otherwise the file pathswill be camel-cased to make them valid js identifiers. |
extension | [optional] Useful for ESM modules. If set to true files will be imported with the file extension. If set to an object, extensions will be converted using this object. |
Convert jsdoc for an es export from a javascript/typescript file to markdown.
<!-- codegen:start {preset: markdownFromJsdoc, source: src/foo.ts, export: bar} -->
name | description |
---|---|
source | {string} relative file path containing the export with jsdoc that should be copied to markdown |
export | {string} the name of the export |
headerLevel | {1 |
Generate a table of contents for a monorepo.
<!-- codegen:start {preset: monorepoTOC} -->
<!-- codegen:start {preset: monorepoTOC, repoRoot: .., workspaces: lerna, filter: {package.name: foo}, sort: -readme.length} -->
name | description |
---|---|
repoRoot | [optional] the relative path to the root of the git repository. By default, searches parent directories for a package.json to find the "root". |
filter | [optional] a dictionary of filter rules to whitelist packages. Filters can be applied based on package.json keys, examples: - filter: '@myorg/.*-lib' (match packages with names matching this regex)- filter: { package.name: '@myorg/.*-lib' } (equivalent to the above)- filter: { package.version: '^[1-9].*' } (match packages with versions starting with a non-zero digit, i.e. 1.0.0+)- filter: '^(?!.*(internal$))' (match packages that do not contain "internal" anywhere (using negative lookahead))- filter: { package.name: '@myorg', path: 'libraries' } (match packages whose name contains "@myorg" and whose path matches "libraries")- filter: { readme: 'This is production-ready' } (match packages whose readme contains the string "This is production-ready") |
sort | [optional] sort based on package properties (see filter ), or readme length. Use - as a prefix to sort descending.examples: - sort: package.name (sort by package name)- sort: -readme.length (sort by readme length, descending)- sort: toplogical (sort by toplogical dependencies, starting with the most depended-on packages) |
Convert jsdoc to an es export from a javascript/typescript file to markdown.
<!-- codegen:start {preset: markdownFromJsdoc, source: src/foo.ts, export: bar} -->
name | description |
---|---|
source | {string} relative file path containing the export with jsdoc that should be copied to markdown |
export | {string} the name of the export |
Generate a table of contents from the current markdown file, based on markdown headers (e.g. ### My section title
)
<!-- codegen:start {preset: markdownTOC, minDepth: 2, maxDepth: 5} -->
name | description |
---|---|
minDepth | exclude headers with lower "depth". e.g. if set to 2, # H1 would be excluded but ## H2 would be included. @default 2 |
maxDepth | exclude headers with higher "depth". e.g. if set to 3, #### H4 would be excluded but ### H3 would be included. @default Infinity |
Use a test file to generate library usage documentation. Note: this has been tested with vitest and jest. It might also work fine with mocha, and maybe ava, but those haven't been tested. JSDoc/inline comments above tests will be added as a "preamble", making this a decent way to quickly document API usage of a library, and to be sure that the usage is real and accurate.
<!-- codegen:start {preset: markdownFromTests, source: test/foo.test.ts, headerLevel: 3} -->
name | description |
---|---|
source | the test file |
include | if defined, only tests with titles matching one of these regexes will be included |
exclude | if defined, tests with titles matching one of these regexes will be excluded |
headerLevel | The number of # characters to prefix each title with |
includeEslintDisableDirectives | If true, // eslint-disable ... type comments will be included in the preamble. @default false |
Generates a yaml config for the GitHub Pull Request Labeler Action. Creates a label per package name, which will be applied to any file modified under the leaf package path. When packages are added or removed from the repo, or renamed, the yaml config will stay in sync with them. Additional labels can be added outside of the generated code block. See https://github.com/mmkal/ts/tree/main/.github/labeler.yml for an example.
1# codegen:start {preset: labeler}
Note: eslint and related tools make it quite difficult to lint github action yaml files. To get it working, you'll need to:
'!.github'
to your .eslintignore
file, or the ignorePatterns
property in your lint config."yaml"
to the "eslint.validate"
list in vscode/settings.json
.'.yml'
(and/or '.yaml'
) to the parserOptions.extraFileExtensions
list in your lint config..
) in your tsconfig. See https://github.com/mmkal/ts/tree/main/tsconfig.eslint.json for an example.name | description |
---|---|
repoRoot | [optional] path to the repository root. If not specified, the rule will recursively search parent directories for package.json files |
In addition to the custom preset, you can also define your own presets in eslint configuration, e.g.:
1module.exports = { 2 // ... 3 plugins: [ 4 // ... 5 'codegen', 6 ], 7 rules: { 8 // ... 9 'codegen/codegen': ['error', {presets: require('./-presets')}], 10 }, 11}
presets
should be a record of preset functions, conforming to the Preset
interface from this package. This can be used to extend the in-built ones. For example, you could make generated markdown collapsible:
Before:
<!-- codegen:start {preset: markdownTOC}-->
- [Section1](#section1)
- [Section2](#section2)
<!-- codeg```
`my-custom-presets.js`:
<!-- eslint-disable @typescript-eslint/no-var-requires -->
<!-- eslint-disable import/no-extraneous-dependencies -->
```js
const {presets} = require('eslint-plugin-codegen')
module.exports.markdownTOC = params => {
const toc = presets.markdownTOC(params)
return [
'<details>',
'<summary>click to expand</summary>',
'',
toc,
'</details>',
].join('\n')
}
.eslintrc.js
:
1module.exports = { 2 // ... 3 plugins: [ 4 // ... 5 'codegen', 6 ], 7 rules: { 8 // ... 9 'codegen/codegen': ['error', {presets: require('./my-custom-presets')}], 10 }, 11}
After:
readme.md
:
<!-- codegen:start {preset: markdownTOC}-->
<details>
<summary>click to expand</summary>
- [Section1](#section1)
- [Section2](#section2)
</details>
<!-- codegen:end -->
Rendered:
The code in this repository was moved from https://github.com/mmkal/ts
No vulnerabilities found.