@homer0/eslint-plugin

12.0.7 • Public • Published

eslint-plugin-homer0

Dependencies status

My custom configurations for ESLint

Motivation

Let's start with... I LOVE ESLint and I use it on every project.

Together with ESLint, I use the Airbnb's plugin, eslint-plugin-import and eslint-plugin-node.

Everything is great, but...I'm always overwriting the same rules, and every time one the packages mentioned above is released, I have to go and update every single project.

So, I decided to make my own plugin, with the configurations I use.

Usage

Just include the plugin and extend from the configuration you want to use:

{
  "root": true,
  "plugins": ["homer0"],
  "extends": ["plugin:homer0/node"]
}

Available configurations

There are a few different configurations that can be used, dependending on the project and/or the scope:

TypeScript

A base configuration that uses @typescript-eslint/parser and @typescript-eslint/eslint-plugin. It extends plugin:@typescript-eslint/recommended, and it's prepared to work with a .eslintrc.js config file.

Name: plugin:homer0/typescript

Node

This is a basic configuration for Node that extends from plugin:node/recommended.

Name: plugin:homer0/node

Node with TypeScript

It's a merge of the TypeScript and Node configurations, with a few extra overrides specifics to Node.

Name: plugin:homer0/node-typescript

Browser

This extends the base rules and disables a few ones from the import plugin, as most of the browser code I wrote goes through transpilation, so there's no need to validate production/development dependencies.

Name: plugin:homer0/browser

Jest

Jest is my favorite tool for testing, so all my projects (with test) use it. This configuration not only changes the environment to jest but it also disables a few rules from the import plugin.

Name: plugin:homer0/jest

Jest with Node and TypeScript

Instead of using the base configuration like the Jest configuration, this one uses Node with TypeScript as a base, since it requires a few changes in the root level.

Name: plugin:homer0/jest-node-typescript

JSDoc

I'm a big fan of documenting EVERY class, method/function, property, etc; so this configuration helps me validate that all my comments are valid JSDoc.

Name: plugin:homer0/jsdoc

React

A configuration for React projects, based on the react and react-hooks plugins. It extends from the browser configuration as a base, then adds plugin:react/recommended and plugin:react-hooks/recommended, and a few extra rules based on my experience.

Svelte

Specific for Svelte (v3 (for now)) projects, uses svelte plugin, the browser configuration as a base, and extends plugin:svelte/recommended, with a few extra rules based on my experience.

(Internal) NextBase

A base configuration to use with Next.js projects with TypeScript. This is an internal configuration because it's meant to be used with the Next.js preset.

Name: plugin:homer0/next-base

With Prettier

Most of the configuartions have a -with-prettier version that includes the prettier plugin, turning off the rules that conflict with it.

Configuration Prettier version
browser browser-with-prettier
jest jest-with-prettier
node node-with-prettier
node-typescript node-typescript-with-prettier
jest-node-typescript jest-node-typescript-with-prettier
react react-with-prettier
svelte svelte-with-prettier

Presets

Presets are a way to generate ESLint configurations that use the plugin by passing a few options.

Next.js

Allows you to generate a configuration for a Next.js project with TypeScript:

// .eslintrc.js in the root of the project
const preset = require('@homer0/eslint-plugin/presets/next');

module.exports = preset({
  // The directory where the `tsconfig` is located.
  rootDir: __dirname,
  // A list of extra configurations to extend. They'll be added at the end of the list
  // (after the one from next and the one from this plugin).
  configs: [],
  // The name of the `tsconfig`.
  tsConfig: 'tsconfig.json',
  // The source type for the `tsconfig`.
  sourceType: 'module',
  // The name of the Next.js config to extend.
  nextConfig: 'next/core-web-vitals',
  // The filter for JS files, like `next.config.js` that should be linted with `espree`.
  jsFilter: ['*.js'],
  // Whether or not the config is root.
  root: true,
  // Whether or not to use the prettier variant of this plugin base config.
  prettier: true,
});

TypeScript

Generates a base configuration for TypeScript projects:

// .eslintrc.js in the root of the project
const preset = require('@homer0/eslint-plugin/presets/typescript');

module.exports = preset({
  // The directory where the `tsconfig` is located.
  rootDir: __dirname,
  // A list of extra configurations to extend. They'll be added at the end of the list
  // (after the one from next and the one from this plugin).
  configs: [],
  // The name of the `tsconfig`.
  tsConfig: 'tsconfig.json',
  // The source type for the `tsconfig`.
  sourceType: 'module',
  // Whether or not the config is root.
  root: true,
});

Rules

Here's a list of the rules I modified and the reason I did it:

Best practices

off -> warn.

I want the linter to warn me if I'm writing big function/methods.

error -> off

I write almost everything using OOP, and some times I want to create a method to extract some logic and I don't want to be restricted by this rule.

off -> ['error', { ignore: [0, 1, -1, 60, 1000]}]

I like self explanatory code, and magic numbers get in the way of that. The reason I left those 4 "ignore cases" is because...

  • 0 and 1: To work with arrays. Examples: To check if an array is empty or not, and to check if an array has a single element or more.
  • -1: To work with indexOf.
  • 60 and 1000: To work with timestamps.

Now, this rule is disabled on the jest config, as it's not uncommon to use random numbers for "fake data" or to check calls with .toHaveBeenCalledTimes().

['error', {...}] -> error

I don't believe this is a good practice (as explained by the rule) and I prefer having to add an exception in the cases I need to use instead of having a lot of exceptions by default.

off -> error

No idea why do they disabled this rule, it's a great help to avoid infinite loops.

off -> error

I haven't use call or apply for years now, as I believe that with ES6 you can cover 99% of the cases in which they were useful. Now, I enabled this rule because if I can't prevent their use, I want to make it as restricted as possible.

error -> off

Not all the time, but I use regular expressions quite a lot, and I have found cases in which this rule is unnecessarily triggered.

Node

off -> ['error', '^(err|error|\\w+Error)$']

I consider error handling a "required good practice" on every project.

error -> off

You can't tell ESLint which part of the project is tooling for development and which part is for production, and I don't want to have to create different directories with other .eslintrc for this... at least for now.

off -> error

It helps to organize the code a little bit.

off -> error

No, I'm not crazy and is not that I don't use process.env. The reason I enabled this rule is because I think environment variables should be consumed on a single place, instead of reading them all over the project.

Instead of having the rule disable for all files, I prefer writing the "disable comment" on a single file.

error -> off

I don't get how this rule works...it gets triggered for some packages that comply with all the requirements a "published package" must have.

error -> off

This is because is not unusual for me to write .sh or extension-less files with the Node shebang...which triggers the rule.

Style

off -> ['error', 'consistent']

Consistency.

['error', 2, {...}] -> ['error', 2, {..., MemberExpression: 0 }]

I don't like to indent when there's really no need for it:

// Bad
something()
  .then(() => ...);

// Good
something()
.then(() => ...);

['error', {...}] -> off

Most of the times, I write JSDoc block comments between methods, and I consider that is enough "separation".

off -> ['error', { max: 1 }]

Readability

error -> off

Yes, the reason for having this rule enabled is valid, but I don't believe that's a very common case.

['error', {
  allow: [],
  allowAfterThis: false,
  allowAfterSuper: false,
  enforceInMethodNames: true,
}]

->

['error', {
  allowAfterThis: true,
  allowAfterSuper: true,
  enforceInMethodNames: false,
}]

Since I don't use TypeScript, I follow the convention of prefixing protected/private property/methods with underscore.

['error', 'before', {...}] -> ['error', 'after', {...}]

I know that having the operators at the beginning of the line may better for readability for some people, but in my case, I find it easier to read if they are at the end. Yes, I may be that I've been using it like this for years.

JSDoc

These are not overwrites, the plugin I use doesn't have a default preset, so I'm just going to explain why did enabled each rule and what values they have.

'error'

Since most of the things I write are classes, I use the @access tag a lot, and this rule is more about protecting you from typos and invalid values than a restriction.

'error'

It's not uncommon for IDEs/editors to "auto align" JSDoc blocks when you copy them and mess the alignment of the asterisks.

['error', { allowExtraTrailingParamDocs: true }]

This is a big helper for when you update a function/method signature, you won't forget to update the JSDoc block.

The allowExtraTrailingParamDocs option is enabled so you won't get errors when documenting parameters for abstract methods.

'error'

It helps you prevent duplicated properties and avoid incomplete declarations.

'error'

Helps avoid invalid syntax.

['error', {
  definedTags: ['parent'],
}]

This rule prevents the use of invalid JSDoc tags; the definedTags is used to add the following exceptions:

  • parent: I use it as an alias of memberof to be able use module: and avoid issues with the plugin. I transform it to memberof using the jsdoc-ts-utils when generating the JSDoc site.

'error'

Helps with consistency as it forces you to always use the same casing for native types.

'error'

The @implements tag should only be used on classes.

'error'

Consistency.

['error', { checkConstructors: false }]

Everything should have a description.

The checkConstructors option is to avoid comments like "class constructor".

'error'

All descriptions should formatted as sentences.

['error', 'never']

I never used, and I don't like, hyphens as separators on JSDoc blocks.

['error', {
  require: {
    allowAfterThis: true,
    ArrowFunctionExpression: true,
    ClassDeclaration: true,
    ClassExpression: true,
    FunctionDeclaration: true,
    MethodDefinition: true,
  },
  exemptEmptyConstructors: true,
}]

EVERYTHING should be documented!

The require option is so the rule will be applied to all available contexts, and exemptEmptyConstructors is so it will skip constructors with no pameters.

'error'

Yes, every parameter should be documented.

'error'

Yes, every parameter should have a human-readable description.

'error'

I'm still not sure why this rule even exists, it should be part of jsdoc/require-param.

'error'

Of course every parameter should have its type documented.

'error'

The type Object is like the any on TypeScript, it doesn't say anything; if you are going to use it, you should documented the properties too.

'error'

Yes, every property should have a human-readable description.

'error'

Like jsdoc/require-param-name, this shouldn't exist.

'error'

Yes, the types should be documented.

'error'

Yes, the @returns tag is as importan as @param.

'error'

I like this rule because is not that it validates the comments based on the code, but it's the other way around: if you use @returns, the function/method should have a return (it's only logical).

'error'

Yes, if there's a @returns, it should have a type.

'error'

This helps a lot when writing error handling code.

'error'

Prevents invalid definitions.

TypeScript

['error', { ignores: ['modules'] }]

Because TypeScript uses ES syntax.

off

Like the version for require, not sure how the rule works.

off

It's an overkill.

off

Because TypeScript uses ES syntax, and the only case in which I would consider to enforce this rule would be CommonJS, where it's actual a destructuring assignment.

off

I enforce this rule when using JSDoc, as the blocks work as separators between the methods. When working with TypeScript, I don't use that much JSDoc, so I want to be able to leave an empty line between the methods.

off

Empty function are a common resource for optional paramters/properties.

off

There are some cases, mostly on frontend, where you know the state is non-null, and I don't want to have to use optional chaining for EVERYTHING.

['error', {
  require: {
    'ts-expect-error': 'allow-with-description',
    'ts-ignore': 'allow-with-description',
    'ts-nocheck': 'allow-with-description',
    'ts-check': 'allow-with-description',
  },
  exemptEmptyConstructors: true,
}]

I'm allowed to ignore TypeScript... with a valid excuse.

Development

Tasks

Task Description
lint Lint the modified files.
lint:all Lint the project code.
todo List all the pending to-do's.

Repository hooks

I use husky to automatically install the repository hooks so the code will be tested and linted before any commit and the dependencies updated after every merge.

The configuration is on the husky property of the package.json and the hooks' files are on ./utils/hooks.

Commits convention

I use conventional commits with commitizen in order to support semantic releases. The one that sets it up is actually husky, that installs a script that runs commitizen on the git commit command.

The hook for this is on ./utils/hooks/prepare-commit-msg and the configuration for comitizen is on the config.commitizen property of the package.json.

/@homer0/eslint-plugin/

    Package Sidebar

    Install

    npm i @homer0/eslint-plugin

    Weekly Downloads

    13

    Version

    12.0.7

    License

    MIT

    Unpacked Size

    55.7 kB

    Total Files

    31

    Last publish

    Collaborators

    • homer0