0.28.0 • Published 1 month ago

eslint-plugin-codegen v0.28.0

Weekly downloads
687
License
Apache-2.0
Repository
github
Last release
1 month ago

eslint-plugin-codegen

An eslint plugin for inline codegen. Auto-fixes out of sync code, with presets for barrels, jsdoc to markdown and more.

CI npm

Motivation

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:

npm.io

Contents

How to use

Before you use this, note that it's still in v0. That means:

  1. Breaking changes might happen. Presets might be renamed, or have their options changed. The documentation should stay up to date though, since that's partly the point of the project.
  2. There are missing features, or incompletely-implemented ones. For example, markdownFromJsdoc only works with export const ... style exports. Currently most of the features implemented are ones that are specifically needed for this git repo.
  3. There might be bugs. The project is in active development - raise an issue if you find one!

Setup

In an eslint-enabled project, install with

npm install --save-dev eslint-plugin-codegen

or

yarn add --dev eslint-plugin-codegen

Then add the plugin and rule to your eslint config, for example in eslintrc.js:

module.exports = {
  //...
  plugins: [
    // ...
    'codegen',
  ],
  rules: {
    // ...
    'codegen/codegen': 'error',
  },
}

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):

{
  "eslint.validate": ["markdown", "javascript", "typescript"],
  "editor.codeActionsOnSave": {
    "source.fixAll.eslint": true
  }
}

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.

Usage with eslint-plugin-markdown

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.

module.exports = {
  plugins: ['markdown', 'codegen'],
  extends: ['plugin:markdown/recommended', 'plugin:codegen/recommended'],
}

Or specify the processor explicitly - when you switch to flat config this will be required:

module.exports = {
  // 1. Add the plugin.
  plugins: ['markdown'],
  overrides: [
    {
      // 2. Enable the Markdown processor for all .md files.
      files: ['**/*.md'],
      processor: 'codegen/processor', // NOT 'markdown/markdown'
    },
  ],
}

Presets

barrel

Bundle several modules into a single convenient one.

Example
// codegen:start {preset: barrel, include: some/path/*.ts, exclude: some/path/*util.ts}
export * from './some/path/module-a'
export * from './some/path/module-b'
export * from './some/path/module-c'
// codegen:end
Params
namedescription
includeoptional If specified, the barrel will only include file paths that match this glob pattern
excludeoptional If specified, the barrel will exclude file paths that match these glob patterns
importoptional If specified, matching files will be imported and re-exported rather than directly exportedwith export * from './xyz'. Use import: star for import * as xyz from './xyz' style imports.Use import: default for import xyz from './xyz' style imports.
exportoptional 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 setto {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.
extensionoptional 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.
Demo

npm.io

custom

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:

Example
import {Preset} from 'eslint-plugin-codegen'

export const jsonPrinter: Preset<{myCustomProp: string}> = ({meta, options}) => {
  const components = meta.glob('**\/*.tsx') // uses 'globSync' from glob package
  return `filename: ${meta.filename}\ncustom prop: ${options.myCustomProp}\nComponent paths: ${components.join(', ')}`
}

This can be used with:

<!-- codegen:start {preset: custom, source: ./lib/my-custom-preset.js, export: jsonPrinter, myCustomProp: hello} Note that a glob helper method is passed to the preset via meta. This uses the globSync method of https://npm.im/glob. There are also fs and path helpers passed, corresponding to those node modules respectively. These can be useful to allow access to those libraries without them being production dependencies.

Params
namedescription
sourceRelative path to the module containing the custom preset. Default: the file being linted.
exportThe name of the export. If omitted, the module's default export should be a preset function.
requireA module to load before source. If not set, defaults to ts-node/register/transpile-only for typescript sources.
devSet 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.
Demo

npm.io

markdownFromJsdoc

Convert jsdoc for an es export from a javascript/typescript file to markdown.

Example

<!-- codegen:start {preset: markdownFromJsdoc, source: src/foo.ts, export: bar} -->

Params
namedescription
source{string} relative file path containing the export with jsdoc that should be copied to markdown
export{string} the name of the export
headerLevel{12345} Determines if the export will correspond to a H1, H2, H3, H4 or H5. Nested headers will increment from this value. @default 4

monorepoTOC

Generate a table of contents for a monorepo.

Example (basic)

<!-- codegen:start {preset: monorepoTOC} -->

Example (using config options)

<!-- codegen:start {preset: monorepoTOC, repoRoot: .., workspaces: lerna, filter: {package.name: foo}, sort: -readme.length} -->

Params
namedescription
repoRootoptional the relative path to the root of the git repository. By default, searches parent directories for a package.json to find the "root".
filteroptional 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")
sortoptional 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)
Demo

npm.io

markdownFromJsdoc

Convert jsdoc to an es export from a javascript/typescript file to markdown.

Example

<!-- codegen:start {preset: markdownFromJsdoc, source: src/foo.ts, export: bar} -->

Params
namedescription
source{string} relative file path containing the export with jsdoc that should be copied to markdown
export{string} the name of the export
Demo

npm.io

markdownTOC

Generate a table of contents from the current markdown file, based on markdown headers (e.g. ### My section title)

Example

<!-- codegen:start {preset: markdownTOC, minDepth: 2, maxDepth: 5} -->

Params
namedescription
minDepthexclude headers with lower "depth". e.g. if set to 2, # H1 would be excluded but ## H2 would be included. @default 2
maxDepthexclude headers with higher "depth". e.g. if set to 3, #### H4 would be excluded but ### H3 would be included. @default Infinity
Demo

npm.io

markdownFromTests

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.

Example

<!-- codegen:start {preset: markdownFromTests, source: test/foo.test.ts, headerLevel: 3} -->

Params
namedescription
sourcethe test file
headerLevelThe number of # characters to prefix each title with
Demo

npm.io

labeler

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.

Example
# 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:

  • add '!.github' to your .eslintignore file, or the ignorePatterns property in your lint config.
  • {vscode} add "yaml" to the "eslint.validate" list in vscode/settings.json.
  • {@typescript/eslint} add '.yml' (and/or '.yaml') to the parserOptions.extraFileExtensions list in your lint config.
  • {@typescript/eslint} explicitly include 'hidden' files (with paths starting with .) in your tsconfig. See https://github.com/mmkal/ts/tree/main/tsconfig.eslint.json for an example.
Params
namedescription
repoRootoptional path to the repository root. If not specified, the rule will recursively search parent directories for package.json files
Demo

npm.io

Customisation

In addition to the custom preset, you can also define your own presets in eslint configuration, e.g.:

module.exports = {
  // ...
  plugins: [
    // ...
    'codegen',
  ],
  rules: {
    // ...
    'codegen/codegen': ['error', {presets: require('./my-custom-presets')}],
  },
}

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)
 <!-- codegen:end -->

my-custom-presets.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:

module.exports = {
  // ...
  plugins: [
    // ...
    'codegen',
  ],
  rules: {
    // ...
    'codegen/codegen': ['error', {presets: require('./my-custom-presets')}],
  },
}

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

0.27.1-0

1 month ago

0.28.0

1 month ago

0.27.0

1 month ago

0.27.0-1

1 month ago

0.27.0-2

1 month ago

0.26.0

2 months ago

0.25.0

2 months ago

0.24.1

2 months ago

0.24.0

2 months ago

0.23.0

3 months ago

0.22.1

3 months ago

0.22.0

3 months ago

0.19.0-5

5 months ago

0.20.1

5 months ago

0.20.0

5 months ago

0.19.0

5 months ago

0.19.0-0

5 months ago

0.19.0-3

5 months ago

0.19.0-4

5 months ago

0.19.0-1

5 months ago

0.19.0-2

5 months ago

0.18.0-0

6 months ago

0.18.0-2

6 months ago

0.18.0-3

6 months ago

0.18.0-4

6 months ago

0.18.0-5

6 months ago

0.21.0

5 months ago

0.18.1

6 months ago

0.18.0

6 months ago

0.20.0-0

5 months ago

0.20.0-1

5 months ago

0.17.0

1 year ago

0.16.1

3 years ago

0.16.0

3 years ago

0.15.0

3 years ago

0.14.4

3 years ago

0.14.3

3 years ago

0.14.2

3 years ago

0.14.1

3 years ago

0.14.0

3 years ago

0.13.3

4 years ago

0.13.2

4 years ago

0.13.1

4 years ago

0.13.0

4 years ago

0.12.3

4 years ago

0.12.2

4 years ago

0.12.1

4 years ago

0.12.0

4 years ago

0.11.3

4 years ago

0.11.4

4 years ago

0.11.2

4 years ago

0.11.0

4 years ago

0.11.1

4 years ago

0.10.0

4 years ago

0.9.0

4 years ago

0.8.8

4 years ago

0.8.7

4 years ago

0.8.6

4 years ago

0.8.5

4 years ago

0.8.4

4 years ago

0.8.1

4 years ago

0.8.3

4 years ago

0.8.2

4 years ago

0.8.0

4 years ago

0.7.2

4 years ago

0.7.1

4 years ago

0.6.2

4 years ago

0.7.0

4 years ago

0.6.1

4 years ago

0.6.0

4 years ago

0.5.0

4 years ago

0.4.1

4 years ago

0.4.0

4 years ago

0.3.0

4 years ago

0.2.0

4 years ago