-
Notifications
You must be signed in to change notification settings - Fork 1.7k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
feat(@lexical/eslint-plugin): new package with eslint rules for lexical #5908
Conversation
The latest updates on your projects. Learn more about Vercel for Git ↗︎
|
89b252d
to
0f7b246
Compare
0f7b246
to
efdd7d7
Compare
efdd7d7
to
ba8aec8
Compare
ba8aec8
to
f662d7a
Compare
f662d7a
to
447f8bb
Compare
447f8bb
to
c638a14
Compare
c638a14
to
761f14d
Compare
761f14d
to
690f739
Compare
690f739
to
e48cad5
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
- This looks great. Thanks for working on this!
- i saw we have some items in our roadmap to add lint rules related to examples like: "prevent mistakes - append inline on RootNode or $createTextNode('\n')" , we could potentially extend this.
- also once this lands, will plan on the script to sync the lint rules to Meta as well
// @lexical/yjs | ||
'createBinding', | ||
], | ||
isSafeDollarFunction: '$createRootNode', |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
just a question, is this supposed to be array as well like other ?
isSafeDollarFunction: ['$createRootNode'],
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Either works, I wrote it this way mostly to demonstrate that you do not need to wrap it with an array. Basically a convenience for OSS users who may just have one pattern or string to configure.
Here are some of the relevant types, I pasted them here together:
/**
* @typedef {(name: string, node: Identifier) => boolean} NameIdentifierMatcher
* @typedef {NameIdentifierMatcher | string | RegExp | undefined} ToMatcher
* @typedef {Partial<BaseMatchers<ToMatcher | ToMatcher[]>>} RulesOfLexicalOptions
*/
/**
* @template T
* @typedef {Object} BaseMatchers<T>
* @property {T} isDollarFunction Catch all identifiers that begin with '$' or 'INTERNAL_$' followed by a lowercase Latin character or underscore
* @property {T} isIgnoredFunction These functions may call any $functions even though they do not have the isDollarFunction naming convention
* @property {T} isLexicalProvider Certain calls through the editor or editorState allow for implicit access to call $functions: read, registerCommand, registerNodeTransform, update.
* @property {T} isSafeDollarFunction It's usually safe to call $isNode functions, so any '$is' or 'INTERNAL_$is' function may be called in any context.
*/
secondary convention in your codebase, such as non-latin letters, or an | ||
internal prefix that you want to consider (e.g. `"^INTERNAL_\\$"`). | ||
|
||
#### `isIgnoredFunction` |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
- right now we have isIgnoredFunction to bypass lint at project level if i understand correctly ?
- Should this isIgnoredFunction be at a rule level as well, apart from having it to ignore all rules for the project
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
All configuration here only applies to the @lexical/rules-of-lexical rule. In the example above you can see how it is scoped
{
"plugins": [
// ...
"@lexical"
],
"rules": {
// ...
"@lexical/rules-of-lexical": [
"error",
{
"isDollarFunction": ["^\\$[a-z_]"],
"isIgnoredFunction": [],
"isLexicalProvider": [
"parseEditorState",
"read",
"registerCommand",
"registerNodeTransform",
"update"
],
"isSafeDollarFunction": ["^\\$is"]
}
]
}
}
I suppose it is somewhat confusing because the name "rules-of-lexical" is plural, so it sounds like it maybe applies to more than one eslint rule, but I just took the naming convention from "react-hooks/rules-of-hooks" since it serves a very similar purpose.
@Sahejkm it would be great to have a bit more context around what other rules would be useful, and in particular what the suggestions should be. |
Yep this looks like a great idea. i quoted the extra lint rule examples from the "Lexical OSS Roadmap 2024" items. sorry for missing the details around it, those could be separate PRs /enhancements. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LGTM
Latest commit just merges with master to bring in #6019 so the tests pass |
@Sahejkm is this "Lexical OSS Roadmap 2024" published somewhere that I would be able to read it? |
|
}, | ||
meta: {name, version}, | ||
rules: { | ||
'rules-of-lexical': rulesOfLexical, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
great work! im looking forward to adding a lint rule, maybe such as "prefering plain for loop over forEach/map or any lambda function for performance reasons"
to do so would i be adding a lint-rule-name.js
to rules folder, and add other stuff using this PR as an example?
if there are any docs i should be aware of do point me to them too!
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This plugin in particular is for rules that we want to offer to OSS users and I think that they should be focused on topics that relate to Lexical usage, not just our preferred code style for the monorepo.
We do have another plugin at ./eslint-plugin which can be used to create custom rules that only apply to the Lexical monorepo.
Those use cases sound like things that could probably be found in an existing eslint plugin somewhere, although I think that it would be tricky to automatically identify situations where we really care about using loops vs. higher-order functions. I would personally find that rule pretty annoying when working on build scripts or tests for example, but if it only applied to dollar functions and LexicalNode methods maybe it would make sense to include it here somehow!
As far as eslint plugin development goes, the resources that I found most useful were:
- https://github.com/facebook/react/tree/main/packages/eslint-plugin-react-hooks (I probably read through a few other plugin sources along the way)
- https://eslint.org/docs/latest/extend/plugins
- https://astexplorer.net/
- Reading through most of the relevant types and some of the source code to most of the things involved in the process.
I was already somewhat familiar from how all of this works from writing codemods and babel/vite/rollup plugins and in general working with ASTs and compilers in other languages. It is a deep topic!
Latest commit just resolves conflict in package.json and package-json.lock |
@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
AFTER
Renaming an export
BEFORE
AFTER
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:
To see a concrete example of what this plugin would do in the monorepo, you can check out the last commit in #5979 095502e