Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: add types for flat config files #7273

Merged
merged 3 commits into from Nov 13, 2023
Merged

Conversation

bradzacher
Copy link
Member

@bradzacher bradzacher commented Jul 19, 2023

Overview

Adds in strict type definitions for the new "flat config" style config.
I took the time to do a little bit of refactoring to attempt to share as many types as possible and better organise things.
In particular I tried to move things a little so everything didn't hang off the Linter namespace like before.

I believe I got everything right - it was pieced together from the docs. Sadly there is no json schema like with classic configs and the validation function is quite loose - so some of it is pieced together based on past config structure.
Feel free to eyeball the docs and compare them to make sure I got it right.

This change should be completely backwards-compatible - please flag if you think I made any incompatible changes though.

TODO - https://eslint.org/docs/latest/extend/plugins#metadata-in-plugins

@bradzacher bradzacher added the enhancement New feature or request label Jul 19, 2023
@typescript-eslint
Copy link
Contributor

Thanks for the PR, @bradzacher!

typescript-eslint is a 100% community driven project, and we are incredibly grateful that you are contributing to that community.

The core maintainers work on this in their personal time, so please understand that it may not be possible for them to review your work immediately.

Thanks again!


🙏 Please, if you or your company is finding typescript-eslint valuable, help us sustain the project by sponsoring it transparently on https://opencollective.com/typescript-eslint.

@netlify
Copy link

netlify bot commented Jul 19, 2023

Deploy Preview for typescript-eslint ready!

Name Link
🔨 Latest commit e63dfb9
🔍 Latest deploy log https://app.netlify.com/sites/typescript-eslint/deploys/65511358cecdb5000832db2c
😎 Deploy Preview https://deploy-preview-7273--typescript-eslint.netlify.app
📱 Preview on mobile
Toggle QR Code...

QR Code

Use your smartphone camera to open QR code link.
Lighthouse
Lighthouse
1 paths audited
Performance: 99 (no change from production)
Accessibility: 100 (no change from production)
Best Practices: 92 (no change from production)
SEO: 98 (no change from production)
PWA: 80 (no change from production)
View the detailed breakdown and full score reports

To edit notification comments on pull requests, go to your Netlify site configuration.

@nx-cloud
Copy link

nx-cloud bot commented Jul 19, 2023

☁️ Nx Cloud Report

CI is running/has finished running commands for commit 96b6e74. As they complete they will appear below. Click to see the status, the terminal output, and the build insights.

📂 See all runs for this branch


✅ Successfully ran 35 targets

Sent with 💌 from NxCloud.

@bradzacher bradzacher force-pushed the flat-config-types branch 4 times, most recently from 68e4da9 to 96b6e74 Compare July 20, 2023 12:00
Copy link
Member

@JoshuaKGoldberg JoshuaKGoldberg left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

OK! From what I can tell, this is a very accurate and good representation of how things will be in the new world. But, as you mentioned in the description, this is all conjecture and guessing. I think we'd want some response to eslint/eslint#17391 eslint/eslint#17242 and/or an explicit review from someone on ESLint core. 🤷

packages/utils/src/ts-eslint/Parser.ts Outdated Show resolved Hide resolved
@JoshuaKGoldberg
Copy link
Member

@bradzacher now that eslint/eslint#17640 is merged, are there any changes you want to make here?

@karlhorky
Copy link

karlhorky commented Nov 11, 2023

@JoshuaKGoldberg @bradzacher while we wait for the PR merge + release, is it possible to publish a WIP version of this to npm? Or is there another easy way for package authors to help test out these new types in their projects? (before this PR is merged)

Eg. some prerelease tag like 7.0.0-flat-config-types.0 or similar?

@karlhorky
Copy link

karlhorky commented Nov 11, 2023

Workaround

For now, I'm using @types/eslint, which has the Linter.FlatConfig export:

/** @type {import('eslint').Linter.FlatConfig} */
const config = {
};

export default config;

Types aren't as nice as the @typescript-eslint/util types, but it works so far for now...

@JoshuaKGoldberg
Copy link
Member

Or is there another easy way for package authors to help test out these new types in their projects? (before this PR is merged)

https://typescript-eslint.io/contributing/local-development -> https://typescript-eslint.io/contributing/local-development/local-linking would be the right way.

Looks like this has some merge conflicts right now. Were it not for those and builds on main being broken (#7899) I'd merge as-is.

@bradzacher
Copy link
Member Author

One of the reasons I hadn't pushed forward with this was I was trying to decide if I should build some features for typing the config or not.

There's a whole project around that so might be worth just not doing it for the moment.

@karlhorky
Copy link

features for typing the config

There's a whole project around that

I assume you mean eslint-define-config by @Shinigami92 ?

Alternative: I have been using the types directly with JSDoc and the experience hasn't been too bad.

@bradzacher
Copy link
Member Author

bradzacher commented Nov 12, 2023

Yes, like that but built in.
I spent a while musing on this idea but I ultimately got stuck on trying to make it truly safe.
Sadly the way that flat configs are defined are just not compatible with true type safety.

For example this is a valid config

import foo from 'eslint-plugin-foo';
import bar from 'eslint-plugin-bar';

export default [
  { plugins: {foo} }, 
  {
    files: ["path1"],
    plugins: {bar}, 
  }, 
  {
    rules: {
      // foo/* rules are available here
      // but not bar/* rules
    },
  },
];

And you can see how the generics just don't work because config merging. So we'd need to go with a less sound approach (which is fine enough but not ideal).

There's also the complicated thing of renaming plugins which would need to be handled. EG

import foo from 'eslint-plugin-foo';

export default [
  { 
    files: ["path1"],
    plugins: {foo},
    rules: {
      // eslint-plugin-foo rules are available as foo/*
    },
  }, 
  {
    files: ["path2"],
    plugins: {foobar: foo},
    rules: {
      // eslint-plugin-foo rules are available as foobar/*
    },
  },
  {
    rules: {
      // eslint-plugin-foo rules are NOT available here
    },
  },
];

There's a lot of sadness with the flat config design as it was intended to be a very dynamic DSL -- which ofc sucks to strictly type.
"The first rule of strict types is don't do highly dynamic JS bullshit."


The other thing I was looking at was our schema generation tooling. To make type-safe configs work we would want a way to auto-generate types for the plugin based on the json schemas. We now have that tooling but we don't have it designed for public consumption - or even published.

Theres a lot of work to do to define this space as a general solution that any plugin at all can easily leverage and users can "just use".


Which is a long and rambly confirmation that this probably just needs to land as-is and the type-safe rule configs can come later.

@JoshuaKGoldberg
Copy link
Member

cc @bradzacher - GitHub won't let me request your review on your own PR (because nobody has ever swapped author/reviewer role on a PR, ever) but I think I got it merged from main all right.

@JoshuaKGoldberg JoshuaKGoldberg merged commit 66cd0c0 into main Nov 13, 2023
45 checks passed
@JoshuaKGoldberg JoshuaKGoldberg deleted the flat-config-types branch November 13, 2023 13:06
@JoshuaKGoldberg
Copy link
Member

oop - @bradzacher if you want to send another PR I should be able to review before our auto-release today

(I'm about to step out but will be back soon)

/**
* The definition of plugin rules.
*/
rules?: Record<string, RuleCreateFunction | AnyRuleModule>;
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think that just specifying RuleCreateFunction might be dead for flat configs?
I know it's deprecated I just don't know if it's dead dead.

@bradzacher
Copy link
Member Author

🤷‍♂️ was just drive by commenting before i went to bed.
i'd forgotten what I'd even put in this pr TBH heh.
anything older than a month is mostly deleted from my memory #toddlerdadlife

@JoshuaKGoldberg
Copy link
Member

Well, this is released in https://github.com/typescript-eslint/typescript-eslint/releases/tag/v6.11.0 (cc @karlhorky). If folks don't like it we can always fix it up 😄

@karlhorky
Copy link

karlhorky commented Nov 13, 2023

Hmm... just trying out @typescript-eslint/utils@6.11.0 now, and I'm having trouble getting my JSDoc imports working

I first tried this:

/** @type {import('@typescript-eslint/utils').TSESLint.Linter.Config[]} */
const config = [
  { 
    plugins: {
      '@next/next': next,
    },
    ...
  },
  ...

But this resulted in the error that the type of plugins doesn't match the eslintrc-style config (string[]):

Type '{ '@next/next': ESLint.Plugin; '@typescript-eslint': ESLint.Plugin; 'jsx-a11y': ESLint.Plugin; 'jsx-expressions': ESLint.Plugin; 'react-hooks': ESLint.Plugin; ... 5 more ...; upleveled: ESLint.Plugin; }' is not assignable to type 'string[]'.
  Object literal may only specify known properties, and ''@next/next'' does not exist in type 'string[]'.

There was no FlatConfig exposed on import('@typescript-eslint/utils').TSESLint.Linter directly, so another option I tried was to try to import the FlatConfig namespace directly from dist/ts-eslint/Config.js, but I think this doesn't work either:

/** @type {import('./node_modules/@typescript-eslint/utils/dist/ts-eslint/Config.js').FlatConfig[]} */

This resulted in a different error:

Namespace '"/Users/k/p/eslint-config-upleveled/node_modules/@typescript-eslint/utils/dist/ts-eslint/Config"' has no exported member 'FlatConfig'.

Is there a better way to import this type for usage in my shared config?

@bradzacher
Copy link
Member Author

bradzacher commented Nov 13, 2023

@karlhorky you should be able to use

import('@typescript-eslint/utils/ts-eslint').FlatConfig.ConfigArray

@karlhorky
Copy link

karlhorky commented Nov 14, 2023

Thanks! Switched over here:

Still some manual work needed (see below), but looks like it's working 🙌

@github-actions github-actions bot locked as resolved and limited conversation to collaborators Nov 22, 2023
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
1 approval PR that a maintainer has LGTM'd - any maintainer can merge this when ready enhancement New feature or request
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

3 participants