@lexical/eslint-plugin 0.15.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/LexicalEslintPlugin.d.ts +21 -0
- package/LexicalEslintPlugin.dev.js +699 -0
- package/LexicalEslintPlugin.dev.mjs +697 -0
- package/LexicalEslintPlugin.js +11 -0
- package/LexicalEslintPlugin.js.flow +11 -0
- package/LexicalEslintPlugin.mjs +12 -0
- package/LexicalEslintPlugin.node.mjs +10 -0
- package/LexicalEslintPlugin.prod.js +20 -0
- package/LexicalEslintPlugin.prod.mjs +9 -0
- package/README.md +216 -0
- package/index.d.ts +14 -0
- package/package.json +49 -0
- package/rules/rules-of-lexical.d.ts +52 -0
- package/util/buildMatcher.d.ts +6 -0
- package/util/getFunctionName.d.ts +1 -0
- package/util/getParentAssignmentName.d.ts +1 -0
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Copyright (c) Meta Platforms, Inc. and affiliates.
|
|
3
|
+
*
|
|
4
|
+
* This source code is licensed under the MIT license found in the
|
|
5
|
+
* LICENSE file in the root directory of this source tree.
|
|
6
|
+
*
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import * as modDev from './LexicalEslintPlugin.dev.mjs';
|
|
10
|
+
import * as modProd from './LexicalEslintPlugin.prod.mjs';
|
|
11
|
+
const mod = process.env.NODE_ENV === 'development' ? modDev : modProd;
|
|
12
|
+
export default mod.default;
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Copyright (c) Meta Platforms, Inc. and affiliates.
|
|
3
|
+
*
|
|
4
|
+
* This source code is licensed under the MIT license found in the
|
|
5
|
+
* LICENSE file in the root directory of this source tree.
|
|
6
|
+
*
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
const mod = await (process.env.NODE_ENV === 'development' ? import('./LexicalEslintPlugin.dev.mjs') : import('./LexicalEslintPlugin.prod.mjs'));
|
|
10
|
+
export default mod.default;
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Copyright (c) Meta Platforms, Inc. and affiliates.
|
|
3
|
+
*
|
|
4
|
+
* This source code is licensed under the MIT license found in the
|
|
5
|
+
* LICENSE file in the root directory of this source tree.
|
|
6
|
+
*
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
'use strict';var l={},n={},p={getParentAssignmentName:function(a){let c=a.parent;if("VariableDeclarator"===c.type&&c.init===a)return c.id;if("AssignmentExpression"===c.type&&c.right===a&&"="===c.operator)return c.left}};let {getParentAssignmentName:r}=p;n.getFunctionName=function(a){if("FunctionDeclaration"===a.type||"FunctionExpression"===a.type&&a.id)return a.id;if("FunctionExpression"===a.type||"ArrowFunctionExpression"===a.type)return r(a)};
|
|
10
|
+
let {getFunctionName:t}=n,{getParentAssignmentName:z}=p,{buildMatcher:A}={buildMatcher:function(...a){let c=[],e=[];for(let b of a.flat(1))b&&("string"===typeof b?e.push(/^[(^]/.test(b)?b:`^${b.replace(/[.*+?^${}()|[\]\\]/g,"\\$&")}$`):b&&b instanceof RegExp?b.flags?c.push(h=>b.test(h)):e.push(b.source):"function"===typeof b&&c.push(b));if(a=e.map(b=>`(?:${b})`).join("|")){let b=new RegExp(a);c.push(h=>b.test(h))}return b=>{if(b){if("Identifier"!==b.type)throw Error(`Expecting Identifier, not ${b.type}`);
|
|
11
|
+
for(let h of c)if(h(b.name,b))return!0}return!1}}};function B(a,c){a=a.scopeManager;for(let b=c;b;b=b.parent){var e=a.getDeclaredVariables(b).find(h=>h.identifiers.includes(c));if(e)return e;if(e=a.acquire(b))return e.set.get(c.name)||(e.upper?e.upper.set.get(c.name):void 0)}}let C={isDollarFunction:[/^\$[a-z_]/],isIgnoredFunction:[],isLexicalProvider:["parseEditorState","read","registerCommand","registerNodeTransform","update"],isSafeDollarFunction:[/^\$is[A-Z_]/]};
|
|
12
|
+
function D(a){let c={};for(let u in C){var e=u,b=e,h=C[e],g=a;g=Array.isArray(g.options)?g.options[0]:void 0;c[b]=A(h,g&&e in g?g[e]:void 0)}return c}function E(a){if(a){if("Identifier"===a.type)return a;if("MemberExpression"===a.type&&!a.computed)return E(a.property)}}function L(a,c){a=a.name;a=/^[a-z]/.test(a)?"$"+a:/^[A-Z][a-z]/.test(a)?"$"+a.slice(0,1).toLowerCase()+a.slice(1):`$_${a}`;if(c)for(c=c.scope;c;c=c.upper)if(c.set.has(a))return a+"_";return a}
|
|
13
|
+
function M(a){if(a&&1===a.defs.length){[{node:a}]=a.defs;if("ExportNamedDeclaration"===a.parent.type)return a.parent;if("VariableDeclaration"===a.parent.type&&"ExportNamedDeclaration"===a.parent.parent.type)return a.parent.parent}}function N({caller:a,suggestName:c}){return`\n/** @deprecated renamed to {@link ${c}} by @lexical/eslint-plugin rules-of-lexical */\nexport const ${a} = ${c};`}let O={oneOf:[{type:"string"},{contains:{type:"string"},type:"array"}]};
|
|
14
|
+
l.rulesOfLexical={create(a){let c=a.getSourceCode(),e=D(a),b=new Set,h=new Set,g=[],u=()=>{if(0<b.size)return!0;const d=g[g.length-1];return d&&"Property"===d.node.parent.type},F=d=>b.delete(d),v=d=>{a:{var f=t(d);if(!f){f=d.parent;if("CallExpression"===f.type&&f.arguments[0]===d){let k=E(f.callee);if(k&&"Identifier"===k.type&&/^use([A-Z]|$)/.test(k.name)){f=z(f);break a}}f=void 0}}f=E(f);g.push({name:f,node:d});(e.isDollarFunction(f)||e.isIgnoredFunction(f)||e.isLexicalProvider(f))&&b.add(d)},w=
|
|
15
|
+
d=>{g.pop();b.delete(d)},W=()=>{const d=g[g.length-1];return d?d.name:void 0};return{ArrowFunctionExpression:v,"ArrowFunctionExpression:exit":w,CallExpression:d=>{if(!u()){var f=E(d.callee);if(e.isLexicalProvider(f)||e.isSafeDollarFunction(f))b.add(d);else if(e.isDollarFunction(f)){var k=W();if(k&&!h.has(k)){h.add(k);var q=B(c,k),G=L(k,q),H=M(q),x={callee:c.getText(d.callee),caller:c.getText(k),suggestName:G};d=I=>{const J=new Set,y=[],K=m=>{J.has(m)||(J.add(m),y.push(I.replaceText(m,G)))};K(k);H&&
|
|
16
|
+
y.push(I.insertTextAfter(H,N(x)));if(q)for(const m of q.references)K(m.identifier);return y};a.report({data:x,fix:d,messageId:"rulesOfLexicalReport",node:k,suggest:[{data:x,fix:d,messageId:"rulesOfLexicalSuggestion"}]})}}}},"CallExpression:exit":F,ClassBody:d=>b.add(d),"ClassBody:exit":F,FunctionDeclaration:v,"FunctionDeclaration:exit":w,FunctionExpression:v,"FunctionExpression:exit":w}},meta:{docs:{description:"enforces the Rules of Lexical",recommended:!0,url:"https://lexical.dev/docs/packages/lexical-eslint-plugin"},
|
|
17
|
+
fixable:"code",hasSuggestions:!0,messages:{rulesOfLexicalReport:"{{ callee }} called from {{ caller }}, without $ prefix or read/update context",rulesOfLexicalSuggestion:"Rename {{ caller }} to {{ suggestName }}"},schema:[{additionalProperties:!1,properties:{isDollarFunction:O,isIgnoredFunction:O,isLexicalProvider:O,isSafeDollarFunction:O},type:"object"}],type:"suggestion"}};
|
|
18
|
+
let {name:P,version:Q}={name:"@lexical/eslint-plugin",description:"Lexical specific linting rules for ESLint",keywords:["eslint","eslint-plugin","eslintplugin","lexical","editor"],version:"0.15.0",license:"MIT",repository:{type:"git",url:"git+https://github.com/facebook/lexical.git",directory:"packages/lexical-eslint-plugin"},main:"LexicalEslintPlugin.js",types:"index.d.ts",bugs:{url:"https://github.com/facebook/lexical/issues"},homepage:"https://lexical.dev/docs/packages/lexical-eslint-plugin",sideEffects:!1,
|
|
19
|
+
peerDependencies:{eslint:">=7.31.0 || ^8.0.0"},exports:{".":{"import":{types:"./index.d.ts",development:"./LexicalEslintPlugin.dev.mjs",production:"./LexicalEslintPlugin.prod.mjs",node:"./LexicalEslintPlugin.node.mjs","default":"./LexicalEslintPlugin.mjs"},require:{types:"./index.d.ts",development:"./LexicalEslintPlugin.dev.js",production:"./LexicalEslintPlugin.prod.js","default":"./LexicalEslintPlugin.js"}}},devDependencies:{"@types/eslint":"^8.56.9"},module:"LexicalEslintPlugin.mjs"},{rulesOfLexical:R}=
|
|
20
|
+
l,S={plugins:["@lexical"],rules:{"@lexical/rules-of-lexical":"warn"}};for(var T={configs:{all:S,recommended:S},meta:{name:P,version:Q},rules:{"rules-of-lexical":R}},U={__proto__:null,default:T&&T.__esModule&&Object.prototype.hasOwnProperty.call(T,"default")?T["default"]:T},V=[T],X=0;X<V.length;X++){var Y=V[X];if("string"!==typeof Y&&!Array.isArray(Y))for(var Z in Y)"default"===Z||Z in U||(U[Z]=Y[Z])}module.exports=U
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Copyright (c) Meta Platforms, Inc. and affiliates.
|
|
3
|
+
*
|
|
4
|
+
* This source code is licensed under the MIT license found in the
|
|
5
|
+
* LICENSE file in the root directory of this source tree.
|
|
6
|
+
*
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
function e(e,t){for(var n=0;n<t.length;n++){var i=t[n];if("string"!=typeof i&&!Array.isArray(i))for(var r in i)"default"===r||r in e||(e[r]=i[r])}return e}function t(e){return e&&e.__esModule&&Object.prototype.hasOwnProperty.call(e,"default")?e.default:e}var n={name:"@lexical/eslint-plugin",description:"Lexical specific linting rules for ESLint",keywords:["eslint","eslint-plugin","eslintplugin","lexical","editor"],version:"0.15.0",license:"MIT",repository:{type:"git",url:"git+https://github.com/facebook/lexical.git",directory:"packages/lexical-eslint-plugin"},main:"LexicalEslintPlugin.js",types:"index.d.ts",bugs:{url:"https://github.com/facebook/lexical/issues"},homepage:"https://lexical.dev/docs/packages/lexical-eslint-plugin",sideEffects:!1,peerDependencies:{eslint:">=7.31.0 || ^8.0.0"},exports:{".":{import:{types:"./index.d.ts",development:"./LexicalEslintPlugin.dev.mjs",production:"./LexicalEslintPlugin.prod.mjs",node:"./LexicalEslintPlugin.node.mjs",default:"./LexicalEslintPlugin.mjs"},require:{types:"./index.d.ts",development:"./LexicalEslintPlugin.dev.js",production:"./LexicalEslintPlugin.prod.js",default:"./LexicalEslintPlugin.js"}}},devDependencies:{"@types/eslint":"^8.56.9"},module:"LexicalEslintPlugin.mjs"},i={},r={},s={getParentAssignmentName:function(e){const t=e.parent;return"VariableDeclarator"===t.type&&t.init===e?t.id:"AssignmentExpression"===t.type&&t.right===e&&"="===t.operator?t.left:void 0}};const{getParentAssignmentName:o}=s;r.getFunctionName=function(e){return"FunctionDeclaration"===e.type||"FunctionExpression"===e.type&&e.id?e.id:"FunctionExpression"===e.type||"ArrowFunctionExpression"===e.type?o(e):void 0};var a={};a.buildMatcher=function(...e){const t=[],n=[];for(const r of e.flat(1))r&&("string"==typeof r?n.push(/^[(^]/.test(r)?r:`^${i=r,i.replace(/[.*+?^${}()|[\]\\]/g,"\\$&")}$`):r&&r instanceof RegExp?r.flags?t.push((e=>r.test(e))):n.push(r.source):"function"==typeof r&&t.push(r));var i;const r=n.map((e=>`(?:${e})`)).join("|");if(r){const e=new RegExp(r);t.push((t=>e.test(t)))}return e=>{if(e){if("Identifier"!==e.type)throw new Error(`Expecting Identifier, not ${e.type}`);for(const n of t)if(n(e.name,e))return!0}return!1}};const{getFunctionName:l}=r,{getParentAssignmentName:c}=s,{buildMatcher:u}=a;const p={isDollarFunction:[/^\$[a-z_]/],isIgnoredFunction:[],isLexicalProvider:["parseEditorState","read","registerCommand","registerNodeTransform","update"],isSafeDollarFunction:[/^\$is[A-Z_]/]};function d(e){if(e)return"Identifier"===e.type?e:"MemberExpression"!==e.type||e.computed?void 0:d(e.property)}function f(e,t){const n=Array.isArray(e.options)?e.options[0]:void 0;return n&&t in n?n[t]:void 0}const g={oneOf:[{type:"string"},{contains:{type:"string"},type:"array"}]};i.rulesOfLexical={create(e){const t=function(e){return e.getSourceCode()}(e),n=function(e){const t={};for(const n in p){const i=n;t[i]=u(p[i],f(e,i))}return t}(e),i=new Set,r=new Set,s=[],o=e=>i.add(e),a=e=>i.delete(e),g=e=>{const t=d(function(e){const t=l(e);if(t)return t;const n=e.parent;if("CallExpression"===n.type&&n.arguments[0]===e&&function(e){return e&&"Identifier"===e.type&&/^use([A-Z]|$)/.test(e.name)}(d(n.callee)))return c(n)}(e));s.push({name:t,node:e}),(n.isDollarFunction(t)||n.isIgnoredFunction(t)||n.isLexicalProvider(t))&&o(e)},x=e=>{s.pop(),a(e)};return{ArrowFunctionExpression:g,"ArrowFunctionExpression:exit":x,CallExpression:a=>{if((()=>{if(i.size>0)return!0;const e=s[s.length-1];return e&&"Property"===e.node.parent.type})())return;const l=d(a.callee);if(n.isLexicalProvider(l)||n.isSafeDollarFunction(l))return void o(a);if(!n.isDollarFunction(l))return;const c=(e=>{const t=s[s.length-1];return t?t.name:void 0})();if(!c||r.has(c))return;r.add(c);const u=function(e,t){const n=e.scopeManager;for(let e=t;e;e=e.parent){const i=n.getDeclaredVariables(e).find((e=>e.identifiers.includes(t)));if(i)return i;const r=n.acquire(e);if(r)return r.set.get(t.name)||(r.upper?r.upper.set.get(t.name):void 0)}}(t,c),p=function(e,t){const n=function(e){return/^[a-z]/.test(e)?"$"+e:/^[A-Z][a-z]/.test(e)?"$"+e.slice(0,1).toLowerCase()+e.slice(1):`$_${e}`}(e.name);if(t)for(let e=t.scope;e;e=e.upper)if(e.set.has(n))return n+"_";return n}(c,u),f=function(e){if(e&&1===e.defs.length){const[{node:t}]=e.defs;if("ExportNamedDeclaration"===t.parent.type)return t.parent;if("VariableDeclaration"===t.parent.type&&"ExportNamedDeclaration"===t.parent.parent.type)return t.parent.parent}}(u),g={callee:t.getText(a.callee),caller:t.getText(c),suggestName:p},x=e=>{const t=new Set,n=[],i=i=>{t.has(i)||(t.add(i),n.push(e.replaceText(i,p)))};if(i(c),f&&n.push(e.insertTextAfter(f,function({caller:e,suggestName:t}){return`\n/** @deprecated renamed to {@link ${t}} by @lexical/eslint-plugin rules-of-lexical */\nexport const ${e} = ${t};`}(g))),u)for(const e of u.references)i(e.identifier);return n};e.report({data:g,fix:x,messageId:"rulesOfLexicalReport",node:c,suggest:[{data:g,fix:x,messageId:"rulesOfLexicalSuggestion"}]})},"CallExpression:exit":a,ClassBody:o,"ClassBody:exit":a,FunctionDeclaration:g,"FunctionDeclaration:exit":x,FunctionExpression:g,"FunctionExpression:exit":x}},meta:{docs:{description:"enforces the Rules of Lexical",recommended:!0,url:"https://lexical.dev/docs/packages/lexical-eslint-plugin"},fixable:"code",hasSuggestions:!0,messages:{rulesOfLexicalReport:"{{ callee }} called from {{ caller }}, without $ prefix or read/update context",rulesOfLexicalSuggestion:"Rename {{ caller }} to {{ suggestName }}"},schema:[{additionalProperties:!1,properties:{isDollarFunction:g,isIgnoredFunction:g,isLexicalProvider:g,isSafeDollarFunction:g},type:"object"}],type:"suggestion"}};const{name:x,version:m}=n,{rulesOfLexical:y}=i,h={plugins:["@lexical"],rules:{"@lexical/rules-of-lexical":"warn"}};var E={configs:{all:h,recommended:h},meta:{name:x,version:m},rules:{"rules-of-lexical":y}},v=e({__proto__:null,default:t(E)},[E]);export{v as default};
|
package/README.md
ADDED
|
@@ -0,0 +1,216 @@
|
|
|
1
|
+
# `@lexical/eslint-plugin`
|
|
2
|
+
|
|
3
|
+
This ESLint plugin enforces the [Lexical $function convention](https://lexical.dev/docs/intro#reading-and-updating-editor-state).
|
|
4
|
+
|
|
5
|
+
## Installation
|
|
6
|
+
|
|
7
|
+
Assuming you already have ESLint installed, run:
|
|
8
|
+
|
|
9
|
+
```sh
|
|
10
|
+
npm install @lexical/eslint-plugin --save-dev
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
Then extend the recommended eslint config:
|
|
14
|
+
|
|
15
|
+
```js
|
|
16
|
+
{
|
|
17
|
+
"extends": [
|
|
18
|
+
// ...
|
|
19
|
+
"plugin:@lexical/recommended"
|
|
20
|
+
]
|
|
21
|
+
}
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
### Custom Configuration
|
|
25
|
+
|
|
26
|
+
If you want more fine-grained configuration, you can instead add a snippet like this to your ESLint configuration file:
|
|
27
|
+
|
|
28
|
+
```js
|
|
29
|
+
{
|
|
30
|
+
"plugins": [
|
|
31
|
+
// ...
|
|
32
|
+
"@lexical"
|
|
33
|
+
],
|
|
34
|
+
"rules": {
|
|
35
|
+
// ...
|
|
36
|
+
"@lexical/rules-of-lexical": "error"
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
### Advanced configuration
|
|
42
|
+
|
|
43
|
+
Most of the heuristics in `@lexical/rules-of-lexical` can be extended with
|
|
44
|
+
additional terms or patterns.
|
|
45
|
+
|
|
46
|
+
The code example below is shown using the default implementations for each
|
|
47
|
+
option. When you configure these they are combined with the default
|
|
48
|
+
implementations using "OR", the default implementations can not be overridden.
|
|
49
|
+
These terms and patterns are only shown for reference and pasting this example
|
|
50
|
+
into your project is not useful.
|
|
51
|
+
|
|
52
|
+
If the string begins with a `"^"` or `"("` then it is treated as a RegExp,
|
|
53
|
+
otherwise it will be an exact match. A string may also be used instead
|
|
54
|
+
of an array of strings.
|
|
55
|
+
|
|
56
|
+
```js
|
|
57
|
+
{
|
|
58
|
+
"plugins": [
|
|
59
|
+
// ...
|
|
60
|
+
"@lexical"
|
|
61
|
+
],
|
|
62
|
+
"rules": {
|
|
63
|
+
// ...
|
|
64
|
+
"@lexical/rules-of-lexical": [
|
|
65
|
+
"error",
|
|
66
|
+
{
|
|
67
|
+
"isDollarFunction": ["^\\$[a-z_]"],
|
|
68
|
+
"isIgnoredFunction": [],
|
|
69
|
+
"isLexicalProvider": [
|
|
70
|
+
"parseEditorState",
|
|
71
|
+
"read",
|
|
72
|
+
"registerCommand",
|
|
73
|
+
"registerNodeTransform",
|
|
74
|
+
"update"
|
|
75
|
+
],
|
|
76
|
+
"isSafeDollarFunction": ["^\\$is"]
|
|
77
|
+
}
|
|
78
|
+
]
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
#### `isDollarFunction`
|
|
84
|
+
|
|
85
|
+
*Base case*: `/^\$[a-z_]/`
|
|
86
|
+
|
|
87
|
+
This defines the \$function convention, which by default is any function that
|
|
88
|
+
starts with a dollar sign followed by a lowercase latin letter. You may have a
|
|
89
|
+
secondary convention in your codebase, such as non-latin letters, or an
|
|
90
|
+
internal prefix that you want to consider (e.g. `"^INTERNAL_\\$"`).
|
|
91
|
+
|
|
92
|
+
#### `isIgnoredFunction`
|
|
93
|
+
|
|
94
|
+
*Base case*: None
|
|
95
|
+
|
|
96
|
+
Functions that match these patterns are ignored from analysis, they may call
|
|
97
|
+
Lexical \$functions but are not considered to be a dollar function themselves.
|
|
98
|
+
|
|
99
|
+
#### `isLexicalProvider`
|
|
100
|
+
|
|
101
|
+
*Base case*: `/^(parseEditorState|read|registerCommand|registerNodeTransform|update)$/`
|
|
102
|
+
|
|
103
|
+
These are functions that allow their function argument to use Lexical
|
|
104
|
+
\$functions.
|
|
105
|
+
|
|
106
|
+
#### `isSafeDollarFunction`
|
|
107
|
+
|
|
108
|
+
*Base case*: `/^\$is/`
|
|
109
|
+
|
|
110
|
+
These \$functions are considered safe to call from anywhere, generally
|
|
111
|
+
these functions are runtime type checks that do not depend on any other
|
|
112
|
+
state.
|
|
113
|
+
|
|
114
|
+
## Valid and Invalid Examples
|
|
115
|
+
|
|
116
|
+
### Valid Examples
|
|
117
|
+
|
|
118
|
+
\$functions may be called by other \$functions
|
|
119
|
+
|
|
120
|
+
```js
|
|
121
|
+
function $namedCorrectly() {
|
|
122
|
+
return $getRoot();
|
|
123
|
+
}
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
\$functions may be called in functions defined when calling the following
|
|
127
|
+
methods (the heuristic only considers the method name):
|
|
128
|
+
|
|
129
|
+
* `editor.update`
|
|
130
|
+
* `editorState.read`
|
|
131
|
+
* `editor.registerCommand`
|
|
132
|
+
* `editor.registerNodeTransform`
|
|
133
|
+
|
|
134
|
+
```js
|
|
135
|
+
function validUsesEditorOrState(editor) {
|
|
136
|
+
editor.update(() => $getRoot());
|
|
137
|
+
editor.getLatestState().read(() => $getRoot());
|
|
138
|
+
}
|
|
139
|
+
```
|
|
140
|
+
|
|
141
|
+
\$functions may be called from class methods
|
|
142
|
+
|
|
143
|
+
```js
|
|
144
|
+
class CustomNode extends ElementNode {
|
|
145
|
+
appendText(string) {
|
|
146
|
+
this.appendChild($createTextNode(string));
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
```
|
|
150
|
+
|
|
151
|
+
### Invalid Examples
|
|
152
|
+
|
|
153
|
+
#### Rename autofix
|
|
154
|
+
|
|
155
|
+
```js
|
|
156
|
+
function invalidFunction() {
|
|
157
|
+
return $getRoot();
|
|
158
|
+
}
|
|
159
|
+
function $callsInvalidFunction() {
|
|
160
|
+
return invalidFunction();
|
|
161
|
+
}
|
|
162
|
+
```
|
|
163
|
+
|
|
164
|
+
*Autofix:* The function is renamed with a $ prefix. Any references to this
|
|
165
|
+
name in this module are also always renamed.
|
|
166
|
+
|
|
167
|
+
```js
|
|
168
|
+
function $invalidFunction() {
|
|
169
|
+
return $getRoot();
|
|
170
|
+
}
|
|
171
|
+
function $callsInvalidFunction() {
|
|
172
|
+
return $invalidFunction();
|
|
173
|
+
}
|
|
174
|
+
```
|
|
175
|
+
|
|
176
|
+
#### Rename & deprecate autofix
|
|
177
|
+
|
|
178
|
+
```js
|
|
179
|
+
export function exportedInvalidFunction() {
|
|
180
|
+
return $getRoot();
|
|
181
|
+
}
|
|
182
|
+
```
|
|
183
|
+
|
|
184
|
+
*Autofix:* The exported function is renamed with a $ prefix. The previous name
|
|
185
|
+
is also exported and marked deprecated, because automatic renaming of
|
|
186
|
+
references to that name is limited to the module's scope.
|
|
187
|
+
|
|
188
|
+
```js
|
|
189
|
+
export function $exportedInvalidFunction() {
|
|
190
|
+
return $getRoot();
|
|
191
|
+
}
|
|
192
|
+
/** @deprecated renamed to {@link $exportedInvalidFunction} by @lexical/eslint-plugin rules-of-lexical */
|
|
193
|
+
export const exportedInvalidFunction = $exportedInvalidFunction;
|
|
194
|
+
```
|
|
195
|
+
|
|
196
|
+
#### Rename scope conflict
|
|
197
|
+
|
|
198
|
+
```js
|
|
199
|
+
import {$getRoot} from 'lexical';
|
|
200
|
+
function InvalidComponent() {
|
|
201
|
+
const [editor] = useLexicalComposerContext();
|
|
202
|
+
const getRoot = useCallback(() => $getRoot(), []);
|
|
203
|
+
return (<button onClick={() => editor.update(() => getRoot())} />);
|
|
204
|
+
}
|
|
205
|
+
```
|
|
206
|
+
|
|
207
|
+
*Autofix:* The function is renamed with a $ prefix and _ suffix since the suggested name was already in scope.
|
|
208
|
+
|
|
209
|
+
```js
|
|
210
|
+
import {$getRoot} from 'lexical';
|
|
211
|
+
function InvalidComponent() {
|
|
212
|
+
const [editor] = useLexicalComposerContext();
|
|
213
|
+
const $getRoot_ = useCallback(() => $getRoot(), []);
|
|
214
|
+
return (<button onClick={() => editor.update(() => $getRoot_())} />);
|
|
215
|
+
}
|
|
216
|
+
```
|
package/index.d.ts
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Copyright (c) Meta Platforms, Inc. and affiliates.
|
|
3
|
+
*
|
|
4
|
+
* This source code is licensed under the MIT license found in the
|
|
5
|
+
* LICENSE file in the root directory of this source tree.
|
|
6
|
+
*
|
|
7
|
+
*/
|
|
8
|
+
/**
|
|
9
|
+
* For bootstrapping reasons, this module is written in CJS JavaScript so no
|
|
10
|
+
* compilation is necessary
|
|
11
|
+
*/
|
|
12
|
+
import * as plugin from './LexicalEslintPlugin.js';
|
|
13
|
+
export type { RulesOfLexicalOptions } from './rules/rules-of-lexical.js';
|
|
14
|
+
export default plugin;
|
package/package.json
ADDED
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@lexical/eslint-plugin",
|
|
3
|
+
"description": "Lexical specific linting rules for ESLint",
|
|
4
|
+
"keywords": [
|
|
5
|
+
"eslint",
|
|
6
|
+
"eslint-plugin",
|
|
7
|
+
"eslintplugin",
|
|
8
|
+
"lexical",
|
|
9
|
+
"editor"
|
|
10
|
+
],
|
|
11
|
+
"version": "0.15.0",
|
|
12
|
+
"license": "MIT",
|
|
13
|
+
"repository": {
|
|
14
|
+
"type": "git",
|
|
15
|
+
"url": "git+https://github.com/facebook/lexical.git",
|
|
16
|
+
"directory": "packages/lexical-eslint-plugin"
|
|
17
|
+
},
|
|
18
|
+
"main": "LexicalEslintPlugin.js",
|
|
19
|
+
"types": "index.d.ts",
|
|
20
|
+
"bugs": {
|
|
21
|
+
"url": "https://github.com/facebook/lexical/issues"
|
|
22
|
+
},
|
|
23
|
+
"homepage": "https://lexical.dev/docs/packages/lexical-eslint-plugin",
|
|
24
|
+
"sideEffects": false,
|
|
25
|
+
"peerDependencies": {
|
|
26
|
+
"eslint": ">=7.31.0 || ^8.0.0"
|
|
27
|
+
},
|
|
28
|
+
"exports": {
|
|
29
|
+
".": {
|
|
30
|
+
"import": {
|
|
31
|
+
"types": "./index.d.ts",
|
|
32
|
+
"development": "./LexicalEslintPlugin.dev.mjs",
|
|
33
|
+
"production": "./LexicalEslintPlugin.prod.mjs",
|
|
34
|
+
"node": "./LexicalEslintPlugin.node.mjs",
|
|
35
|
+
"default": "./LexicalEslintPlugin.mjs"
|
|
36
|
+
},
|
|
37
|
+
"require": {
|
|
38
|
+
"types": "./index.d.ts",
|
|
39
|
+
"development": "./LexicalEslintPlugin.dev.js",
|
|
40
|
+
"production": "./LexicalEslintPlugin.prod.js",
|
|
41
|
+
"default": "./LexicalEslintPlugin.js"
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
},
|
|
45
|
+
"devDependencies": {
|
|
46
|
+
"@types/eslint": "^8.56.9"
|
|
47
|
+
},
|
|
48
|
+
"module": "LexicalEslintPlugin.mjs"
|
|
49
|
+
}
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
export const rulesOfLexical: RuleModule;
|
|
2
|
+
export type NodeParentExtension = import('eslint').Rule.NodeParentExtension;
|
|
3
|
+
export type CallExpression = import('estree').CallExpression & NodeParentExtension;
|
|
4
|
+
export type Identifier = import('estree').Identifier & NodeParentExtension;
|
|
5
|
+
export type RuleContext = import('eslint').Rule.RuleContext;
|
|
6
|
+
export type Fix = import('eslint').Rule.Fix;
|
|
7
|
+
export type Node = import('eslint').Rule.Node;
|
|
8
|
+
export type RuleModule = import('eslint').Rule.RuleModule;
|
|
9
|
+
export type ReportFixer = import('eslint').Rule.ReportFixer;
|
|
10
|
+
export type SourceCode = import('eslint').SourceCode;
|
|
11
|
+
export type Variable = import('eslint').Scope.Variable;
|
|
12
|
+
export type Scope = import('eslint').Scope.Scope;
|
|
13
|
+
export type RulesOfLexicalOptions = Partial<BaseMatchers<ToMatcher | ToMatcher[]>>;
|
|
14
|
+
export type Matchers = BaseMatchers<IdentifierMatcher>;
|
|
15
|
+
export type ToMatcher = import('../util/buildMatcher.js').ToMatcher;
|
|
16
|
+
export type IdentifierMatcher = import('../util/buildMatcher.js').IdentifierMatcher;
|
|
17
|
+
/**
|
|
18
|
+
* <T>
|
|
19
|
+
*/
|
|
20
|
+
export type BaseMatchers<T> = {
|
|
21
|
+
/**
|
|
22
|
+
* Catch all identifiers that begin with '$' or 'INTERNAL_$' followed by a lowercase Latin character or underscore
|
|
23
|
+
*/
|
|
24
|
+
isDollarFunction: T;
|
|
25
|
+
/**
|
|
26
|
+
* These functions may call any $functions even though they do not have the isDollarFunction naming convention
|
|
27
|
+
*/
|
|
28
|
+
isIgnoredFunction: T;
|
|
29
|
+
/**
|
|
30
|
+
* Certain calls through the editor or editorState allow for implicit access to call $functions: read, registerCommand, registerNodeTransform, update.
|
|
31
|
+
*/
|
|
32
|
+
isLexicalProvider: T;
|
|
33
|
+
/**
|
|
34
|
+
* It's usually safe to call $isNode functions, so any '$is' or 'INTERNAL_$is' function may be called in any context.
|
|
35
|
+
*/
|
|
36
|
+
isSafeDollarFunction: T;
|
|
37
|
+
};
|
|
38
|
+
/**
|
|
39
|
+
* @typedef {import('../util/buildMatcher.js').ToMatcher} ToMatcher
|
|
40
|
+
* @typedef {import('../util/buildMatcher.js').IdentifierMatcher} IdentifierMatcher
|
|
41
|
+
*/
|
|
42
|
+
/**
|
|
43
|
+
* @template T
|
|
44
|
+
* @typedef {Object} BaseMatchers<T>
|
|
45
|
+
* @property {T} isDollarFunction Catch all identifiers that begin with '$' or 'INTERNAL_$' followed by a lowercase Latin character or underscore
|
|
46
|
+
* @property {T} isIgnoredFunction These functions may call any $functions even though they do not have the isDollarFunction naming convention
|
|
47
|
+
* @property {T} isLexicalProvider Certain calls through the editor or editorState allow for implicit access to call $functions: read, registerCommand, registerNodeTransform, update.
|
|
48
|
+
* @property {T} isSafeDollarFunction It's usually safe to call $isNode functions, so any '$is' or 'INTERNAL_$is' function may be called in any context.
|
|
49
|
+
*/
|
|
50
|
+
/** @type {BaseMatchers<Exclude<ToMatcher, undefined>[]>} */
|
|
51
|
+
declare const BaseMatchers: BaseMatchers<Exclude<ToMatcher, undefined>[]>;
|
|
52
|
+
export {};
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
export function buildMatcher(...toMatchers: any[]): IdentifierMatcher;
|
|
2
|
+
export type Node = import('estree').Node;
|
|
3
|
+
export type Identifier = import('estree').Identifier;
|
|
4
|
+
export type NameIdentifierMatcher = (name: string, node: Identifier) => boolean;
|
|
5
|
+
export type ToMatcher = NameIdentifierMatcher | string | RegExp | undefined;
|
|
6
|
+
export type IdentifierMatcher = (node: Identifier | undefined) => boolean;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export function getFunctionName(node: import('eslint').Rule.Node): any;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export function getParentAssignmentName(node: import('eslint').Rule.Node): import("estree").Pattern | undefined;
|