Skip to content

RFC: Common format to specify a type or value as a rule option #5271

Closed
@JoshuaKGoldberg

Description

@JoshuaKGoldberg

Overview

There are quite a few times when users have a real need to change or disable a rule for specifically some package exports:

This RFC proposes a unified format that all rules can use to specify some types or values from the global namespace and/or packages.

Prior Art

Some rules have previously created name-based overrides, such as typesToIgnore from no-unnecessary-type-assertion. Those are sub-optimal:

  • Names have conflicts. If you configure a rule for, say, all functions of a particular name that a package exports, you might also happen to ignore the rule for your internal functions of the same name.
  • It may be necessary to configure a rule for all values of a particular type, regardless of their name.

For example, @typescript-eslint/no-floating-promises even has a void operator to get around needing to disable the rule for specific cases. It's the workaround we currently recommend for issues such as #5231.

Proposal

The current proposal is for specifying the following types of sources, in increasing order of distance from a line of code:

  • File: the current file or another specific file in your package (often exported as a module, but not necessarily)
  • Lib: globals such as from lib.d.ts or lib.dom.d.ts
  • Package: an export from a package, such as "lodash"

Additionally, you can specify multiple of these at the same time with type many.

interface FileSpecifier {
  from: "file";
  name: string | string[];
  source?: string;
}

interface LibSpecifier {
  from: "lib";
  name: string | string[];
}

interface PackageSpecifier {
  from: "package";
  name: string | string[];
  source: string;
}

interface ManySpecifiers {
  from: EntitySpecifierFrom[];
  name: string;
}

type EntitySpecifierFrom = "file" | "lib" | "package";

type EntitySpecifier =
  | string
  | FileSpecifier
  | LibSpecifier
  | PackageSpecifier
  | ManySpecifiers;

An example of how this might look in practice:

export default {
  rules: {
    // https://github.com/typescript-eslint/typescript-eslint/issues/5231
    "@typescript-eslint/no-floating-promises": ["error", {
      "ignore": [
        {
          "from": "node:test",
          "name": ["it", "test"]
        }
      ]
    }],
  }
}

See #5271 (comment) and following comments for more context.

Original format, not rich enough

I propose the format look be able to specify whether the item is from the global namespace (i.e. globalThis, window) or a module (e.g. "node:test", "./path/to/file"):

interface GlobalSpecifier {
  name: string;
}

interface ModuleSpecifier {
  export: string;
  module: string;
}

type Specifier = GlobalSpecifier | ModuleSpecifier;

Examples

For example, specifying the global location object:

{
  "name": "location"
}

Instead specifying the global window.location object:

{
  "name": "window.location"
}

Specifying the test export from the node:test module:

{
  "export": "test",
  "module": "node:test"
}

Specify the SafePromise export from some ~/utils/promises module (presumably configured by TSConfig paths to point to a file with a location like ./src/utils/promises.ts):

{
  "export": "SafePromise",
  "module": "~/utils/promises"
}

Specify the SafePromise export from some my-safe-promises (TODO: find some unused or useful name) module:

{
  "export": "SafePromise",
  "module": "safe-promises"
}

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions