Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .eslintrc.js
Original file line number Diff line number Diff line change
Expand Up @@ -111,6 +111,7 @@ module.exports = {
},
{
files: [
'scripts/__tests__/**',
'packages/**/src/__tests__/**',
'packages/lexical-playground/**',
'packages/lexical-devtools/**',
Expand Down Expand Up @@ -166,7 +167,6 @@ module.exports = {

// import helps to configure simple-import-sort
'import',
'jest',
'no-function-declare-after-return',
'react',
'no-only-tests',
Expand Down
1 change: 1 addition & 0 deletions .github/workflows/call-integration-tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -25,3 +25,4 @@ jobs:
- name: Install dependencies
run: pnpm install --frozen-lockfile
- run: pnpm run test-integration
- run: pnpm run test-eslint-integration
24 changes: 0 additions & 24 deletions jest.config.js

This file was deleted.

12 changes: 4 additions & 8 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -38,9 +38,10 @@
"collab": "cross-env HOST=localhost PORT=1234 npx y-websocket",
"validation": "npx ts-node --cwdMode packages/lexical-playground/src/server/validation.ts",
"vitest": "vitest",
"test-unit": "vitest --no-watch",
"test-unit-watch": "vitest",
"test-integration": "jest --selectProjects integration --testPathPattern",
"test-unit": "vitest --project unit --project scripts-unit --no-watch",
"test-unit-watch": "vitest --project unit --project scripts-unit",
"test-eslint-integration": "node packages/lexical-eslint-plugin/__tests__/integration/integration-test.mjs",
"test-integration": "vitest --project integration --no-watch",
"test-e2e-chromium": "cross-env E2E_BROWSER=chromium playwright test --project=\"chromium\"",
"test-e2e-firefox": "cross-env E2E_BROWSER=firefox playwright test --project=\"firefox\"",
"test-e2e-webkit": "cross-env E2E_BROWSER=webkit playwright test --project=\"webkit\"",
Expand Down Expand Up @@ -123,7 +124,6 @@
"@rollup/plugin-replace": "^5.0.5",
"@rollup/plugin-terser": "^0.4.4",
"@types/fs-extra": "^8.1.5",
"@types/jest": "^29.5.12",
"@types/jsdom": "^21.1.6",
"@types/katex": "^0.16.7",
"@types/node": "^17.0.31",
Expand All @@ -148,7 +148,6 @@
"eslint-plugin-ft-flow": "^3.0.7",
"eslint-plugin-header": "^3.1.1",
"eslint-plugin-import": "^2.29.1",
"eslint-plugin-jest": "^28.5.0",
"eslint-plugin-jsx-a11y": "^6.8.0",
"eslint-plugin-no-function-declare-after-return": "^1.1.0",
"eslint-plugin-no-only-tests": "^3.1.0",
Expand All @@ -166,8 +165,6 @@
"hermes-parser": "^0.26.0",
"hermes-transform": "^0.26.0",
"husky": "^7.0.1",
"jest": "^29.7.0",
"jest-environment-jsdom": "^29.7.0",
"jsdom": "^24.0.0",
"lint-staged": "^11.1.0",
"minimist": "^1.2.5",
Expand All @@ -182,7 +179,6 @@
"react-test-renderer": "^19.1.1",
"rollup": "^4.59.0",
"tmp": "^0.2.1",
"ts-jest": "^29.1.2",
"ts-morph": "^25.0.1",
"ts-node": "^10.9.1",
"typedoc": "^0.28.10",
Expand Down
2 changes: 1 addition & 1 deletion packages/lexical-eslint-plugin-internal/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,6 @@
"main": "src/index.js",
"private": true,
"peerDependencies": {
"eslint": "^7.31.0 || ^8.0.0"
"eslint": ">=7.31.0"
}
}
47 changes: 34 additions & 13 deletions packages/lexical-eslint-plugin-internal/src/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,20 +10,41 @@

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

module.exports = {
// Legacy config format (ESLint 7-8)
const legacyAll = {
rules: {
'@lexical/internal/no-imports-from-self': 'error',
'@lexical/internal/no-optional-chaining': 'error',
},
};

const plugin = {
configs: {
all: {
rules: {
'@lexical/internal/no-imports-from-self': 'error',
'@lexical/internal/no-optional-chaining': 'error',
},
},
recommended: {
rules: {
'@lexical/internal/no-imports-from-self': 'error',
'@lexical/internal/no-optional-chaining': 'error',
},
},
// Legacy configs (ESLint 7-8) - available under multiple names for compatibility
all: legacyAll,
// Flat configs (ESLint 9-10+) - placeholders, will be set below
'flat/all': /** @type {any} */ (null),
'flat/recommended': /** @type {any} */ (null),
'legacy-all': legacyAll,
'legacy-recommended': legacyAll,
recommended: legacyAll,
},
rules,
};

// Flat config format (ESLint 9-10+)
// Must be created after plugin is defined to avoid circular reference
const flatAll = {
plugins: {
'@lexical/internal': plugin,
},
rules: {
'@lexical/internal/no-imports-from-self': 'error',
'@lexical/internal/no-optional-chaining': 'error',
},
};

plugin.configs['flat/all'] = flatAll;
plugin.configs['flat/recommended'] = flatAll;

module.exports = plugin;
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,9 @@
/** @type {import('eslint').Rule.RuleModule} */
module.exports = {
create(context) {
const sourceCode = context.getSourceCode();
// ESLint 9+ provides sourceCode directly on context (required in ESLint 10+)
// ESLint 7-8 requires calling getSourceCode() method
const sourceCode = context.sourceCode || context.getSourceCode();

/**
* Checks if the given token is a `?.` token or not.
Expand Down
90 changes: 87 additions & 3 deletions packages/lexical-eslint-plugin/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@

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

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

## Installation

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

Then extend the recommended eslint config:
### ESLint 9+ (Flat Config)

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`:

```js
import lexical from '@lexical/eslint-plugin';

export default [
// ... other configs
lexical.configs['flat/recommended']
];
```

### ESLint 7-8 (Legacy Config)

For ESLint 7 or 8 with the legacy `.eslintrc` format, extend the recommended config:

```js
{
"extends": [
// ...
"plugin:@lexical/recommended"
"plugin:@lexical/legacy-recommended"
]
}
```

> **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.

### Custom Configuration

If you want more fine-grained configuration, you can instead add a snippet like this to your ESLint configuration file:
#### ESLint 9+ (Flat Config)

```js
import lexical from '@lexical/eslint-plugin';

export default [
{
plugins: {
'@lexical': lexical
},
rules: {
'@lexical/rules-of-lexical': 'error'
}
}
];
```

#### ESLint 7-8 (Legacy Config)

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

#### ESLint 9+ (Flat Config)

```js
import lexical from '@lexical/eslint-plugin';

export default [
{
plugins: {
'@lexical': lexical
},
rules: {
'@lexical/rules-of-lexical': [
'error',
{
isDollarFunction: ['^\\$[a-z_]'],
isIgnoredFunction: [],
isLexicalProvider: [
'parseEditorState',
'read',
'registerCommand',
'registerNodeTransform',
'update'
],
isSafeDollarFunction: ['^\\$is']
}
]
}
}
];
```

#### ESLint 7-8 (Legacy Config)

```js
{
"plugins": [
Expand Down Expand Up @@ -111,6 +180,21 @@ These \$functions are considered safe to call from anywhere, generally
these functions are runtime type checks that do not depend on any other
state.

## Testing

To verify that the plugin works with different ESLint versions, run the integration tests:

```bash
node packages/lexical-eslint-plugin/__tests__/integration-test.js
```

This will test:
- ✓ ESLint 8 with legacy `.eslintrc.json` configuration
- ✓ ESLint 10 with flat `eslint.config.js` configuration
- ✓ Legacy config name aliases (`recommended` vs `legacy-recommended`)

The tests use `pnpm dlx` to run different ESLint versions without modifying `package.json` or `pnpm-lock.yaml`.

## Valid and Invalid Examples

### Valid Examples
Expand Down
71 changes: 71 additions & 0 deletions packages/lexical-eslint-plugin/__tests__/integration/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
# ESLint Plugin Integration Tests

This directory contains integration tests that verify `@lexical/eslint-plugin` works correctly with different ESLint versions.

## Test Coverage

The integration tests verify:

- **ESLint 8.x** with legacy `.eslintrc.json` configuration
- **ESLint 10.x** with flat `eslint.config.js` configuration
- **Config name aliases** (`recommended` vs `legacy-recommended`)

## Running the Tests

From the repository root:

```bash
node packages/lexical-eslint-plugin/__tests__/integration-test.js
```

Or from the package directory:

```bash
cd packages/lexical-eslint-plugin
node __tests__/integration-test.js
```

## How It Works

The test script uses `pnpx` to run different ESLint versions without modifying `package.json` or `pnpm-lock.yaml`. This ensures:

- No dependency conflicts
- Clean testing environment
- Multiple ESLint versions can be tested in the same run

## Test Fixtures

### ESLint 8 (Legacy Config)

Located in `fixtures/eslint8-legacy/`:
- `.eslintrc.json` - Legacy ESLint configuration
- `valid.js` - Code that should pass linting
- `invalid.js` - Code that should trigger `@lexical/rules-of-lexical` errors

### ESLint 10 (Flat Config)

Located in `fixtures/eslint10-flat/`:
- `eslint.config.js` - Flat ESLint configuration
- `valid.js` - Code that should pass linting
- `invalid.js` - Code that should trigger `@lexical/rules-of-lexical` errors

## Expected Behavior

### Valid Code Examples

These should **pass** linting:
- Functions with `$` prefix calling other `$` functions
- Code inside `editor.update()` callbacks calling `$` functions
- Class methods calling `$` functions

### Invalid Code Examples

These should **fail** linting with `@lexical/rules-of-lexical` error:
- Functions without `$` prefix calling `$` functions directly

## Test Output

The test script provides colored output:
- ✓ Green = Test passed
- ✗ Red = Test failed
- Summary at the end with total/passed/failed counts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
# Test files copied during integration tests
./*/valid.js
./*/invalid.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
const lexical = require('@lexical/eslint-plugin');

module.exports = [
{
files: ['**/*.js'],
ignores: [],
...lexical.configs['flat/recommended'],
rules: {
'@lexical/rules-of-lexical': 'error',
},
languageOptions: {
ecmaVersion: 2020,
sourceType: 'module',
globals: {
module: 'readonly',
require: 'readonly',
},
},
},
];
Loading
Loading