eslint icon indicating copy to clipboard operation
eslint copied to clipboard

Change Request: Autogenerated type definitions for rules

Open nzakas opened this issue 1 year ago • 19 comments

ESLint version

HEAD

What problem do you want to solve?

We inherited a lot of hardcoded rule configuration types from @types/eslint, but these were all handcoded and we've already seen that they are out of date. (https://github.com/eslint/eslint/pull/18902, https://github.com/eslint/eslint/pull/18906, https://github.com/eslint/eslint/pull/18905, https://github.com/eslint/eslint/pull/18903, https://github.com/eslint/eslint/pull/18901). Keeping the types in sync with rules is going to be a problem going forward.

What do you think is the correct solution?

We should create a script that automatically generates the rule config types based on the rule schemas.

Participation

  • [ ] I am willing to submit a pull request for this change.

Additional comments

Looking for a volunteer for this.

nzakas avatar Sep 16 '24 14:09 nzakas

Writing a script to generate types might be a better approach. I've made a basic implementation. Could you review it to see if it aligns with what you were expecting? If so, I would like to extend it to support other schema types as well. https://replit.com/@AmareshSuriya/Autogenerated-type-definitions

amareshsm avatar Sep 17 '24 00:09 amareshsm

Writing a script to generate types might be a better approach.

I'm a bit confused, how is that different than what I said? "We should create a script that automatically generates the rule config types based on the rule schemas."

https://replit.com/@AmareshSuriya/Autogenerated-type-definitions

This looks pretty good! I don't think we need to have the separate files (best-practices.d.ts), we can just put everything into a rules.d.ts.

And we should run this on precommit to make sure new changes are always updated.

nzakas avatar Sep 17 '24 15:09 nzakas

Writing a script to generate types might be a better approach.

I'm a bit confused, how is that different than what I said? "We should create a script that automatically generates the rule config types based on the rule schemas."

I was trying to say I agree with you like ya writing a script..

amareshsm avatar Sep 17 '24 19:09 amareshsm

This looks pretty good! I don't think we need to have the separate files (best-practices.d.ts), we can just put everything into a rules.d.ts.

And we should run this on precommit to make sure new changes are always updated.

Sure. I will look at this. I will to extend it to support other schema types as well.

amareshsm avatar Sep 17 '24 19:09 amareshsm

The typescript-eslint team has an internal package which may be leveraged.

DMartens avatar Sep 18 '24 10:09 DMartens

Speaking as a typescript-eslint team member: that internal package could probably be split out into something more public. We'd be happy to take an issue if folks would find it useful!

Speaking just in general: I wonder if there's something that already exists & can be pulled in? "AJV schema to TypeScript types" feels like a general problem other folks would have wanted solved by now.

  • https://github.com/ajv-validator/ajv-cli/issues/192 mentions this.
  • https://www.npmjs.com/package/json-schema-to-typescript looks pretty promising itself.

JoshuaKGoldberg avatar Sep 21 '24 01:09 JoshuaKGoldberg

I tried using typescript-eslint's internal package with ESLint rules. It creates type declarations for rule options as TypeScript code (no syntax tree) without JSDoc comments. For instance, this is what the output for the no-unused-vars rule looks like:

type Options = [
  | "all"
  | "local"
  | {
      args?: "after-used" | "all" | "none";
      argsIgnorePattern?: string;
      caughtErrors?: "all" | "none";
      caughtErrorsIgnorePattern?: string;
      destructuredArrayIgnorePattern?: string;
      ignoreClassWithStaticInitBlock?: boolean;
      ignoreRestSiblings?: boolean;
      reportUsedIgnorePattern?: boolean;
      vars?: "all" | "local";
      varsIgnorePattern?: string;
    },
];

Compare this to how the rule type is currently defined (which is also roughly what should be autogenerated):

https://github.com/eslint/eslint/blob/064c8b612e2e4b773d6b25867f2045e3ceaa9d66/lib/types/rules/variables.d.ts#L159-L203

For reference, here is the code I run to get the output from @typescript-eslint/rule-schema-to-typescript-types after building the package locally:

const { compile } = require("@typescript-eslint/rule-schema-to-typescript-types");
const path = require("node:path");

const eslintRulesPath = path.join(require.resolve("eslint"), "../rules");
const filepath = path.join(eslintRulesPath, "no-unused-vars.js");
const rule = require(filepath);

compile(rule.meta.schema, { filepath: ".ts" })
    .then(text => console.log(text));

fasttime avatar Dec 06 '24 19:12 fasttime

I'm not sure how valuable the comments are. I think it's much more important that the types are up-to-date.

nzakas avatar Dec 06 '24 21:12 nzakas

Speaking as a typescript-eslint team member: that internal package could probably be split out into something more public.

Another problem of @typescript-eslint/rule-schema-to-typescript-types is that it doesn't support all rule schemas used in ESLint. For example, it runs into an error when trying to generate option types for logical-assignment-operators:

.../typescript-eslint/packages/rule-schema-to-typescript-types/dist/generateType.js:60
        throw new errors_1.NotSupportedError('untyped schemas without one of [$ref, enum, oneOf]', schema);
              ^

NotSupportedError: Generating a type for untyped schemas without one of [$ref, enum, oneOf] is not currently supported:
{
  "items": [
    {
      "const": "always"
    },
    {
      "type": "object",
      "properties": {
        "enforceForIfStatements": {
          "type": "boolean"
        }
      },
      "additionalProperties": false
    }
  ],
  "minItems": 0,
  "maxItems": 2
}
    at generateType (.../typescript-eslint/packages/rule-schema-to-typescript-types/dist/generateType.js:60:15)
    at .../typescript-eslint/packages/rule-schema-to-typescript-types/dist/generateUnionType.js:31:60
    at generateUnionType (.../typescript-eslint/packages/rule-schema-to-typescript-types/dist/generateUnionType.js:33:11)
    at generateType (.../typescript-eslint/packages/rule-schema-to-typescript-types/dist/generateType.js:55:58)
    at compileSchema (.../typescript-eslint/packages/rule-schema-to-typescript-types/dist/index.js:69:50)
    at compile (.../typescript-eslint/packages/rule-schema-to-typescript-types/dist/index.js:31:24)
    at Object.<anonymous> (.../test/test.js:10:1)
    at Module._compile (node:internal/modules/cjs/loader:1565:14)
    at Object..js (node:internal/modules/cjs/loader:1708:10)
    at Module.load (node:internal/modules/cjs/loader:1318:32)

Node.js v22.12.0

It's an interesting tool anyway, so we could fork it and update it to make it work for us. Not sure about making it a public package as that would be clearly more work.

https://www.npmjs.com/package/json-schema-to-typescript looks pretty promising itself.

Could it be that that tool simply ignores schemas it doesn't understand rather than throwing an error? Here is the whole output produced by json-schema-to-typescript for the complexity rule:

/* eslint-disable */
/**
 * This file was automatically generated by json-schema-to-typescript.
 * DO NOT MODIFY IT BY HAND. Instead, modify the source JSONSchema file,
 * and run json-schema-to-typescript to regenerate this file.
 */

For most other rules the output is disappointing as well, with unknown types, unions of empty tuples, etc.

fasttime avatar Dec 07 '24 13:12 fasttime

I just filed https://github.com/typescript-eslint/typescript-eslint/issues/10469 to track making @typescript-eslint/rule-schema-to-typescript-types public.

comments

Having at least the @default tags added is a nice idea... I think I'd like that in typescript-eslint too. 🙂

JoshuaKGoldberg avatar Dec 07 '24 14:12 JoshuaKGoldberg

@amareshsm are you still working on this?

nzakas avatar May 09 '25 15:05 nzakas

@amareshsm are you still working on this?

Apologies for this. I had started working on it but didn’t get the time to complete the implementation, so I’ve unassigned it from myself now.

amareshsm avatar May 12 '25 22:05 amareshsm

I will take this.

Tanujkanti4441 avatar May 13 '25 05:05 Tanujkanti4441

@Tanujkanti4441 are you still working on this?

nzakas avatar Jun 19 '25 19:06 nzakas

@Tanujkanti4441 ping

nzakas avatar Jul 10 '25 20:07 nzakas

Sorry for the delays, somehow missed this, got engaged in other PR's and stuffs, working on it.

Thanks!

Tanujkanti4441 avatar Aug 14 '25 14:08 Tanujkanti4441

Thanks for the update @Tanujkanti4441. When do you think you'll be able to open a pull request?

fasttime avatar Aug 20 '25 20:08 fasttime

Thanks for the update @Tanujkanti4441. When do you think you'll be able to open a pull request?

Within a week i guess.

Tanujkanti4441 avatar Aug 23 '25 18:08 Tanujkanti4441

👋 Hi! This issue is being addressed in pull request https://github.com/eslint/eslint/pull/20061. Thanks, @Tanujkanti4441!

eslint-github-bot[bot] avatar Aug 31 '25 15:08 eslint-github-bot[bot]