feat(linter/node): implement no-top-level-await rule#24634
Merged
Conversation
Port of eslint-plugin-n's `no-top-level-await`. Disallows top-level `await`, `for await...of`, and `await using` outside of any function, since ES modules with top-level `await` cannot be loaded via `require(esm)`. Supports the portable `ignoreBin` option (skips files starting with a hashbang). The filesystem-dependent parts of the original rule (package.json/.npmignore resolution, bin field, convertPath) are omitted as they have no counterpart in oxc's linter. AI usage disclosure: implemented with Claude Code. Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
Add cases for the rule's distinct branches: plain `using` (not flagged), `await` in class/object methods and nested non-async functions, bare top-level `await` statements, `await` nested in non-function blocks/loops, and `await using` with `ignoreBin` + hashbang. AI usage disclosure: implemented with Claude Code. Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
Subscribe the rule to `Program` only and walk top-level code with a visitor that stops descending at function boundaries, instead of subscribing to every `AwaitExpression`/`ForOfStatement`/`VariableDeclaration` node and walking ancestors to check for an enclosing function. Most `await`/`for-of`/`var` nodes live inside functions, so the old approach paid per-node dispatch on the ubiquitous node types only to reject them via an ancestor walk. The visitor visits just the slice of code that can hold a top-level `await`. Behavior is byte-identical (verified against a 100K-line fixture with ~88K in-function nodes: same diagnostics). On that worst case the rule's own overhead drops ~0.8ms (95% CI 0.4-1.1ms, paired n=120). Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
The `ignoreBin` option docs are generated automatically from the config struct's field doc comment, so the hand-written `### Options` block is redundant. Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
This reverts commit b69dcea.
Merging this PR will not alter performance
Comparing Footnotes
|
The config holds only one `bool`, so `Box<NoTopLevelAwaitConfig>` added a heap allocation and pointer indirection per rule instance with no size or heap-content justification (unlike sibling rules that box an FxHashSet). Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
connorshea
marked this pull request as ready for review
July 18, 2026 02:58
camc314
reviewed
Jul 19, 2026
node/no-top-level-await rule
camc314
added a commit
that referenced
this pull request
Jul 21, 2026
# Oxlint ### 💥 BREAKING CHANGES - 54cc121 ast: [**BREAKING**] Split `MetaProperty` into `ImportMeta` and `NewTarget` (#24557) (camc314) ### 🚀 Features - 7b045cd minfier: Drop last break from last switch case (#24673) (Armano) - dd18383 linter/node: Implement no-top-level-await rule (#24634) (Connor Shea) - 16a65f2 linter/react: Implement function-component-definition rule (#24471) (Cole Ellison) - 7f1f585 linter: Reuse `jest/padding-around-test-blocks` for `vitest/padding-around-test-blocks` (#24519) (Mikhail Baev) - 99978a8 linter/import/consistent-type-specifier-style: Support `prefer-top-level-if-only-type-imports` option (#24502) (camc314) ### 🐛 Bug Fixes - 0184ad6 linter/unicorn/no-useless-undefined: Preserve valid parameter defaults (#24686) (camc314) - 8694167 linter/eslint/prefer-destructuring: Handle typed declarations (#24616) (camc314) - 477cf0f linter/eslint/no-throw-literal: Handle assigned errors (#24561) (Cole Ellison) - ac9200a linter: Detect React components from returned JSX (#24521) (camc314) - c0a6522 linter/eslint/no-useless-computed-key: Allow TS syntax in computed keys (#24524) (Cole Ellison) ### ⚡ Performance - 346eed1 linter/unicorn/prefer-event-target: Only run on `Class` and `NewExpression` nodes (#24685) (Mikhail Baev) - 7be5cf0 oxlint/lsp: Only invoke lint on code actions when document is not opened (#24676) (Sysix) - d3f07a0 diagnostics: Box OxcDiagnosticInner to reduce binary size (#24665) (Boshen) - 90ae040 linter/reporter/stylish: Compute diagnostic Info once per diagnostic (#24525) (connorshea) ### 📚 Documentation - e6f7174 linter/valid-expect: Fix correct example being identical to incorrect one (#24468) (mkan0141) # Oxfmt ### 💥 BREAKING CHANGES - 54cc121 ast: [**BREAKING**] Split `MetaProperty` into `ImportMeta` and `NewTarget` (#24557) (camc314) ### 🚀 Features - 3d22307 parser: Add `ParseOptions::enable_ident_hashes` (#24491) (Boshen) ### 🐛 Bug Fixes - 6fe866a oxfmt: Keep tailwind classes glued to template expr with `preserveWhitespace` (#24609) (leaysgur) - 33e32d8 formatter_css: Use `line_suffix` for EOL line comment (#24580) (leaysgur) - 5f76998 formatter_graphql: Keep same line comments pending across intervening tokens (#24579) (leaysgur) Co-authored-by: Boshen <[email protected]> Co-authored-by: Cameron <[email protected]>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
This implements
no-top-level-awaitfrom the Node ESLint plugin (#493).AI Disclosure: Generated with Claude Code. Reviewed and tested by me.
I have an alternative implementation using a Program visitor but it made no difference performance-wise on vscode or in benchmarking outside extreme cases, so I don't think it's really worth the trouble vs the simpler implementation here. It's still in the commit history if that implementation is preferred.
There is one notable difference between this and the original implementation, which is that the
convertPathoption is not implemented, and the checking ofpackage.json/.npmignoreis not implemented (it was used for checking if the given module was actually exposed in the library) as there's no way to check against those in oxlint. There's an argument to be made that this makes the rule not worth implementing, and I'm open to that argument if we'd prefer to mark this rule as unsupported instead.All tests were ported from the original rule (excluding those reliant on the mentioned config options/checks that were skipped). I've also had Claude add a few additional test cases (see commit 40418f7) on top to cover behavior that wasn't tested previously. I have confirmed that all of these tests are identical to the behavior of the upstream rule, but I can remove them if we don't want to have any divergence from the upstream test suite.