fix: type compatibility issue between Markdown v8 and ESLint v9.39.x#648
Open
lumirlumir wants to merge 15 commits intomainfrom
Open
fix: type compatibility issue between Markdown v8 and ESLint v9.39.x#648lumirlumir wants to merge 15 commits intomainfrom
lumirlumir wants to merge 15 commits intomainfrom
Conversation
b8b4ffd to
0917752
Compare
0917752 to
38fd019
Compare
…solve-eslint-markdown-v8-and-eslint-v9-compatibility-issue
There was a problem hiding this comment.
Pull request overview
Fixes TypeScript type compatibility between @eslint/markdown v8 and ESLint v9.39.x by decoupling the published rule types from ESLint’s evolving internal rule-definition typings, while expanding CI/type checks across supported ESLint versions.
Changes:
- Switch JSDoc typing in runtime JS (
src/index.js,src/processor.js) to use types from@eslint/coreinstead ofeslint. - Update rule build generation to emit a
Record<RuleId, any>-typed rules map to avoid cross-major ESLint type incompatibilities. - Expand type-compatibility testing (CI matrix +
tests/types) and clarify README version guidance.
Reviewed changes
Copilot reviewed 6 out of 6 changed files in this pull request and generated 4 comments.
Show a summary per file
| File | Description |
|---|---|
| tools/build-rules.js | Generates a typed RuleId union and coerces exported rules map to a looser type to avoid ESLint type coupling. |
| tests/types/types.test.ts | Adds type assertions intended to validate compatibility with ESLint v9.39.x/v9.x/v10.x and rule key presence. |
| src/processor.js | Replaces eslint-sourced types with @eslint/core types in JSDoc for fixes/messages/ranges. |
| src/index.js | Replaces eslint-sourced config/rules typing with @eslint/core equivalents for generated declarations. |
| README.md | Updates installation note to clarify which ESLint versions are type-compatible. |
| .github/workflows/ci.yml | Adds an ESLint version matrix for the type-checking job and installs matching ESLint versions in CI. |
Comments suppressed due to low confidence (1)
src/processor.js:445
excludeUnsatisfiableRules()is used aftergroup.map(adjust)whereadjustcan returnnull, but the parameter is typed asLintMessage. Consider typing the parameter asLintMessage | nulland/or making this a type guard sopostprocess()is correctly typed as returningLintMessage[].
/**
* Excludes unsatisfiable rules from the list of messages.
* @param {LintMessage} message A message from the linter.
* @returns {boolean} True if the message should be included in output.
*/
function excludeUnsatisfiableRules(message) {
return message && !UNSATISFIABLE_RULES.has(message.ruleId);
}
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
…solve-eslint-markdown-v8-and-eslint-v9-compatibility-issue
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.
Prerequisites checklist
AI acknowledgment
What is the purpose of this pull request?
This PR fixes a type compatibility issue that makes Markdown plugin v8 incompatible with ESLint v9.39.x, which is now in maintenance mode.
Cause of the type incompatibility problem
Currently, the incompatibility comes from the
export { rules };statement indist/index.d.ts(line 17).This happens because
rulesis exposed such asRecord<RuleId, MarkdownRuleDefinition>, andMarkdownRuleDefinitiondepends onCustomRuleDefinitionType,CustomRuleTypeDefinitions, andCustomRuleVisitorWithExit. Those types were changed as part of ESLint v10’s breaking changes, so some type signatures no longer match, making Markdown plugin v8 incompatible with ESLint v9.39.x.Interestingly, if we avoid coercing the rule types to
MarkdownRuleDefinitionand keep them in their original object form instead, there are no type errors. That is because the rule objects themselves have not changed between versions.I tried a number of different approaches to preserve the original rule object form while still keeping the benefits of type restrictions. In the end, the simplest approach was to coerce the built
src/build/rules.jsobject toRecord<RuleId, any>.This lets us keep type restrictions when writing the code, while preventing end users from running into type incompatibilities caused by major ESLint version bumps.
Potential downsides of this change
Since the user-facing rule type changes from
Record<RuleId, MarkdownRuleDefinition>toRecord<RuleId, any>, directly accessing rule metadata through the index file becomes less strictly typed.However, because directly accessing
rulesis considered an unstable API and is not treated as a breaking change under our policy, this seems acceptable.Ref: https://github.com/eslint/eslint/blob/main/lib/unsupported-api.js
Compatibility with ESLint versions earlier than v9.39.0?
For ESLint versions earlier than v9.39.0, type errors occur not only with
MarkdownRuleDefinitionbut also in other parts, such asprocessor.I'm not sure there is an easy way to make this work with ESLint < v9.39.0. If there is, I'd be happy to follow it.
What changes did you make? (Give an overview)
ci.yml,types.test.ts, andREADME.mdUpdated tests to ensure type compatibility with ESLint v9.39.0, v9.39.x, and v10.x.
src/index.js, andsrc/processor.jsUpdated the types to use the ones from
@eslint/core.Before this change,
@eslint/markdowndepended on types exported from theeslintpackage. This is problematic because those types do not come from a direct dependency; instead, they effectively come from the peer-likeeslintpackage.First,
eslintis currently not declared as a peer dependency, which can cause type errors when@eslint/markdownis used without theeslintpackage. For example, in Code Explorer, we useeslint-linter-browserifyinstead ofeslint.Second, the ESLint types come from the host package and depend on the version installed by the user, making the behavior unpredictable. This is especially problematic because some internal implementation code uses types from
@eslint/core, while types fromeslintare also mixed in.tools/build-rules.jsUpdated the auto-generated
src/build/rules.jsfile to use the typeRecord<RuleId, any>. I believe this will also help avoid type inconsistency errors when updating theRuleDefinitiontype in the future.Related Issues
Ref: eslint/json#213
Is there anything you'd like reviewers to focus on?
N/A