facebook / lexical

Lexical is an extensible text editor framework that provides excellent reliability, accessibility and performance.
https://lexical.dev
MIT License
17.37k stars 1.43k forks source link

feat(@lexical/eslint-plugin): new package with eslint rules for lexical #5908

Open etrepum opened 2 weeks ago

etrepum commented 2 weeks ago

@lexical/eslint-plugin - an eslint plugin that helps with the rules of lexical ($functions) and provides an autofixer

Documentation preview: https://lexical-qke2cx8a9-fbopensource.vercel.app/docs/packages/lexical-eslint-plugin

Currently defines one rule: @lexical/rules-of-lexical

There's documentation per #5869 for adding a new package to npm already in the maintainers guide: https://lexical-qke2cx8a9-fbopensource.vercel.app/docs/maintainers-guide#creating-a-new-package

The thing about this package that's atypical relative to how most new packages would be created is that it's entirely written in cjs (not TypeScript or using modules), so that it can be used in the monorepo with our version of eslint without compilation. There are plenty of jsdoc comments for type checking though.

The design is basically that it will look for $function call expressions and see if they were made in an allowed context (e.g. from another $function). If they are not, then it will suggest a rename of fn to $fn.

Some complex situations that rules-of-lexical handles:

Avoid shadowing an existing variable BEFORE ```typescript const createKeywordNode = useCallback((textNode: TextNode): KeywordNode => { return $createKeywordNode(textNode.getTextContent()); }, []); ``` AFTER ```typescript const $createKeywordNode_ = useCallback((textNode: TextNode): KeywordNode => { return $createKeywordNode(textNode.getTextContent()); }, []); ```
Renaming an export BEFORE ```typescript /** * Traverses up the tree and returns the first ListItemNode found. * @param node - Node to start the search. * @returns The first ListItemNode found, or null if none exist. */ export function findNearestListItemNode( node: LexicalNode, ): ListItemNode | null { const matchingParent = $findMatchingParent(node, (parent) => $isListItemNode(parent), ); return matchingParent as ListItemNode | null; } ``` AFTER ```typescript /** * Traverses up the tree and returns the first ListItemNode found. * @param node - Node to start the search. * @returns The first ListItemNode found, or null if none exist. */ export function $findNearestListItemNode( node: LexicalNode, ): ListItemNode | null { const matchingParent = $findMatchingParent(node, (parent) => $isListItemNode(parent), ); return matchingParent as ListItemNode | null; } /** @deprecated renamed to $findNearestListItemNode by @lexical/eslint-plugin rules-of-lexical */ export const findNearestListItemNode = $findNearestListItemNode; ```

I've tested the output artifact (the npm pack that our integration tests build) with a project outside of the monorepo in this branch: https://github.com/etrepum/lexical-esm-nextjs/tree/lexical-eslint-plugin

I stripped this down to just the new code, it does not include fixes to any existing code.

I believe you should be able to try this out outside of the monorepo before it is released by installing it this way:

npm i --save-dev https://gitpkg.now.sh/etrepum/lexical/packages/lexical-eslint-plugin?lexical-eslint-plugin

To see a concrete example of what this plugin would do in the monorepo, you can check out the last commit in #5979 https://github.com/facebook/lexical/pull/5979/commits/095502e543085b497fa18834c99e6d0eb9331997

vercel[bot] commented 2 weeks ago

The latest updates on your projects. Learn more about Vercel for Git ↗︎

Name Status Preview Comments Updated (UTC)
lexical ✅ Ready (Inspect) Visit Preview 💬 Add feedback Apr 27, 2024 8:56pm
lexical-playground ✅ Ready (Inspect) Visit Preview 💬 Add feedback Apr 27, 2024 8:56pm