Skip to content

Commit 4fc1e4c

Browse files
authored
[lexical-eslint-plugin] Chore: update @lexical/eslint-plugin for better eslint 9+ support, finish jest to vitest migration (facebook#8227)
1 parent d7de6f0 commit 4fc1e4c

30 files changed

Lines changed: 672 additions & 1590 deletions

File tree

.eslintrc.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -111,6 +111,7 @@ module.exports = {
111111
},
112112
{
113113
files: [
114+
'scripts/__tests__/**',
114115
'packages/**/src/__tests__/**',
115116
'packages/lexical-playground/**',
116117
'packages/lexical-devtools/**',
@@ -166,7 +167,6 @@ module.exports = {
166167

167168
// import helps to configure simple-import-sort
168169
'import',
169-
'jest',
170170
'no-function-declare-after-return',
171171
'react',
172172
'no-only-tests',

.github/workflows/call-integration-tests.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,3 +25,4 @@ jobs:
2525
- name: Install dependencies
2626
run: pnpm install --frozen-lockfile
2727
- run: pnpm run test-integration
28+
- run: pnpm run test-eslint-integration

jest.config.js

Lines changed: 0 additions & 24 deletions
This file was deleted.

package.json

Lines changed: 4 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -38,9 +38,10 @@
3838
"collab": "cross-env HOST=localhost PORT=1234 npx y-websocket",
3939
"validation": "npx ts-node --cwdMode packages/lexical-playground/src/server/validation.ts",
4040
"vitest": "vitest",
41-
"test-unit": "vitest --no-watch",
42-
"test-unit-watch": "vitest",
43-
"test-integration": "jest --selectProjects integration --testPathPattern",
41+
"test-unit": "vitest --project unit --project scripts-unit --no-watch",
42+
"test-unit-watch": "vitest --project unit --project scripts-unit",
43+
"test-eslint-integration": "node packages/lexical-eslint-plugin/__tests__/integration/integration-test.mjs",
44+
"test-integration": "vitest --project integration --no-watch",
4445
"test-e2e-chromium": "cross-env E2E_BROWSER=chromium playwright test --project=\"chromium\"",
4546
"test-e2e-firefox": "cross-env E2E_BROWSER=firefox playwright test --project=\"firefox\"",
4647
"test-e2e-webkit": "cross-env E2E_BROWSER=webkit playwright test --project=\"webkit\"",
@@ -123,7 +124,6 @@
123124
"@rollup/plugin-replace": "^5.0.5",
124125
"@rollup/plugin-terser": "^0.4.4",
125126
"@types/fs-extra": "^8.1.5",
126-
"@types/jest": "^29.5.12",
127127
"@types/jsdom": "^21.1.6",
128128
"@types/katex": "^0.16.7",
129129
"@types/node": "^17.0.31",
@@ -148,7 +148,6 @@
148148
"eslint-plugin-ft-flow": "^3.0.7",
149149
"eslint-plugin-header": "^3.1.1",
150150
"eslint-plugin-import": "^2.29.1",
151-
"eslint-plugin-jest": "^28.5.0",
152151
"eslint-plugin-jsx-a11y": "^6.8.0",
153152
"eslint-plugin-no-function-declare-after-return": "^1.1.0",
154153
"eslint-plugin-no-only-tests": "^3.1.0",
@@ -166,8 +165,6 @@
166165
"hermes-parser": "^0.26.0",
167166
"hermes-transform": "^0.26.0",
168167
"husky": "^7.0.1",
169-
"jest": "^29.7.0",
170-
"jest-environment-jsdom": "^29.7.0",
171168
"jsdom": "^24.0.0",
172169
"lint-staged": "^11.1.0",
173170
"minimist": "^1.2.5",
@@ -182,7 +179,6 @@
182179
"react-test-renderer": "^19.1.1",
183180
"rollup": "^4.59.0",
184181
"tmp": "^0.2.1",
185-
"ts-jest": "^29.1.2",
186182
"ts-morph": "^25.0.1",
187183
"ts-node": "^10.9.1",
188184
"typedoc": "^0.28.10",

packages/lexical-eslint-plugin-internal/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,6 @@
55
"main": "src/index.js",
66
"private": true,
77
"peerDependencies": {
8-
"eslint": "^7.31.0 || ^8.0.0"
8+
"eslint": ">=7.31.0"
99
}
1010
}

packages/lexical-eslint-plugin-internal/src/index.js

Lines changed: 34 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -10,20 +10,41 @@
1010

1111
const rules = require('./rules');
1212

13-
module.exports = {
13+
// Legacy config format (ESLint 7-8)
14+
const legacyAll = {
15+
rules: {
16+
'@lexical/internal/no-imports-from-self': 'error',
17+
'@lexical/internal/no-optional-chaining': 'error',
18+
},
19+
};
20+
21+
const plugin = {
1422
configs: {
15-
all: {
16-
rules: {
17-
'@lexical/internal/no-imports-from-self': 'error',
18-
'@lexical/internal/no-optional-chaining': 'error',
19-
},
20-
},
21-
recommended: {
22-
rules: {
23-
'@lexical/internal/no-imports-from-self': 'error',
24-
'@lexical/internal/no-optional-chaining': 'error',
25-
},
26-
},
23+
// Legacy configs (ESLint 7-8) - available under multiple names for compatibility
24+
all: legacyAll,
25+
// Flat configs (ESLint 9-10+) - placeholders, will be set below
26+
'flat/all': /** @type {any} */ (null),
27+
'flat/recommended': /** @type {any} */ (null),
28+
'legacy-all': legacyAll,
29+
'legacy-recommended': legacyAll,
30+
recommended: legacyAll,
2731
},
2832
rules,
2933
};
34+
35+
// Flat config format (ESLint 9-10+)
36+
// Must be created after plugin is defined to avoid circular reference
37+
const flatAll = {
38+
plugins: {
39+
'@lexical/internal': plugin,
40+
},
41+
rules: {
42+
'@lexical/internal/no-imports-from-self': 'error',
43+
'@lexical/internal/no-optional-chaining': 'error',
44+
},
45+
};
46+
47+
plugin.configs['flat/all'] = flatAll;
48+
plugin.configs['flat/recommended'] = flatAll;
49+
50+
module.exports = plugin;

packages/lexical-eslint-plugin-internal/src/rules/no-optional-chaining.js

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,9 @@
1515
/** @type {import('eslint').Rule.RuleModule} */
1616
module.exports = {
1717
create(context) {
18-
const sourceCode = context.getSourceCode();
18+
// ESLint 9+ provides sourceCode directly on context (required in ESLint 10+)
19+
// ESLint 7-8 requires calling getSourceCode() method
20+
const sourceCode = context.sourceCode || context.getSourceCode();
1921

2022
/**
2123
* Checks if the given token is a `?.` token or not.

packages/lexical-eslint-plugin/README.md

Lines changed: 87 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@
22

33
This ESLint plugin enforces the [Lexical $function convention](https://lexical.dev/docs/intro#reading-and-updating-editor-state).
44

5+
**ESLint Compatibility:** This plugin supports ESLint 7, 8, 9, and 10+. Both legacy (`.eslintrc`) and flat config (`eslint.config.js`) formats are supported.
6+
57
## Installation
68

79
Assuming you already have ESLint installed, run:
@@ -10,20 +12,54 @@ Assuming you already have ESLint installed, run:
1012
npm install @lexical/eslint-plugin --save-dev
1113
```
1214

13-
Then extend the recommended eslint config:
15+
### ESLint 9+ (Flat Config)
16+
17+
If you're using ESLint 9 or later with the new flat config format (required in ESLint 10+), add this to your `eslint.config.js`:
18+
19+
```js
20+
import lexical from '@lexical/eslint-plugin';
21+
22+
export default [
23+
// ... other configs
24+
lexical.configs['flat/recommended']
25+
];
26+
```
27+
28+
### ESLint 7-8 (Legacy Config)
29+
30+
For ESLint 7 or 8 with the legacy `.eslintrc` format, extend the recommended config:
1431

1532
```js
1633
{
1734
"extends": [
1835
// ...
19-
"plugin:@lexical/recommended"
36+
"plugin:@lexical/legacy-recommended"
2037
]
2138
}
2239
```
2340

41+
> **Note:** The `recommended` and `all` configs are currently aliases to `legacy-recommended` and `legacy-all`. `all` and `recommended` will be migrated to flat config in a future version.
42+
2443
### Custom Configuration
2544

26-
If you want more fine-grained configuration, you can instead add a snippet like this to your ESLint configuration file:
45+
#### ESLint 9+ (Flat Config)
46+
47+
```js
48+
import lexical from '@lexical/eslint-plugin';
49+
50+
export default [
51+
{
52+
plugins: {
53+
'@lexical': lexical
54+
},
55+
rules: {
56+
'@lexical/rules-of-lexical': 'error'
57+
}
58+
}
59+
];
60+
```
61+
62+
#### ESLint 7-8 (Legacy Config)
2763

2864
```js
2965
{
@@ -53,6 +89,39 @@ If the string begins with a `"^"` or `"("` then it is treated as a RegExp,
5389
otherwise it will be an exact match. A string may also be used instead
5490
of an array of strings.
5591

92+
#### ESLint 9+ (Flat Config)
93+
94+
```js
95+
import lexical from '@lexical/eslint-plugin';
96+
97+
export default [
98+
{
99+
plugins: {
100+
'@lexical': lexical
101+
},
102+
rules: {
103+
'@lexical/rules-of-lexical': [
104+
'error',
105+
{
106+
isDollarFunction: ['^\\$[a-z_]'],
107+
isIgnoredFunction: [],
108+
isLexicalProvider: [
109+
'parseEditorState',
110+
'read',
111+
'registerCommand',
112+
'registerNodeTransform',
113+
'update'
114+
],
115+
isSafeDollarFunction: ['^\\$is']
116+
}
117+
]
118+
}
119+
}
120+
];
121+
```
122+
123+
#### ESLint 7-8 (Legacy Config)
124+
56125
```js
57126
{
58127
"plugins": [
@@ -111,6 +180,21 @@ These \$functions are considered safe to call from anywhere, generally
111180
these functions are runtime type checks that do not depend on any other
112181
state.
113182

183+
## Testing
184+
185+
To verify that the plugin works with different ESLint versions, run the integration tests:
186+
187+
```bash
188+
node packages/lexical-eslint-plugin/__tests__/integration-test.js
189+
```
190+
191+
This will test:
192+
- ✓ ESLint 8 with legacy `.eslintrc.json` configuration
193+
- ✓ ESLint 10 with flat `eslint.config.js` configuration
194+
- ✓ Legacy config name aliases (`recommended` vs `legacy-recommended`)
195+
196+
The tests use `pnpm dlx` to run different ESLint versions without modifying `package.json` or `pnpm-lock.yaml`.
197+
114198
## Valid and Invalid Examples
115199

116200
### Valid Examples
Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
# ESLint Plugin Integration Tests
2+
3+
This directory contains integration tests that verify `@lexical/eslint-plugin` works correctly with different ESLint versions.
4+
5+
## Test Coverage
6+
7+
The integration tests verify:
8+
9+
- **ESLint 8.x** with legacy `.eslintrc.json` configuration
10+
- **ESLint 10.x** with flat `eslint.config.js` configuration
11+
- **Config name aliases** (`recommended` vs `legacy-recommended`)
12+
13+
## Running the Tests
14+
15+
From the repository root:
16+
17+
```bash
18+
node packages/lexical-eslint-plugin/__tests__/integration-test.js
19+
```
20+
21+
Or from the package directory:
22+
23+
```bash
24+
cd packages/lexical-eslint-plugin
25+
node __tests__/integration-test.js
26+
```
27+
28+
## How It Works
29+
30+
The test script uses `pnpx` to run different ESLint versions without modifying `package.json` or `pnpm-lock.yaml`. This ensures:
31+
32+
- No dependency conflicts
33+
- Clean testing environment
34+
- Multiple ESLint versions can be tested in the same run
35+
36+
## Test Fixtures
37+
38+
### ESLint 8 (Legacy Config)
39+
40+
Located in `fixtures/eslint8-legacy/`:
41+
- `.eslintrc.json` - Legacy ESLint configuration
42+
- `valid.js` - Code that should pass linting
43+
- `invalid.js` - Code that should trigger `@lexical/rules-of-lexical` errors
44+
45+
### ESLint 10 (Flat Config)
46+
47+
Located in `fixtures/eslint10-flat/`:
48+
- `eslint.config.js` - Flat ESLint configuration
49+
- `valid.js` - Code that should pass linting
50+
- `invalid.js` - Code that should trigger `@lexical/rules-of-lexical` errors
51+
52+
## Expected Behavior
53+
54+
### Valid Code Examples
55+
56+
These should **pass** linting:
57+
- Functions with `$` prefix calling other `$` functions
58+
- Code inside `editor.update()` callbacks calling `$` functions
59+
- Class methods calling `$` functions
60+
61+
### Invalid Code Examples
62+
63+
These should **fail** linting with `@lexical/rules-of-lexical` error:
64+
- Functions without `$` prefix calling `$` functions directly
65+
66+
## Test Output
67+
68+
The test script provides colored output:
69+
- ✓ Green = Test passed
70+
- ✗ Red = Test failed
71+
- Summary at the end with total/passed/failed counts
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
# Test files copied during integration tests
2+
./*/valid.js
3+
./*/invalid.js

0 commit comments

Comments
 (0)