Skip to content

Commit 2184c46

Browse files
committed
feat(eslint-plugin-mobx): add types for flat/legacy configs
Rewrites the plugin in TypeScript so mobx.flatConfigs.recommended and mobx.configs.recommended are typed when consumed from a TypeScript eslint.config.ts, per @kubk's suggestion on the PR: - source files renamed to .ts - rollup compiles src/index.ts with rollup-plugin-typescript2 (already used by the other packages in this monorepo), with Babel still running afterwards (via the existing .babelrc.js) to downlevel the output the same way the pre-TypeScript build did - declarations are generated into dist/index.d.ts and "types" points there, so the declaration can no longer drift from the implementation the way a hand-written one could - the generated declaration's `export default` is patched to `export =` after the build, to match the actual module.exports = <value> runtime shape regardless of the consumer's esModuleInterop setting - the standalone .d.ts rollup-plugin-typescript2 emits for every compiled module (not just the bundled entry point) is removed after the build, since dist/index.d.ts is fully self-contained - @types/eslint moved to dependencies, since the generated declaration references it and it isn't installed transitively as a devDependency; @types/estree is pinned to ^1.0.0 as a direct dependency too, since @types/eslint's own dependency on it is unconstrained and this repo already has an incompatible 0.0.39 hoisted at the root Closes #4545.
1 parent 01211a6 commit 2184c46

18 files changed

Lines changed: 183 additions & 89 deletions
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"eslint-plugin-mobx": patch
3+
---
4+
5+
Add TypeScript types for `configs.recommended` and `flatConfigs.recommended`, so they're typed when used from a TypeScript `eslint.config.ts`.

package-lock.json

Lines changed: 20 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

packages/eslint-plugin-mobx/__tests__/exhaustive-make-observable.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import { getRuleTester } from "./utils/get-rule-tester";
22

3-
import rule from "../src/exhaustive-make-observable.js";
3+
import rule from "../src/exhaustive-make-observable";
44

55
const tester = getRuleTester();
66

packages/eslint-plugin-mobx/__tests__/missing-make-observable.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import { getRuleTester } from "./utils/get-rule-tester";
22

3-
import rule from "../src/missing-make-observable.js";
3+
import rule from "../src/missing-make-observable";
44

55
const tester = getRuleTester();
66

packages/eslint-plugin-mobx/__tests__/missing-observer.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import { getRuleTester } from "./utils/get-rule-tester";
22

3-
import rule from "../src/missing-observer.js"
3+
import rule from "../src/missing-observer"
44

55
const tester = getRuleTester();
66

packages/eslint-plugin-mobx/__tests__/no-anonymous-observer.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import { getRuleTester } from "./utils/get-rule-tester";
22

3-
import rule from "../src/no-anonymous-observer.js"
3+
import rule from "../src/no-anonymous-observer"
44

55
const tester = getRuleTester();
66

packages/eslint-plugin-mobx/__tests__/unconditional-make-observable.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import { getRuleTester } from "./utils/get-rule-tester";
22

3-
import rule from "../src/unconditional-make-observable.js";
3+
import rule from "../src/unconditional-make-observable";
44

55
const tester = getRuleTester();
66

packages/eslint-plugin-mobx/package.json

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
"version": "0.0.14",
44
"description": "ESLint rules for MobX",
55
"main": "dist/index.js",
6+
"types": "dist/index.d.ts",
67
"repository": {
78
"type": "git",
89
"url": "https://github.com/mobxjs/mobx.git",
@@ -27,6 +28,10 @@
2728
"peerDependencies": {
2829
"eslint": "^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0 || ^9.0.0 || ^10.0.0"
2930
},
31+
"dependencies": {
32+
"@types/eslint": "^9.6.1",
33+
"@types/estree": "^1.0.0"
34+
},
3035
"devDependencies": {
3136
"eslint-7": "npm:eslint@^7.0.0",
3237
"eslint-9": "npm:eslint@^9.0.0"
@@ -41,6 +46,8 @@
4146
"test:7": "jest --config jest.config-eslint-7.js",
4247
"test:9": "jest --config jest.config-eslint-9.js",
4348
"test": "npm run test:7 && npm run test:9",
49+
"test:types": "tsc --noEmit",
50+
"test:check": "npm run test:types",
4451
"build": "rollup --config",
4552
"prepublishOnly": "npm run build"
4653
}
Lines changed: 54 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,73 @@
1+
import fs from "fs";
2+
import path from "path";
13
import { nodeResolve } from "@rollup/plugin-node-resolve";
24
import commonjs from "@rollup/plugin-commonjs";
35
import babel from "@rollup/plugin-babel";
6+
import typescript from "rollup-plugin-typescript2";
47
import pkg from "./package.json";
58

9+
// rollup-plugin-typescript2 only strips types and downlevels down to the
10+
// root tsconfig's `target` (es6), so arrow functions, `const`/`let`,
11+
// destructuring, template literals etc. would otherwise reach dist/index.js
12+
// as-is. Babel still runs afterwards (via .babelrc.js, same as before the
13+
// TypeScript migration) to downlevel that to the postinstall-tested `eslint`
14+
// peer range down to ^3.0.0.
15+
//
16+
// rollup-plugin-typescript2 also writes a standalone .d.ts for every source
17+
// module it compiles (dist/utils.d.ts, dist/missing-observer.d.ts, ...), not
18+
// just the bundled entry point — dist/index.d.ts is fully self-contained and
19+
// none of them are referenced from anywhere, so they're removed below.
20+
//
21+
// Finally, rollup-plugin-typescript2 requires an ES module target so Rollup
22+
// can bundle the sources, so src/index.ts is written as `export default
23+
// {...}` — but the bundled runtime output is a single CommonJS export,
24+
// `module.exports = <value>`, not an ES default export wrapped in `.default`.
25+
// A declaration using `export default` only lines up with that shape under
26+
// `esModuleInterop`; a plain `import mobx from "eslint-plugin-mobx"` without
27+
// it would type-check but read `mobx.default`, which is `undefined` at
28+
// runtime. `export =` matches `module.exports = <value>` unconditionally, so
29+
// the generated declaration is patched to use it too.
30+
const fixDeclarationOutput = () => ({
31+
name: "fix-declaration-output",
32+
writeBundle(options) {
33+
const distDir = path.dirname(options.file);
34+
const declarationPath = path.join(distDir, "index.d.ts");
35+
const declaration = fs.readFileSync(declarationPath, "utf8");
36+
const patched = declaration.replace(/^export default (\w+);\s*$/m, "export = $1;");
37+
if (patched === declaration) {
38+
throw new Error(
39+
`Could not find an "export default <name>;" statement to patch in ${declarationPath}`
40+
);
41+
}
42+
fs.writeFileSync(declarationPath, patched);
43+
44+
for (const entry of fs.readdirSync(distDir)) {
45+
if (entry.endsWith(".d.ts") && entry !== "index.d.ts") {
46+
fs.unlinkSync(path.join(distDir, entry));
47+
}
48+
}
49+
},
50+
});
51+
652
export default [
753
{
8-
input: "src/index.js",
54+
input: "src/index.ts",
955
plugins: [
1056
nodeResolve(),
1157
commonjs(),
58+
typescript(),
1259
babel({
1360
babelHelpers: "bundled",
1461
exclude: "**/node_modules/**",
62+
// @rollup/plugin-babel's default extensions don't include ".ts" - by
63+
// the time this runs, rollup-plugin-typescript2 has already emitted
64+
// plain JS for these modules, but the module ids still end in ".ts".
65+
extensions: [".js", ".jsx", ".es6", ".es", ".mjs", ".cjs", ".ts"],
1566
}),
67+
fixDeclarationOutput(),
1668
],
1769
output: [
1870
{ file: pkg.main, format: "cjs", exports: "auto" },
1971
],
2072
},
21-
];
73+
];

packages/eslint-plugin-mobx/src/exhaustive-make-observable.js renamed to packages/eslint-plugin-mobx/src/exhaustive-make-observable.ts

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
1-
"use strict"
1+
import type { Rule } from "eslint"
22

3-
const { findAncestor, isMobxDecorator } = require("./utils.js")
3+
import { findAncestor, isMobxDecorator } from "./utils"
44

55
// TODO support this.foo = 5; in constructor
66
// TODO? report on field as well
@@ -42,7 +42,7 @@ function create(context) {
4242
}
4343

4444
const annotationProps = secondArg?.properties || []
45-
const nonAnnotatedMembers = []
45+
const nonAnnotatedMembers: any[] = []
4646
let hasAnyDecorator = false
4747

4848
members.forEach(member => {
@@ -102,7 +102,7 @@ function create(context) {
102102
}
103103
}
104104

105-
module.exports = {
105+
const rule: Rule.RuleModule = {
106106
meta: {
107107
type: "suggestion",
108108
fixable: "code",
@@ -119,8 +119,7 @@ module.exports = {
119119
],
120120
docs: {
121121
description: "enforce all fields being listen in `makeObservable`",
122-
recommended: true,
123-
suggestion: false
122+
recommended: true
124123
},
125124
messages: {
126125
missingAnnotation:
@@ -129,3 +128,5 @@ module.exports = {
129128
},
130129
create
131130
}
131+
132+
export default rule

0 commit comments

Comments
 (0)