7

I followed the typescript-eslint tutorial for writing custom ESLint rules for TypeScript in TypeScript. I can easily run the unit tests that I wrote (also in TS) with ts-node, but I'm stuck at how to add this rule to my project's .eslintrc config file.

I tried using a few ESLint plugins to import local rules (e.g., eslint-plugin-local), but these do not seem to support rules written in TS out of the box. Do I have to transpile it to JS first? (if so, how exactly) Is there a way to add the rule without transpilation?

1 Answer 1

8
+50

I also recently built a custom eslint plugin ( as part of monorepo ) in typescript and faced a similar issue while integrating into the apps within monorepo and vscode. To solve this I used ts-node register function, which takes care of running the typescript without compiling it.

// rules/customRule.ts
import { ESLintUtils } from '@typescript-eslint/utils';

const rule = ESLintUtils.RuleCreator.withoutDocs({
  create(context) {
    // ...
  },
  meta: {
    // ...
  },
});

export default rule;

// index.ts
import customRule from './rules/customRule';

const rules = {
  "custom-rule": customRule,
};

export default rules;

// index.js

/**
* This takes care of running the ts file with commonjs without running a
* compilation process first.
*/
require('ts-node').register()

const pkg = require('./package.json')

const rules = require('./index.ts').default

const pluginName = pkg.name.replace('eslint-plugin-', '')

module.exports = {
  rules: rules,
  configs: {
    recommended: {
      plugins: [pluginName],
      rules: {
        ....
      }
    }
  }
}

This way, you can now use this plugin within your application by adding this package to the application and eslintrc file. Couple of things you need to take care which are as follows:

  1. In the tsconfig.json file of application, you need to ensure that ts-node is running in commonjs. And also if you are using node APIs in the custom eslint plugin, add @types/node to the dev dependencies of the custom plugin package.json.
// tsconfig.json
{
  "extends": "tsconfig/base.json",
  "compilerOptions": {
    "baseUrl": "./src",
    "outDir": "./build",
    "module": "esnext",
    "noEmit": true,
    .....
  },
  // Added this for typescript file used in custom eslint plugin
  "ts-node": {
    "compilerOptions": {
      "module": "commonjs",
      "types": ["node"]
    },
    "swc": true,
  },
  "include": ["src/**/*", "cypress/**/*", "./global.d.ts"]
}
  1. If in case you are using pnpm as your package manager and hoisting all your eslint packages to root using public-hoist pattern in npmrc; then you need to add your custom eslint plugin package to root package.json of monorepo for it to work otherwise eslint won't be able to find the custom plugin. More details here
{
  "devDependencies": {
    "eslint-plugin-custom": "link:packages/eslint-plugin-custom"
  }
}
Sign up to request clarification or add additional context in comments.

2 Comments

Thanks for this, this has gotten me so close! I'm also in a pnpm monorepo and npx eslint /path/to/any/file.ts works when I'm in the same directory as the eslint plugin, but if I'm somewhere else in the monorepo and run npx eslint /same/path/file.ts, I get error TS6046: Argument for '--module' option must be: 'none', 'commonjs', 'amd', 'system', 'umd', 'es6', 'es2015', 'es2020', 'es2022', 'esnext', 'node12', 'nodenext'. What might be causing ts-node to use a different module depending on what directory I'm in?
Solution to the above: require('ts-node').register({ project: join(__dirname, 'tsconfig.json') }) (after importing join from Node's path module)! That ensures that no matter where the plugin is invoked from, it finds the same tsconfig.json. Hooray!

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.