Skip to content

Bug: [no-unused-expressions, no-empty-functions, possibly others...] extension rules crash with eslint v9.15.0 #10338

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

Closed
4 tasks done
ext4cats opened this issue Nov 15, 2024 · 29 comments · Fixed by #10339 or ministryofjustice/hmpps-typescript-lib#28
Labels
accepting prs Go ahead, send a pull request that resolves this issue bug Something isn't working locked due to age Please open a new issue if you'd like to say more. See https://typescript-eslint.io/contributing.

Comments

@ext4cats
Copy link

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.

Issue Description

eslint crashes when using the latest version of node 22 and the latest version of typescript-eslint.

Oops! Something went wrong! :(

ESLint: 9.15.0

TypeError: Error while loading rule '@typescript-eslint/no-unused-expressions': Cannot read properties of undefined (reading 'allowShortCircuit')
Occurred while linting /home/ext4cats/Projects/tseslint-8.14.0-bug/index.js
    at Object.create (/home/ext4cats/Projects/tseslint-8.14.0-bug/node_modules/.pnpm/eslint@9.15.0/node_modules/eslint/lib/rules/no-unused-expressions.js:75:13)
    at create (/home/ext4cats/Projects/tseslint-8.14.0-bug/node_modules/.pnpm/@typescript-eslint+eslint-plugin@8.14.0_@typescript-eslint+parser@8.14.0_eslint@9.15.0_typesc_u2crwq53uno6k5fweinf7nowby/node_modules/@typescript-eslint/eslint-plugin/dist/rules/no-unused-expressions.js:28:32)
    at Object.create (/home/ext4cats/Projects/tseslint-8.14.0-bug/node_modules/.pnpm/@typescript-eslint+utils@8.14.0_eslint@9.15.0_typescript@5.6.3/node_modules/@typescript-eslint/utils/dist/eslint-utils/RuleCreator.js:31:20)
    at createRuleListeners (/home/ext4cats/Projects/tseslint-8.14.0-bug/node_modules/.pnpm/eslint@9.15.0/node_modules/eslint/lib/linter/linter.js:944:21)
    at /home/ext4cats/Projects/tseslint-8.14.0-bug/node_modules/.pnpm/eslint@9.15.0/node_modules/eslint/lib/linter/linter.js:1082:84
    at Array.forEach (<anonymous>)
    at runRules (/home/ext4cats/Projects/tseslint-8.14.0-bug/node_modules/.pnpm/eslint@9.15.0/node_modules/eslint/lib/linter/linter.js:1013:34)
    at #flatVerifyWithoutProcessors (/home/ext4cats/Projects/tseslint-8.14.0-bug/node_modules/.pnpm/eslint@9.15.0/node_modules/eslint/lib/linter/linter.js:1911:31)
    at Linter._verifyWithFlatConfigArrayAndWithoutProcessors (/home/ext4cats/Projects/tseslint-8.14.0-bug/node_modules/.pnpm/eslint@9.15.0/node_modules/eslint/lib/linter/linter.js:1993:49)
    at Linter._verifyWithFlatConfigArray (/home/ext4cats/Projects/tseslint-8.14.0-bug/node_modules/.pnpm/eslint@9.15.0/node_modules/eslint/lib/linter/linter.js:2082:21)

Reproduction Repository Link

https://github.com/ext4cats/tseslint-8.14.0-bug

Repro Steps

  1. clone the repo
  2. follow steps in readme

Versions

package version
typescript-eslint 8.14.0
TypeScript 5.6.3
ESLint 9.15.0
node 22.11.0
@ext4cats ext4cats added bug Something isn't working triage Waiting for team members to take a look labels Nov 15, 2024
@glass-ships
Copy link

Related to eslint/eslint#19134

@ext4cats
Copy link
Author

I downgraded my project to eslint v9.14.0 and the crash is gone. Seems to be a regression in eslint itself.

@AviVahl
Copy link

AviVahl commented Nov 15, 2024

It looks like @typescript-eslint/no-unused-expressions uses eslint's original no-unused-expressions, calling its create function, passing in the context. no-unused-expressions appears to be expecting context.options to be defined (as an array of options), as explained here: https://eslint.org/docs/latest/extend/custom-rules#the-context-object

https://github.com/eslint/eslint/blob/main/lib/rules/no-unused-expressions.js#L79
image

for some reason, @typescript-eslint/no-unused-expressions's create function appears to be receiving these options as a second parameter:
https://github.com/typescript-eslint/typescript-eslint/blob/main/packages/eslint-plugin/src/rules/no-unused-expressions.ts#L36
image

not sure if this inconsistency was always there, but typescript-eslint may want to propagate options in?

Copy link

Uh oh! @AviVahl, the image you shared is missing helpful alt text. Check #10338 (comment).

Alt text is an invisible description that helps screen readers describe images to blind or low-vision users. If you are using markdown to display images, add your alt text inside the brackets of the markdown image.

Learn more about alt text at Basic writing and formatting syntax: images on GitHub Docs.

🤖 Beep boop! This comment was added automatically by github/accessibility-alt-text-bot.

@AviVahl
Copy link

AviVahl commented Nov 15, 2024

the crash is due to this change https://github.com/eslint/eslint/pull/17656/files#diff-b65ad05fc10a27a62ba130f4ec20fce33dfdfed6917e58581c91d59067aeb30b

which makes typescript-eslint's way of using it break.
the fix should probably come in typescript-eslint, to match the rule's new context expectations.

@kirkwaiblinger
Copy link
Member

Yes, as several have noted this is due to the @typescript-eslint/no-unused-expressions extension rule using brittle monkey-patching to compose our logic with that of the the base rule. When the base rule changes, so must our monkey-patch! I note that eslint/eslint#17656 seems to much broader than just affecting no-unused-expressions, so I honestly wouldn't be surprised if other rules are broken with eslint v9.15.0 too for the same reason. Worth looking into.

@kirkwaiblinger kirkwaiblinger added accepting prs Go ahead, send a pull request that resolves this issue and removed triage Waiting for team members to take a look labels Nov 15, 2024
@AviVahl
Copy link

AviVahl commented Nov 15, 2024

search for baseRule.create in the rules directory. I assume all these could break in theory. not sure which ones are included in the recommended preset.

@AviVahl
Copy link

AviVahl commented Nov 15, 2024

perhaps the fix should be to match the new upstream defaultOptions behavior, and even use it for typescript-eslint rules via context.options (instead of second parameter)?

@tkmcmaster
Copy link

tkmcmaster commented Nov 15, 2024

... I note that eslint/eslint#17656 seems to much broader than just affecting no-unused-expressions, so I honestly wouldn't be surprised if other rules are broken with eslint v9.15.0 too for the same reason. Worth looking into.

I originally hit this bug with typescript-eslint/no-empty-function. It was only after completely turning off that rule, I found the rule in this issue. Which then searching online found this GitHub issue. So yes, there are other rules that are affected.

@kirkwaiblinger
Copy link
Member

perhaps the fix should be to match the new upstream defaultOptions behavior, and even use it for typescript-eslint rules via context.options (instead of second parameter)?

The thing to keep in mind is that we'll want to stay backwards compatible with eslint versions before 9.15. See also #6456.

If you end up poking around with this and find a good solution, do feel free to raise a PR 🙂

@kirkwaiblinger kirkwaiblinger changed the title Bug: crash on v8.14.0 with eslint v9.15.0 and node v22.11.0 Bug: [no-unused-expressions, no-empty-functions, possibly others...] extension rules crash with eslint v9.15.0 Nov 15, 2024
@AviVahl
Copy link

AviVahl commented Nov 15, 2024

Considered it, but it seems like a wider change, so I'm hesitant given my limited knowledge of this codebase...

@kirkwaiblinger
Copy link
Member

Considered it, but it seems like a wider change, so I'm hesitant given my limited knowledge of this codebase...

Totally fair! Rest assured that the root cause of this type of bug is very well understood by each of the maintenance team members, and will be addressed as soon as our bandwidth permits, unless a community champion is able to get to it first 🙂

@LuisUrrutia
Copy link
Contributor

LuisUrrutia commented Nov 16, 2024

First time looking into typescript-eslint code, but it seems to me that we should modify this section:

return createRule<Options, MessageIds, PluginDocs>({
meta: {
...meta,
docs: {
...meta.docs,
url: urlCreator(name),
},
},
...rule,
});

To include defaultOptions within the meta object. The updated code could look something like this:

    return createRule<Options, MessageIds, PluginDocs>({
      meta: {
        ...meta,
        defaultOptions: rule.defaultOptions,
        docs: {
          ...meta.docs,
          url: urlCreator(name),
        },
      },
      ...rule,
    });

You can extract defaultOptions directly in:
https://github.com/typescript-eslint/typescript-eslint/blob/main/packages/utils/src/eslint-utils/RuleCreator.ts#L63-L67

The reason for this change is the introduction of the following update in ESLint:
https://github.com/eslint/eslint/blob/main/docs/src/extend/custom-rules.md#option-defaults

In PR:
https://github.com/eslint/eslint/pull/17656/files#diff-dc3f44ef800795b890a57c8dd95c6ce26ba8a47a4551d7a05d7a5cd9cf016597R805-R834

We should also update the defaultOptions in each rule to maintain parity, but so far this "quick" fix should be enough for now. (Note: I might be overlooking something since, as I mentioned, this is my first time looking into the code.)

I’d work on a PR for this, but I really need to get some sleep.

@bradzacher
Copy link
Member

bradzacher commented Nov 16, 2024

for some reason, @typescript-eslint/no-unused-expressions's create function appears to be receiving these options as a second parameter:
not sure if this inconsistency was always there...

@AviVahl our utilities have always supported a top-level defaultOptions property for as long as they have existed!
Our API design was to pass the merged options as a second parameter to the create function rather than changing the options on the context object because it allowed greater flexibility -- it gives you the option to see BOTH the user's actual config and the merged config -- which can sometimes be useful if you want to act differently when a user skipped an option but also avoid having undefineds around the place.

Unfortunately ESLint core chose a different location for the defaultOptions property just added in v9.15.0:

  • It was added to meta.defaultOptions rather than as a top-level property, and
  • They chose to overwrite the context.options rather than pass it as a separate argument.

In the next major we'll likely converge on their solution to deduplicate things.

@ext4cats
Copy link
Author

@glass-ships Smells like a breaking change sneaking its way into a minor update. I've already locked all of my projects to v9.14.0 so I should be fine for now. I might look into it and see if I can craft a fix this weekend.

If I can figure out the codebase in just a weekend, that is... I don't have high hopes, but I feel invested in this considering how I found it just an hour after v9.15.0 came out, after doing my routine pnpm update 😬

@glass-ships
Copy link

Agreed! If you do happen to give this a shot, feel free to link me to your PR, I'd be happy to contribute however I can.

@Jason3S
Copy link

Jason3S commented Nov 16, 2024

This is the eslint patch I used to get it working with typescript-eslint and unicorn

diff --git a/lib/rules/no-unused-expressions.js b/lib/rules/no-unused-expressions.js
index fd1437c1606a56cbce147d040d601d0acef3d24a..1ac2d6e45fbb04f93d9cccdc5265bb38c3701aca 100644
--- a/lib/rules/no-unused-expressions.js
+++ b/lib/rules/no-unused-expressions.js
@@ -76,7 +76,7 @@ module.exports = {
             allowTernary,
             allowTaggedTemplates,
             enforceForJSX
-        }] = context.options;
+        } = {}] = context.options;
 
         /**
          * Has AST suggesting a directive.
diff --git a/lib/rules/no-warning-comments.js b/lib/rules/no-warning-comments.js
index 628f5a2ac513303cc3fe2cc9adcbf1ae54128747..a42e8318e028c6a3717afceef3c949bf8eab5827 100644
--- a/lib/rules/no-warning-comments.js
+++ b/lib/rules/no-warning-comments.js
@@ -64,7 +64,7 @@ module.exports = {
 
     create(context) {
         const sourceCode = context.sourceCode;
-        const [{ decoration, location, terms: warningTerms }] = context.options;
+        const [{ decoration, location, terms: warningTerms } = this.meta.defaultOptions[0]] = context.options;
         const escapedDecoration = escapeRegExp(decoration ? decoration.join("") : "");
         const selfConfigRegEx = /\bno-warning-comments\b/u;

@TheGreatJordach
Copy link

I had the same problem with V10.8.2 . it stopped happening if I downgraded the version to v9.14.0.

@bradzacher
Copy link
Member

The issue is known. The fix is in progress.
Locking to avoid further noise.

Please subscribe to this issue to keep updated.

@typescript-eslint typescript-eslint locked and limited conversation to collaborators Nov 17, 2024
@bradzacher
Copy link
Member

The fix has merged and will soon be released to our canary tag.
The fix will release to latest with our next release in a few days.
https://typescript-eslint.io/users/releases

@typescript-eslint typescript-eslint unlocked this conversation Nov 17, 2024
@miraclemenikelechi

This comment was marked as resolved.

@SDAdham
Copy link

SDAdham commented Nov 17, 2024

Hello, I just installed canary and the error is still there.

@ext4cats
Copy link
Author

Hello, I just installed canary and the error is still there.

Can you provide details and/or a reproduction?

renovate bot added a commit to seek-oss/fake-hr that referenced this issue Nov 17, 2024
* deps: lock file maintenance

* Temporarily downgrade ESLint due to typescript-eslint/typescript-eslint#10338

---------

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
Co-authored-by: AaronMoat <2937187+AaronMoat@users.noreply.github.com>
@bradzacher
Copy link
Member

If you think this isn't fixed, please file a new issue and provide a complete reproduction and we can investigate.

@typescript-eslint typescript-eslint locked as resolved and limited conversation to collaborators Nov 17, 2024
@kirkwaiblinger
Copy link
Member

kirkwaiblinger commented Nov 18, 2024

Update: The fix is now live in our 8.15.0 release! 🎉

If you encounter issues when you try it out, please double-check that each of the @typescript-eslint/* packages you're using is up-to-date (the most relevant one is @typescript-eslint/eslint-plugin), and create a minimal reproduction of the issue, before filing a new issue.

@bradzacher bradzacher unpinned this issue Nov 21, 2024
@bradzacher bradzacher added the locked due to age Please open a new issue if you'd like to say more. See https://typescript-eslint.io/contributing. label Apr 14, 2025
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 bug Something isn't working locked due to age Please open a new issue if you'd like to say more. See https://typescript-eslint.io/contributing.
Projects
None yet