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

Bug: Created rules using RuleCreator are not portable #5032

Closed
4 tasks done
Andarist opened this issue May 22, 2022 · 6 comments · Fixed by #5036
Closed
4 tasks done

Bug: Created rules using RuleCreator are not portable #5032

Andarist opened this issue May 22, 2022 · 6 comments · Fixed by #5036
Labels
accepting prs Go ahead, send a pull request that resolves this issue breaking change This change will require a new major version to be released bug Something isn't working
Milestone

Comments

@Andarist
Copy link
Contributor

Before You File a Bug Report Please Confirm You Have Done The Following...

  • I have tried restarting my IDE and the issue persists.
  • I have updated to the latest version of the packages.
  • I have searched for related issues and found none that matched my issue.
  • I have read the FAQ and my problem is not listed.

Relevant Package

utils

Playground Link

No response

Repro Code

const createRule = ESLintUtils.RuleCreator(name => {
  const ruleName = parsePath(name).name

  return `${REPO_URL}/blob/@emotion/eslint-plugin@${version}/packages/eslint-plugin/docs/rules/${ruleName}.md`
})

// it isn't a problem with `createRule<never[], keyof typeof messages>({`
createRule({
  name: __filename,
  meta: {
    docs: {
      description: 'Ensure vanilla emotion is not used',
      recommended: false
    },
    messages: {
      vanillaEmotion: 'Vanilla emotion should not be used'
    },
    schema: [],
    type: 'problem'
  },
  defaultOptions: [],
  create(context) {
    return {
      ImportDeclaration(node) {
        if (node.source.value === '@emotion/css') {
          context.report({
            node: node.source,
            messageId: 'vanillaEmotion'
          })
        }
      }
    }
  }
})

ESLint Config

No response

tsconfig

No response

Expected Result

A definition file for a library utilizing this util should be generated just fine.

Actual Result

We get an error like this one:

TS2742: The inferred type of 'rules' cannot be named without a reference to '@typescript-eslint/utils/node_modules/@typescript-eslint/types/dist/generated/ast-spec'. This is likely not portable. A type annotation is necessary.

I've diagnosed this and the problem is that the final RuleModule contains a reference to TRuleListener and its type contains references to types from another packages:

I've also figured out a workaround - we need to provide generic params explicitly, to disable the inference for the third generic param (TRuleListener) and just use its default. The workaround in practice can be checked out here: https://github.com/emotion-js/emotion/pull/2761/files/79848d3a5ff7fa5338d31a27fb94951ea15071fa

I wonder though... why the TRuleListener is a generic? Does it have to be referenced in RuleModule? I would have assumed that a rule should be rather opaque to the consumer 🤔 (at least in relation to what kind of AST types does it process and stuff like that)

cc @JoshuaKGoldberg, funny enough that I've stumbled upon this issue just after this recent conversation on Twitter: https://twitter.com/JoshuaKGoldberg/status/1526969335717191692 😅

Additional Info

No response

Versions

package version
@typescript-eslint/utils 5.25.0
TypeScript 4.5.5
ESLint ^7.10.0
@bradzacher
Copy link
Member

As an aside - one thing I would suggest is disabling type def generation for your plugin.

Nobody is going to be importing your plugin directly so giving it type defs is a waste of package size!

@JoshuaKGoldberg
Copy link
Member

Hmm, I don't reproduce this when I make a new scratch repo. But when I clone that emotion PR and run yarn build / preconstruct build, I do get the error:

~/repos/emotion$ preconstruct build
🎁 info building bundles!
🎁 error Generating TypeScript declarations for packages/eslint-plugin/src/index.ts failed:
🎁 error packages/eslint-plugin/src/index.ts:8:14 - error TS2742: The inferred type of 'rules' cannot be named without a reference to '@typescript-eslint/utils/node_modules/@typescript-eslint/types/dist/generated/ast-spec'. This is likely not portable. A type annotation is necessary.
🎁 error
🎁 error 8 export const rules = {
🎁 error                ~~~~~
🎁 error
🎁 info If want to learn more about the above error, check https://preconstruct.tools/errors
🎁 info If the error is not there and you want to learn more about it, open an issue at https://github.com/preconstruct/preconstruct/issues/new

I'm not very familiar with rollup. What's the tsc command equivalent it's using under-the-hood? Poking through its cli.cjs.dev.js, it looks like it's spawning a bunch of definition compile tasks?

I wonder though... why the TRuleListener is a generic?

Good question. When we create an override rule such as @typescript-eslint/brace-style that wraps around a core ESLint rule, it was set up to know the base rule's shape.

TRuleListener extends RuleListener = RuleListener,

...except, looking through the codebase, none of the createRule calls in override rules use it? Example:

export default createRule<Options, MessageIds>({

...so, maybe we can get rid of the TRuleListener type parameter? I tried it out locally and it seems fine. cc @bradzacher, I'm low-to-medium confidence here.

@bradzacher
Copy link
Member

Probably can get rid of it.
I added it because I just threaded the generics through everywhere, leaving them fore the consumer to use if required.

I doubt anyone does actually use it though! So probably safe to remove.

It is technically a breaking change to remove it from the API, sadly.

@JoshuaKGoldberg JoshuaKGoldberg added enhancement New feature or request breaking change This change will require a new major version to be released accepting prs Go ahead, send a pull request that resolves this issue bug Something isn't working and removed bug Something isn't working triage Waiting for maintainers to take a look enhancement New feature or request labels May 22, 2022
@Andarist
Copy link
Contributor Author

Nobody is going to be importing your plugin directly so giving it type defs is a waste of package size!

That doesn't have to be true - I can imagine people creating presets in a programmatic way and that would "require" type defs to be shipped with the plugin (or at least, it would be better if there would be a type def for a plugin). That being said - I don't have an easy way to opt-out from .d.ts being generated for a single package and I definitely want to generate them for all other packages in the monorepo 😉

I'm not very familiar with rollup. What's the tsc command equivalent it's using under-the-hood? Poking through its cli.cjs.dev.js, it looks like it's spawning a bunch of definition compile tasks?

It's hard to tell what's the exact tsc equivalent because this is generated programmatically through TS APIs, the source code can be found here:
https://github.com/preconstruct/preconstruct/blob/4b4db170a90974f8641ff06678ba4e87bc12c3c1/packages/cli/src/rollup-plugins/typescript-declarations/get-declarations.ts

It is technically a breaking change to remove it from the API, sadly.

I'm in no rush here so that's fine - I just wanted to raise this as an issue. Do you think it's worth raising a PR for this now that would wait for your next major release? Or would you say that this is going to be problematic (merge-wise) and that it's better to wait with such a PR until you actually start working on the new major?

@bradzacher bradzacher added this to the 6.0.0 milestone May 22, 2022
@bradzacher
Copy link
Member

I can imagine people creating presets in a programmatic way and that would "require" type defs to be shipped with the plugin

Unless I'm misunderstanding - every reference to an ESLint plugin should be done via raw, untyped strings.
Even programmatically they should be using a string like 'plugin:@typescript-eslint/recommended' to reference your plugin's configs, or '@typescript-eslint/foo': 'error' to reference a rule.

The only case I know of where one might want to directly import a plugin is if you're "extending" a rule (like we do for the base ESLint rules) - in which case that's an advanced edge case I wouldn't ever ship types for!

That being said - I don't have an easy way to opt-out from .d.ts being generated for a single package

😢 that sucks! In our repo we have one tsconfig per package, so we can (and have) disabled typedef generation for the plugin

// specifically disable declarations for the plugin
"declaration": false,
"declarationMap": false,

Do you think it's worth raising a PR for this now

More than happy to take a PR now! The code in question rarely changes so it shouldn't be a problem!

@Andarist
Copy link
Contributor Author

Unless I'm misunderstanding - every reference to an ESLint plugin should be done via raw, untyped strings.
Even programmatically they should be using a string like 'plugin:@typescript-eslint/recommended' to reference your plugin's configs, or '@typescript-eslint/foo': 'error' to reference a rule.

Then that is me not knowing how one actually creates presets in ESLint :P

More than happy to take a PR now! The code in question rarely changes so it shouldn't be a problem!

Here you go :)

@github-actions github-actions bot locked as resolved and limited conversation to collaborators Dec 1, 2022
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
accepting prs Go ahead, send a pull request that resolves this issue breaking change This change will require a new major version to be released bug Something isn't working
Projects
None yet
Development

Successfully merging a pull request may close this issue.

3 participants