@lexical/eslint-plugin 0.44.1-nightly.20260519.0 → 0.45.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/{LexicalEslintPlugin.dev.js → dist/LexicalEslintPlugin.dev.js} +1 -1
- package/{LexicalEslintPlugin.dev.mjs → dist/LexicalEslintPlugin.dev.mjs} +1 -1
- package/dist/LexicalEslintPlugin.prod.js +9 -0
- package/dist/LexicalEslintPlugin.prod.mjs +9 -0
- package/package.json +27 -13
- package/src/LexicalEslintPlugin.js +53 -0
- package/src/index.ts +61 -0
- package/src/rules/rules-of-lexical.js +431 -0
- package/src/util/buildMatcher.js +77 -0
- package/src/util/getFunctionName.js +43 -0
- package/src/util/getParentAssignmentName.js +44 -0
- package/LexicalEslintPlugin.prod.js +0 -9
- package/LexicalEslintPlugin.prod.mjs +0 -9
- /package/{LexicalEslintPlugin.d.ts → dist/LexicalEslintPlugin.d.ts} +0 -0
- /package/{LexicalEslintPlugin.js → dist/LexicalEslintPlugin.js} +0 -0
- /package/{LexicalEslintPlugin.js.flow → dist/LexicalEslintPlugin.js.flow} +0 -0
- /package/{LexicalEslintPlugin.mjs → dist/LexicalEslintPlugin.mjs} +0 -0
- /package/{LexicalEslintPlugin.node.mjs → dist/LexicalEslintPlugin.node.mjs} +0 -0
- /package/{index.d.ts → dist/index.d.ts} +0 -0
- /package/{rules → dist/rules}/rules-of-lexical.d.ts +0 -0
- /package/{util → dist/util}/buildMatcher.d.ts +0 -0
- /package/{util → dist/util}/getFunctionName.d.ts +0 -0
- /package/{util → dist/util}/getParentAssignmentName.d.ts +0 -0
|
@@ -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
|
+
"use strict";function e(e,n){for(var t=0;t<n.length;t++){var r=n[t];if("string"!=typeof r&&!Array.isArray(r))for(var i in r)"default"===i||i in e||(e[i]=r[i])}return e}function n(e){return e&&e.__esModule&&Object.prototype.hasOwnProperty.call(e,"default")?e.default:e}var t,r,i={name:"@lexical/eslint-plugin",version:"0.45.0"},o={},s={},a={};function l(){return t||(t=1,a.getParentAssignmentName=function(e){const n=e.parent;if(null!=n)return"VariableDeclarator"===n.type&&n.init===e?n.id:"AssignmentExpression"===n.type&&n.right===e&&"="===n.operator?n.left:void 0}),a}function c(){if(r)return s;r=1;const{getParentAssignmentName:e}=l();return s.getFunctionName=function(n){return"FunctionDeclaration"===n.type||"FunctionExpression"===n.type&&n.id?n.id:"FunctionExpression"===n.type||"ArrowFunctionExpression"===n.type?e(n):void 0},s}var u,f,p,d,g={};function x(){if(u)return g;function e(e){return e.replace(/[.*+?^${}()|[\]\\]/g,"\\$&")}return u=1,g.buildMatcher=function(...n){const t=[],r=[];for(const i of n.flat(1))i&&("string"==typeof i?r.push(/^[(^]/.test(i)?i:`^${e(i)}$`):i&&i instanceof RegExp?i.flags?t.push(e=>i.test(e)):r.push(i.source):"function"==typeof i&&t.push(i));const i=r.map(e=>`(?:${e})`).join("|");if(i){const e=new RegExp(i);t.push(n=>e.test(n))}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}},g}function m(){if(f)return o;f=1;const{getFunctionName:e}=c(),{getParentAssignmentName:n}=l(),{buildMatcher:t}=x();const r={isDollarFunction:[/^\$[a-z_]/],isIgnoredFunction:[],isLexicalProvider:["parseEditorState","read","registerCommand","registerNodeTransform","update"],isSafeDollarFunction:[/^\$is[A-Z_]/]};function i(e){if(e)return"Identifier"===e.type?e:"MemberExpression"!==e.type||e.computed?void 0:i(e.property)}function s(e,n){const t=Array.isArray(e.options)?e.options[0]:void 0;return t&&n in t?t[n]:void 0}const a={oneOf:[{type:"string"},{contains:{type:"string"},type:"array"}]};return o.rulesOfLexical={create(o){const a=function(e){return e.sourceCode?e.sourceCode:e.getSourceCode()}(o),l=function(e){const n={};for(const i in r){const o=i;n[o]=t(r[o],s(e,o))}return n}(o),c=new Set,u=new Set,f=[],p=e=>c.add(e),d=e=>c.delete(e),g=t=>{const r=i(function(t){const r=e(t);if(r)return r;const o=t.parent;if(null!=o&&"CallExpression"===o.type&&o.arguments[0]===t&&function(e){return e&&"Identifier"===e.type&&/^use([A-Z]|$)/.test(e.name)}(i(o.callee)))return n(o)}(t));f.push({name:r,node:t}),(l.isDollarFunction(r)||l.isIgnoredFunction(r)||l.isLexicalProvider(r))&&p(t)},x=e=>{f.pop(),d(e)};return{ArrowFunctionExpression:g,"ArrowFunctionExpression:exit":x,CallExpression:e=>{if((()=>{if(c.size>0)return!0;const e=f[f.length-1];return e&&null!=e.node.parent&&"Property"===e.node.parent.type})())return;const n=i(e.callee);if(l.isLexicalProvider(n)||l.isSafeDollarFunction(n))return void p(e);if(!l.isDollarFunction(n))return;const t=(()=>{const e=f[f.length-1];return e?e.name:void 0})();if(!t||u.has(t))return;u.add(t);const r=function(e,n){const t=e.scopeManager;for(let e=n;e;e=e.parent){const r=t.getDeclaredVariables(e).find(e=>e.identifiers.includes(n));if(r)return r;const i=t.acquire(e);if(i)return i.set.get(n.name)||(i.upper?i.upper.set.get(n.name):void 0)}}(a,t),s=function(e,n){const t=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(n)for(let e=n.scope;e;e=e.upper)if(e.set.has(t))return t+"_";return t}(t,r),d=function(e){if(e&&1===e.defs.length){const n=e.defs[0].node.parent;if(null!=n&&"ExportNamedDeclaration"===n.type)return n;if(null!=n&&"VariableDeclaration"===n.type&&null!=n.parent&&"ExportNamedDeclaration"===n.parent.type)return n.parent}}(r),g={callee:a.getText(e.callee),caller:a.getText(t),suggestName:s},x=e=>{const n=new Set,i=[],o=t=>{n.has(t)||(n.add(t),i.push(e.replaceText(t,s)))};if(o(t),d&&i.push(e.insertTextAfter(d,function({caller:e,suggestName:n}){return`\n/** @deprecated renamed to {@link ${n}} by @lexical/eslint-plugin rules-of-lexical */\nexport const ${e} = ${n};`}(g))),r)for(const e of r.references)o(e.identifier);return i};o.report({data:g,fix:x,messageId:"rulesOfLexicalReport",node:t,suggest:[{data:g,fix:x,messageId:"rulesOfLexicalSuggestion"}]})},"CallExpression:exit":d,ClassBody:p,"ClassBody:exit":d,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:a,isIgnoredFunction:a,isLexicalProvider:a,isSafeDollarFunction:a},type:"object"}],type:"suggestion"}},o}function y(){if(d)return p;d=1;const{name:e,version:n}=i,{rulesOfLexical:t}=m(),r={plugins:["@lexical"],rules:{"@lexical/rules-of-lexical":"warn"}},o={configs:{all:r,"flat/all":null,"flat/recommended":null,"legacy-all":r,"legacy-recommended":r,recommended:r},meta:{name:e,version:n},rules:{"rules-of-lexical":t}},s={plugins:{"@lexical":o},rules:{"@lexical/rules-of-lexical":"warn"}};return o.configs["flat/all"]=s,o.configs["flat/recommended"]=s,p=o}var h=y();const v=e({__proto__:null,default:n(h)},[h]);module.exports=v;
|
|
@@ -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,n){for(var t=0;t<n.length;t++){var r=n[t];if("string"!=typeof r&&!Array.isArray(r))for(var i in r)"default"===i||i in e||(e[i]=r[i])}return e}function n(e){return e&&e.__esModule&&Object.prototype.hasOwnProperty.call(e,"default")?e.default:e}var t,r,i={name:"@lexical/eslint-plugin",version:"0.45.0"},o={},s={},a={};function l(){return t||(t=1,a.getParentAssignmentName=function(e){const n=e.parent;if(null!=n)return"VariableDeclarator"===n.type&&n.init===e?n.id:"AssignmentExpression"===n.type&&n.right===e&&"="===n.operator?n.left:void 0}),a}function c(){if(r)return s;r=1;const{getParentAssignmentName:e}=l();return s.getFunctionName=function(n){return"FunctionDeclaration"===n.type||"FunctionExpression"===n.type&&n.id?n.id:"FunctionExpression"===n.type||"ArrowFunctionExpression"===n.type?e(n):void 0},s}var u,f,p,d,g={};function x(){if(u)return g;function e(e){return e.replace(/[.*+?^${}()|[\]\\]/g,"\\$&")}return u=1,g.buildMatcher=function(...n){const t=[],r=[];for(const i of n.flat(1))i&&("string"==typeof i?r.push(/^[(^]/.test(i)?i:`^${e(i)}$`):i&&i instanceof RegExp?i.flags?t.push(e=>i.test(e)):r.push(i.source):"function"==typeof i&&t.push(i));const i=r.map(e=>`(?:${e})`).join("|");if(i){const e=new RegExp(i);t.push(n=>e.test(n))}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}},g}function m(){if(f)return o;f=1;const{getFunctionName:e}=c(),{getParentAssignmentName:n}=l(),{buildMatcher:t}=x();const r={isDollarFunction:[/^\$[a-z_]/],isIgnoredFunction:[],isLexicalProvider:["parseEditorState","read","registerCommand","registerNodeTransform","update"],isSafeDollarFunction:[/^\$is[A-Z_]/]};function i(e){if(e)return"Identifier"===e.type?e:"MemberExpression"!==e.type||e.computed?void 0:i(e.property)}function s(e,n){const t=Array.isArray(e.options)?e.options[0]:void 0;return t&&n in t?t[n]:void 0}const a={oneOf:[{type:"string"},{contains:{type:"string"},type:"array"}]};return o.rulesOfLexical={create(o){const a=function(e){return e.sourceCode?e.sourceCode:e.getSourceCode()}(o),l=function(e){const n={};for(const i in r){const o=i;n[o]=t(r[o],s(e,o))}return n}(o),c=new Set,u=new Set,f=[],p=e=>c.add(e),d=e=>c.delete(e),g=t=>{const r=i(function(t){const r=e(t);if(r)return r;const o=t.parent;if(null!=o&&"CallExpression"===o.type&&o.arguments[0]===t&&function(e){return e&&"Identifier"===e.type&&/^use([A-Z]|$)/.test(e.name)}(i(o.callee)))return n(o)}(t));f.push({name:r,node:t}),(l.isDollarFunction(r)||l.isIgnoredFunction(r)||l.isLexicalProvider(r))&&p(t)},x=e=>{f.pop(),d(e)};return{ArrowFunctionExpression:g,"ArrowFunctionExpression:exit":x,CallExpression:e=>{if((()=>{if(c.size>0)return!0;const e=f[f.length-1];return e&&null!=e.node.parent&&"Property"===e.node.parent.type})())return;const n=i(e.callee);if(l.isLexicalProvider(n)||l.isSafeDollarFunction(n))return void p(e);if(!l.isDollarFunction(n))return;const t=(()=>{const e=f[f.length-1];return e?e.name:void 0})();if(!t||u.has(t))return;u.add(t);const r=function(e,n){const t=e.scopeManager;for(let e=n;e;e=e.parent){const r=t.getDeclaredVariables(e).find(e=>e.identifiers.includes(n));if(r)return r;const i=t.acquire(e);if(i)return i.set.get(n.name)||(i.upper?i.upper.set.get(n.name):void 0)}}(a,t),s=function(e,n){const t=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(n)for(let e=n.scope;e;e=e.upper)if(e.set.has(t))return t+"_";return t}(t,r),d=function(e){if(e&&1===e.defs.length){const n=e.defs[0].node.parent;if(null!=n&&"ExportNamedDeclaration"===n.type)return n;if(null!=n&&"VariableDeclaration"===n.type&&null!=n.parent&&"ExportNamedDeclaration"===n.parent.type)return n.parent}}(r),g={callee:a.getText(e.callee),caller:a.getText(t),suggestName:s},x=e=>{const n=new Set,i=[],o=t=>{n.has(t)||(n.add(t),i.push(e.replaceText(t,s)))};if(o(t),d&&i.push(e.insertTextAfter(d,function({caller:e,suggestName:n}){return`\n/** @deprecated renamed to {@link ${n}} by @lexical/eslint-plugin rules-of-lexical */\nexport const ${e} = ${n};`}(g))),r)for(const e of r.references)o(e.identifier);return i};o.report({data:g,fix:x,messageId:"rulesOfLexicalReport",node:t,suggest:[{data:g,fix:x,messageId:"rulesOfLexicalSuggestion"}]})},"CallExpression:exit":d,ClassBody:p,"ClassBody:exit":d,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:a,isIgnoredFunction:a,isLexicalProvider:a,isSafeDollarFunction:a},type:"object"}],type:"suggestion"}},o}function y(){if(d)return p;d=1;const{name:e,version:n}=i,{rulesOfLexical:t}=m(),r={plugins:["@lexical"],rules:{"@lexical/rules-of-lexical":"warn"}},o={configs:{all:r,"flat/all":null,"flat/recommended":null,"legacy-all":r,"legacy-recommended":r,recommended:r},meta:{name:e,version:n},rules:{"rules-of-lexical":t}},s={plugins:{"@lexical":o},rules:{"@lexical/rules-of-lexical":"warn"}};return o.configs["flat/all"]=s,o.configs["flat/recommended"]=s,p=o}var h=y();const v=e({__proto__:null,default:n(h)},[h]);export{v as default};
|
package/package.json
CHANGED
|
@@ -8,15 +8,15 @@
|
|
|
8
8
|
"lexical",
|
|
9
9
|
"editor"
|
|
10
10
|
],
|
|
11
|
-
"version": "0.
|
|
11
|
+
"version": "0.45.0",
|
|
12
12
|
"license": "MIT",
|
|
13
13
|
"repository": {
|
|
14
14
|
"type": "git",
|
|
15
15
|
"url": "git+https://github.com/facebook/lexical.git",
|
|
16
16
|
"directory": "packages/lexical-eslint-plugin"
|
|
17
17
|
},
|
|
18
|
-
"main": "LexicalEslintPlugin.js",
|
|
19
|
-
"types": "index.d.ts",
|
|
18
|
+
"main": "./dist/LexicalEslintPlugin.js",
|
|
19
|
+
"types": "./dist/index.d.ts",
|
|
20
20
|
"bugs": {
|
|
21
21
|
"url": "https://github.com/facebook/lexical/issues"
|
|
22
22
|
},
|
|
@@ -27,18 +27,19 @@
|
|
|
27
27
|
},
|
|
28
28
|
"exports": {
|
|
29
29
|
".": {
|
|
30
|
+
"source": "./src/index.ts",
|
|
30
31
|
"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"
|
|
32
|
+
"types": "./dist/index.d.ts",
|
|
33
|
+
"development": "./dist/LexicalEslintPlugin.dev.mjs",
|
|
34
|
+
"production": "./dist/LexicalEslintPlugin.prod.mjs",
|
|
35
|
+
"node": "./dist/LexicalEslintPlugin.node.mjs",
|
|
36
|
+
"default": "./dist/LexicalEslintPlugin.mjs"
|
|
36
37
|
},
|
|
37
38
|
"require": {
|
|
38
|
-
"types": "./index.d.ts",
|
|
39
|
-
"development": "./LexicalEslintPlugin.dev.js",
|
|
40
|
-
"production": "./LexicalEslintPlugin.prod.js",
|
|
41
|
-
"default": "./LexicalEslintPlugin.js"
|
|
39
|
+
"types": "./dist/index.d.ts",
|
|
40
|
+
"development": "./dist/LexicalEslintPlugin.dev.js",
|
|
41
|
+
"production": "./dist/LexicalEslintPlugin.prod.js",
|
|
42
|
+
"default": "./dist/LexicalEslintPlugin.js"
|
|
42
43
|
}
|
|
43
44
|
}
|
|
44
45
|
},
|
|
@@ -46,5 +47,18 @@
|
|
|
46
47
|
"@types/eslint": "^9.6.1",
|
|
47
48
|
"@types/estree": "^1.0.6"
|
|
48
49
|
},
|
|
49
|
-
"module": "LexicalEslintPlugin.mjs"
|
|
50
|
+
"module": "./dist/LexicalEslintPlugin.mjs",
|
|
51
|
+
"files": [
|
|
52
|
+
"dist",
|
|
53
|
+
"src",
|
|
54
|
+
"!src/__tests__",
|
|
55
|
+
"!src/__bench__",
|
|
56
|
+
"!src/__mocks__",
|
|
57
|
+
"!src/**/*.test.ts",
|
|
58
|
+
"!src/**/*.test.tsx",
|
|
59
|
+
"!src/**/*.bench.ts",
|
|
60
|
+
"!src/**/*.bench.tsx",
|
|
61
|
+
"README.md",
|
|
62
|
+
"LICENSE"
|
|
63
|
+
]
|
|
50
64
|
}
|
|
@@ -0,0 +1,53 @@
|
|
|
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
|
+
// @ts-check
|
|
10
|
+
|
|
11
|
+
const {name, version} = require('../package.json');
|
|
12
|
+
const {rulesOfLexical} = require('./rules/rules-of-lexical.js');
|
|
13
|
+
|
|
14
|
+
// Legacy config format (ESLint 7-8)
|
|
15
|
+
const legacyAll = {
|
|
16
|
+
plugins: ['@lexical'],
|
|
17
|
+
rules: {
|
|
18
|
+
'@lexical/rules-of-lexical': /** @type {'warn'|'error'|'off'}*/ ('warn'),
|
|
19
|
+
},
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
const plugin = {
|
|
23
|
+
configs: {
|
|
24
|
+
// Legacy configs (ESLint 7-8) - available under multiple names for compatibility
|
|
25
|
+
all: legacyAll,
|
|
26
|
+
// Flat configs (ESLint 9-10+) - placeholders, will be set below
|
|
27
|
+
'flat/all': /** @type {any} */ (null),
|
|
28
|
+
'flat/recommended': /** @type {any} */ (null),
|
|
29
|
+
'legacy-all': legacyAll,
|
|
30
|
+
'legacy-recommended': legacyAll,
|
|
31
|
+
recommended: legacyAll,
|
|
32
|
+
},
|
|
33
|
+
meta: {name, version},
|
|
34
|
+
rules: {
|
|
35
|
+
'rules-of-lexical': rulesOfLexical,
|
|
36
|
+
},
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
// Flat config format (ESLint 9-10+)
|
|
40
|
+
// Must be created after plugin is defined to avoid circular reference
|
|
41
|
+
const flatAll = {
|
|
42
|
+
plugins: {
|
|
43
|
+
'@lexical': plugin,
|
|
44
|
+
},
|
|
45
|
+
rules: {
|
|
46
|
+
'@lexical/rules-of-lexical': 'warn' /** @type {'warn'|'error'|'off'}*/,
|
|
47
|
+
},
|
|
48
|
+
};
|
|
49
|
+
|
|
50
|
+
plugin.configs['flat/all'] = flatAll;
|
|
51
|
+
plugin.configs['flat/recommended'] = flatAll;
|
|
52
|
+
|
|
53
|
+
module.exports = plugin;
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,61 @@
|
|
|
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
|
+
/**
|
|
10
|
+
* For bootstrapping reasons, this module is written in CJS JavaScript so no
|
|
11
|
+
* compilation is necessary
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
import type {Rule} from 'eslint';
|
|
15
|
+
|
|
16
|
+
import * as jsPlugin from './LexicalEslintPlugin.js';
|
|
17
|
+
|
|
18
|
+
export type {RulesOfLexicalOptions} from './rules/rules-of-lexical.js';
|
|
19
|
+
|
|
20
|
+
// Legacy config format (ESLint 7-8)
|
|
21
|
+
export interface LegacyConfig {
|
|
22
|
+
plugins: string[];
|
|
23
|
+
rules: {
|
|
24
|
+
'@lexical/rules-of-lexical': 'warn' | 'error' | 'off';
|
|
25
|
+
};
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
// Flat config format (ESLint 9-10+)
|
|
29
|
+
export interface FlatConfig {
|
|
30
|
+
plugins: {
|
|
31
|
+
'@lexical': Plugin;
|
|
32
|
+
};
|
|
33
|
+
rules: {
|
|
34
|
+
'@lexical/rules-of-lexical': 'warn' | 'error' | 'off';
|
|
35
|
+
};
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export interface Plugin {
|
|
39
|
+
meta: {
|
|
40
|
+
name: string;
|
|
41
|
+
version: string;
|
|
42
|
+
};
|
|
43
|
+
rules: {
|
|
44
|
+
'rules-of-lexical': Rule.RuleModule;
|
|
45
|
+
};
|
|
46
|
+
configs: {
|
|
47
|
+
// Legacy configs (ESLint 7-8) - available under multiple names
|
|
48
|
+
all: LegacyConfig;
|
|
49
|
+
'legacy-all': LegacyConfig;
|
|
50
|
+
'legacy-recommended': LegacyConfig;
|
|
51
|
+
recommended: LegacyConfig;
|
|
52
|
+
// Flat configs (ESLint 9-10+)
|
|
53
|
+
'flat/all': FlatConfig;
|
|
54
|
+
'flat/recommended': FlatConfig;
|
|
55
|
+
};
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
const plugin: Plugin = jsPlugin;
|
|
59
|
+
|
|
60
|
+
// eslint-disable-next-line no-restricted-exports
|
|
61
|
+
export default plugin;
|
|
@@ -0,0 +1,431 @@
|
|
|
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
|
+
// @ts-check
|
|
9
|
+
|
|
10
|
+
const {getFunctionName} = require('../util/getFunctionName.js');
|
|
11
|
+
const {getParentAssignmentName} = require('../util/getParentAssignmentName.js');
|
|
12
|
+
const {buildMatcher} = require('../util/buildMatcher.js');
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* @typedef {import('eslint').Rule.NodeParentExtension} NodeParentExtension
|
|
16
|
+
* @typedef {import('estree').CallExpression & NodeParentExtension} CallExpression
|
|
17
|
+
* @typedef {import('estree').Identifier & NodeParentExtension} Identifier
|
|
18
|
+
* @typedef {import('eslint').Rule.RuleContext} RuleContext
|
|
19
|
+
* @typedef {import('eslint').Rule.Fix} Fix
|
|
20
|
+
* @typedef {import('eslint').Rule.Node} Node
|
|
21
|
+
* @typedef {import('eslint').Rule.RuleModule} RuleModule
|
|
22
|
+
* @typedef {import('eslint').Rule.ReportFixer} ReportFixer
|
|
23
|
+
* @typedef {import('eslint').SourceCode} SourceCode
|
|
24
|
+
* @typedef {import('eslint').Scope.Variable} Variable
|
|
25
|
+
* @typedef {import('eslint').Scope.Scope} Scope
|
|
26
|
+
*/
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Find the variable associated with the given Identifier
|
|
30
|
+
*
|
|
31
|
+
* @param {SourceCode} sourceCode
|
|
32
|
+
* @param {Identifier} identifier
|
|
33
|
+
*/
|
|
34
|
+
function getIdentifierVariable(sourceCode, identifier) {
|
|
35
|
+
const scopeManager = sourceCode.scopeManager;
|
|
36
|
+
for (
|
|
37
|
+
let node = /** @type {Node | null} */ (identifier);
|
|
38
|
+
node;
|
|
39
|
+
node = /** @type {Node | null}*/ (node.parent)
|
|
40
|
+
) {
|
|
41
|
+
const variable = scopeManager
|
|
42
|
+
.getDeclaredVariables(node)
|
|
43
|
+
.find(v => v.identifiers.includes(identifier));
|
|
44
|
+
if (variable) {
|
|
45
|
+
return variable;
|
|
46
|
+
}
|
|
47
|
+
const scope = scopeManager.acquire(node);
|
|
48
|
+
if (scope) {
|
|
49
|
+
return (
|
|
50
|
+
scope.set.get(identifier.name) ||
|
|
51
|
+
(scope.upper ? scope.upper.set.get(identifier.name) : undefined)
|
|
52
|
+
);
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
return undefined;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* @typedef {import('../util/buildMatcher.js').ToMatcher} ToMatcher
|
|
60
|
+
* @typedef {import('../util/buildMatcher.js').IdentifierMatcher} IdentifierMatcher
|
|
61
|
+
*/
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* @template T
|
|
65
|
+
* @typedef {Object} BaseMatchers<T>
|
|
66
|
+
* @property {T} isDollarFunction Catch all identifiers that begin with '$' or 'INTERNAL_$' followed by a lowercase Latin character or underscore
|
|
67
|
+
* @property {T} isIgnoredFunction These functions may call any $functions even though they do not have the isDollarFunction naming convention
|
|
68
|
+
* @property {T} isLexicalProvider Certain calls through the editor or editorState allow for implicit access to call $functions: read, registerCommand, registerNodeTransform, update.
|
|
69
|
+
* @property {T} isSafeDollarFunction It's usually safe to call $isNode functions, so any '$is' or 'INTERNAL_$is' function may be called in any context.
|
|
70
|
+
*/
|
|
71
|
+
|
|
72
|
+
/** @type {BaseMatchers<Exclude<ToMatcher, undefined>[]>} */
|
|
73
|
+
const BaseMatchers = {
|
|
74
|
+
isDollarFunction: [/^\$[a-z_]/],
|
|
75
|
+
isIgnoredFunction: [],
|
|
76
|
+
isLexicalProvider: [
|
|
77
|
+
'parseEditorState',
|
|
78
|
+
'read',
|
|
79
|
+
'registerCommand',
|
|
80
|
+
'registerNodeTransform',
|
|
81
|
+
'update',
|
|
82
|
+
],
|
|
83
|
+
isSafeDollarFunction: [/^\$is[A-Z_]/],
|
|
84
|
+
};
|
|
85
|
+
|
|
86
|
+
/**
|
|
87
|
+
* @typedef {Partial<BaseMatchers<ToMatcher | ToMatcher[]>>} RulesOfLexicalOptions
|
|
88
|
+
* @typedef {BaseMatchers<IdentifierMatcher>} Matchers
|
|
89
|
+
*/
|
|
90
|
+
|
|
91
|
+
/**
|
|
92
|
+
* @param {RuleContext} context
|
|
93
|
+
* @returns {Matchers}
|
|
94
|
+
*/
|
|
95
|
+
function compileMatchers(context) {
|
|
96
|
+
const rval = /** @type {Matchers} */ ({});
|
|
97
|
+
for (const k_ in BaseMatchers) {
|
|
98
|
+
const k = /** @type {keyof Matchers} */ (k_);
|
|
99
|
+
rval[k] = buildMatcher(BaseMatchers[k], parseMatcherOption(context, k));
|
|
100
|
+
}
|
|
101
|
+
return rval;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
/**
|
|
105
|
+
* Hook functions start with use followed by a capital latin letter.
|
|
106
|
+
*
|
|
107
|
+
* @param {Node | undefined} node
|
|
108
|
+
*/
|
|
109
|
+
function isHookFunctionIdentifier(node) {
|
|
110
|
+
return node && node.type === 'Identifier' && /^use([A-Z]|$)/.test(node.name);
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
/**
|
|
114
|
+
* Return this node if is an Identifier, otherwise if it is a MemberExpression such as
|
|
115
|
+
* `editor.read` return the Identifier of its property ('read' in this case).
|
|
116
|
+
*
|
|
117
|
+
* @param {Node | undefined} node
|
|
118
|
+
* @returns {Identifier | undefined}
|
|
119
|
+
*/
|
|
120
|
+
function getFunctionNameIdentifier(node) {
|
|
121
|
+
if (!node) {
|
|
122
|
+
return;
|
|
123
|
+
} else if (node.type === 'Identifier') {
|
|
124
|
+
return node;
|
|
125
|
+
} else if (node.type === 'MemberExpression' && !node.computed) {
|
|
126
|
+
return getFunctionNameIdentifier(/** @type {Node} */ (node.property));
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
/**
|
|
131
|
+
* Get the function's name, or if it is defined with a hook
|
|
132
|
+
* (e.g. useMemo, useCallback), then get the name of the variable the result
|
|
133
|
+
* is assigned to.
|
|
134
|
+
*
|
|
135
|
+
* @param {Node} node
|
|
136
|
+
*/
|
|
137
|
+
function getLexicalFunctionName(node) {
|
|
138
|
+
const name = getFunctionName(node);
|
|
139
|
+
if (name) {
|
|
140
|
+
return name;
|
|
141
|
+
}
|
|
142
|
+
const nodeParent = node.parent;
|
|
143
|
+
if (
|
|
144
|
+
nodeParent != null &&
|
|
145
|
+
nodeParent.type === 'CallExpression' &&
|
|
146
|
+
nodeParent.arguments[0] === node
|
|
147
|
+
) {
|
|
148
|
+
const parentName = getFunctionNameIdentifier(
|
|
149
|
+
/** @type {Node} */ (nodeParent.callee),
|
|
150
|
+
);
|
|
151
|
+
if (isHookFunctionIdentifier(parentName)) {
|
|
152
|
+
return getParentAssignmentName(/** @type {Node} */ (nodeParent));
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
/**
|
|
158
|
+
* Return a name suitable for a suggestion.
|
|
159
|
+
* isDollarFunction(getFirstSuggestion(name)) should
|
|
160
|
+
* be true for any given name.
|
|
161
|
+
*
|
|
162
|
+
* @param {string} name
|
|
163
|
+
* @returns {string}
|
|
164
|
+
*/
|
|
165
|
+
function getFirstSuggestion(name) {
|
|
166
|
+
if (/^[a-z]/.test(name)) {
|
|
167
|
+
return '$' + name;
|
|
168
|
+
} else if (/^[A-Z][a-z]/.test(name)) {
|
|
169
|
+
return '$' + name.slice(0, 1).toLowerCase() + name.slice(1);
|
|
170
|
+
} else {
|
|
171
|
+
return `$_${name}`;
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
/**
|
|
176
|
+
* Get the suggested name for a variable and add an underscore if it shadows
|
|
177
|
+
* or conflicts with an existing variable.
|
|
178
|
+
*
|
|
179
|
+
* @param {Identifier} nameIdentifier
|
|
180
|
+
* @param {Variable | undefined} variable
|
|
181
|
+
*/
|
|
182
|
+
function getSuggestName(nameIdentifier, variable) {
|
|
183
|
+
const suggestName = getFirstSuggestion(nameIdentifier.name);
|
|
184
|
+
// Add an underscore if this would shadow an existing name
|
|
185
|
+
if (variable) {
|
|
186
|
+
for (
|
|
187
|
+
let scope = /** @type {Scope | null} */ (variable.scope);
|
|
188
|
+
scope;
|
|
189
|
+
scope = scope.upper
|
|
190
|
+
) {
|
|
191
|
+
if (scope.set.has(suggestName)) {
|
|
192
|
+
return suggestName + '_';
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
return suggestName;
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
/**
|
|
200
|
+
* Get the export declaration for a variable, if it has one.
|
|
201
|
+
*
|
|
202
|
+
* @param {Variable | undefined} variable
|
|
203
|
+
*/
|
|
204
|
+
function getExportDeclaration(variable) {
|
|
205
|
+
if (variable && variable.defs.length === 1) {
|
|
206
|
+
const defNode = /** @type {Node} */ (variable.defs[0].node);
|
|
207
|
+
const defParent = defNode.parent;
|
|
208
|
+
if (defParent != null && defParent.type === 'ExportNamedDeclaration') {
|
|
209
|
+
// export function foo();
|
|
210
|
+
return defParent;
|
|
211
|
+
} else if (
|
|
212
|
+
defParent != null &&
|
|
213
|
+
defParent.type === 'VariableDeclaration' &&
|
|
214
|
+
defParent.parent != null &&
|
|
215
|
+
defParent.parent.type === 'ExportNamedDeclaration'
|
|
216
|
+
) {
|
|
217
|
+
// export const foo = () => {};
|
|
218
|
+
return defParent.parent;
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
/**
|
|
224
|
+
* The comment we insert when an export is renamed.
|
|
225
|
+
*
|
|
226
|
+
* @param {Record<'caller'|'suggestName', string>} data
|
|
227
|
+
*/
|
|
228
|
+
function renameExportText({caller, suggestName}) {
|
|
229
|
+
return `\n/** @deprecated renamed to {@link ${suggestName}} by @lexical/eslint-plugin rules-of-lexical */\nexport const ${caller} = ${suggestName};`;
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
/**
|
|
233
|
+
* @param {RuleContext} context
|
|
234
|
+
* @param {string} optionName
|
|
235
|
+
* @returns {ToMatcher}
|
|
236
|
+
*/
|
|
237
|
+
function parseMatcherOption(context, optionName) {
|
|
238
|
+
const options = Array.isArray(context.options)
|
|
239
|
+
? context.options[0]
|
|
240
|
+
: undefined;
|
|
241
|
+
return options && optionName in options ? options[optionName] : undefined;
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
/** @param {RuleContext} context */
|
|
245
|
+
function getSourceCode(context) {
|
|
246
|
+
if (context.sourceCode) {
|
|
247
|
+
return context.sourceCode;
|
|
248
|
+
}
|
|
249
|
+
// @ts-expect-error -- getSourceCode() removed from types in ESLint 10, kept for ESLint 8 compat
|
|
250
|
+
return context.getSourceCode();
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
const matcherSchema = {
|
|
254
|
+
oneOf: [{type: 'string'}, {contains: {type: 'string'}, type: 'array'}],
|
|
255
|
+
};
|
|
256
|
+
|
|
257
|
+
/** @type {RuleModule} */
|
|
258
|
+
module.exports.rulesOfLexical = {
|
|
259
|
+
create(context) {
|
|
260
|
+
const sourceCode = getSourceCode(context);
|
|
261
|
+
const matchers = compileMatchers(context);
|
|
262
|
+
|
|
263
|
+
/**
|
|
264
|
+
* When this set is non-empty it means that we are visiting a node
|
|
265
|
+
* that should not be analyzed.
|
|
266
|
+
*
|
|
267
|
+
* @type {Set<Node>}
|
|
268
|
+
*/
|
|
269
|
+
const ignoreSet = new Set();
|
|
270
|
+
/**
|
|
271
|
+
* The set of Identifier nodes that have been reported, we do not
|
|
272
|
+
* want to report the same node more than once (a function making several
|
|
273
|
+
* calls to $functions only needs to be renamed once!)
|
|
274
|
+
*
|
|
275
|
+
* @type {Set<Identifier>}
|
|
276
|
+
*/
|
|
277
|
+
const reportedSet = new Set();
|
|
278
|
+
/**
|
|
279
|
+
* The current stack of functions
|
|
280
|
+
*
|
|
281
|
+
* @type {{ name?: Identifier, node: Node }[]} funStack
|
|
282
|
+
*/
|
|
283
|
+
const funStack = [];
|
|
284
|
+
const shouldIgnore = () => {
|
|
285
|
+
if (ignoreSet.size > 0) {
|
|
286
|
+
return true;
|
|
287
|
+
}
|
|
288
|
+
// Ignore property assignments
|
|
289
|
+
const lastFunction = funStack[funStack.length - 1];
|
|
290
|
+
return (
|
|
291
|
+
lastFunction &&
|
|
292
|
+
lastFunction.node.parent != null &&
|
|
293
|
+
lastFunction.node.parent.type === 'Property'
|
|
294
|
+
);
|
|
295
|
+
};
|
|
296
|
+
const pushIgnoredNode = (/** @type {Node} */ node) => ignoreSet.add(node);
|
|
297
|
+
const popIgnoredNode = (/** @type {Node} */ node) => ignoreSet.delete(node);
|
|
298
|
+
const pushFunction = (/** @type {Node} */ node) => {
|
|
299
|
+
const name = getFunctionNameIdentifier(
|
|
300
|
+
/** @type {Node | undefined} */ (getLexicalFunctionName(node)),
|
|
301
|
+
);
|
|
302
|
+
funStack.push({name, node});
|
|
303
|
+
if (
|
|
304
|
+
matchers.isDollarFunction(name) ||
|
|
305
|
+
matchers.isIgnoredFunction(name) ||
|
|
306
|
+
matchers.isLexicalProvider(name)
|
|
307
|
+
) {
|
|
308
|
+
pushIgnoredNode(node);
|
|
309
|
+
}
|
|
310
|
+
};
|
|
311
|
+
const popFunction = (/** @type {Node} */ node) => {
|
|
312
|
+
funStack.pop();
|
|
313
|
+
popIgnoredNode(node);
|
|
314
|
+
};
|
|
315
|
+
const getParentLexicalFunctionNameIdentifier = (
|
|
316
|
+
/** @type {Node} */ _node,
|
|
317
|
+
) => {
|
|
318
|
+
const pair = funStack[funStack.length - 1];
|
|
319
|
+
return pair ? pair.name : undefined;
|
|
320
|
+
};
|
|
321
|
+
// Find all $function calls that are not inside a class or inside a $function
|
|
322
|
+
// by visiting all function definitions and calls
|
|
323
|
+
return {
|
|
324
|
+
ArrowFunctionExpression: pushFunction,
|
|
325
|
+
'ArrowFunctionExpression:exit': popFunction,
|
|
326
|
+
CallExpression: node => {
|
|
327
|
+
if (shouldIgnore()) {
|
|
328
|
+
return;
|
|
329
|
+
}
|
|
330
|
+
const calleeName = getFunctionNameIdentifier(
|
|
331
|
+
/** @type {Node} */ (node.callee),
|
|
332
|
+
);
|
|
333
|
+
if (
|
|
334
|
+
matchers.isLexicalProvider(calleeName) ||
|
|
335
|
+
matchers.isSafeDollarFunction(calleeName)
|
|
336
|
+
) {
|
|
337
|
+
pushIgnoredNode(node);
|
|
338
|
+
return;
|
|
339
|
+
}
|
|
340
|
+
if (!matchers.isDollarFunction(calleeName)) {
|
|
341
|
+
return;
|
|
342
|
+
}
|
|
343
|
+
const nameIdentifier = getParentLexicalFunctionNameIdentifier(node);
|
|
344
|
+
if (!nameIdentifier || reportedSet.has(nameIdentifier)) {
|
|
345
|
+
return;
|
|
346
|
+
}
|
|
347
|
+
reportedSet.add(nameIdentifier);
|
|
348
|
+
const variable = getIdentifierVariable(sourceCode, nameIdentifier);
|
|
349
|
+
const suggestName = getSuggestName(nameIdentifier, variable);
|
|
350
|
+
const exportDeclaration = getExportDeclaration(variable);
|
|
351
|
+
const data = {
|
|
352
|
+
callee: sourceCode.getText(node.callee),
|
|
353
|
+
caller: sourceCode.getText(nameIdentifier),
|
|
354
|
+
suggestName,
|
|
355
|
+
};
|
|
356
|
+
/** @type {ReportFixer} */
|
|
357
|
+
const fix = fixer => {
|
|
358
|
+
/** @type {Set<Identifier>} */
|
|
359
|
+
const replaced = new Set();
|
|
360
|
+
/** @type {Fix[]} */
|
|
361
|
+
const fixes = [];
|
|
362
|
+
const renameIdentifier = (/** @type {Identifier} */ identifier) => {
|
|
363
|
+
if (!replaced.has(identifier)) {
|
|
364
|
+
replaced.add(identifier);
|
|
365
|
+
fixes.push(fixer.replaceText(identifier, suggestName));
|
|
366
|
+
}
|
|
367
|
+
};
|
|
368
|
+
renameIdentifier(nameIdentifier);
|
|
369
|
+
if (exportDeclaration) {
|
|
370
|
+
fixes.push(
|
|
371
|
+
fixer.insertTextAfter(exportDeclaration, renameExportText(data)),
|
|
372
|
+
);
|
|
373
|
+
}
|
|
374
|
+
if (variable) {
|
|
375
|
+
for (const ref of variable.references) {
|
|
376
|
+
renameIdentifier(/** @type {Identifier} */ (ref.identifier));
|
|
377
|
+
}
|
|
378
|
+
}
|
|
379
|
+
return fixes;
|
|
380
|
+
};
|
|
381
|
+
context.report({
|
|
382
|
+
data,
|
|
383
|
+
fix,
|
|
384
|
+
messageId: 'rulesOfLexicalReport',
|
|
385
|
+
node: nameIdentifier,
|
|
386
|
+
suggest: [
|
|
387
|
+
{
|
|
388
|
+
data,
|
|
389
|
+
fix,
|
|
390
|
+
messageId: 'rulesOfLexicalSuggestion',
|
|
391
|
+
},
|
|
392
|
+
],
|
|
393
|
+
});
|
|
394
|
+
},
|
|
395
|
+
'CallExpression:exit': popIgnoredNode,
|
|
396
|
+
ClassBody: pushIgnoredNode,
|
|
397
|
+
'ClassBody:exit': popIgnoredNode,
|
|
398
|
+
FunctionDeclaration: pushFunction,
|
|
399
|
+
'FunctionDeclaration:exit': popFunction,
|
|
400
|
+
FunctionExpression: pushFunction,
|
|
401
|
+
'FunctionExpression:exit': popFunction,
|
|
402
|
+
};
|
|
403
|
+
},
|
|
404
|
+
meta: {
|
|
405
|
+
docs: {
|
|
406
|
+
description: 'enforces the Rules of Lexical',
|
|
407
|
+
recommended: true,
|
|
408
|
+
url: 'https://lexical.dev/docs/packages/lexical-eslint-plugin',
|
|
409
|
+
},
|
|
410
|
+
fixable: 'code',
|
|
411
|
+
hasSuggestions: true,
|
|
412
|
+
messages: {
|
|
413
|
+
rulesOfLexicalReport:
|
|
414
|
+
'{{ callee }} called from {{ caller }}, without $ prefix or read/update context',
|
|
415
|
+
rulesOfLexicalSuggestion: 'Rename {{ caller }} to {{ suggestName }}',
|
|
416
|
+
},
|
|
417
|
+
schema: [
|
|
418
|
+
{
|
|
419
|
+
additionalProperties: false,
|
|
420
|
+
properties: {
|
|
421
|
+
isDollarFunction: matcherSchema,
|
|
422
|
+
isIgnoredFunction: matcherSchema,
|
|
423
|
+
isLexicalProvider: matcherSchema,
|
|
424
|
+
isSafeDollarFunction: matcherSchema,
|
|
425
|
+
},
|
|
426
|
+
type: 'object',
|
|
427
|
+
},
|
|
428
|
+
],
|
|
429
|
+
type: 'suggestion',
|
|
430
|
+
},
|
|
431
|
+
};
|
|
@@ -0,0 +1,77 @@
|
|
|
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
|
+
/**
|
|
10
|
+
* @typedef {import('estree').Node} Node
|
|
11
|
+
* @typedef {import('estree').Identifier} Identifier
|
|
12
|
+
* @typedef {(name: string, node: Identifier) => boolean} NameIdentifierMatcher
|
|
13
|
+
* @typedef {NameIdentifierMatcher | string | RegExp | undefined} ToMatcher
|
|
14
|
+
* @typedef {(node: Identifier | undefined) => boolean} IdentifierMatcher
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Escape a string for exact match in a RegExp
|
|
19
|
+
* https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Regular_expressions#escaping
|
|
20
|
+
*
|
|
21
|
+
* @param {string} string
|
|
22
|
+
* @returns {string}
|
|
23
|
+
*/
|
|
24
|
+
function escapeRegExp(string) {
|
|
25
|
+
return string.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'); // $& means the whole matched string
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Build an Identifier Node Matcher from the given ToMatcher arguments.
|
|
30
|
+
* The Matcher is roughly equivalent to building RegExp from all of the
|
|
31
|
+
* sources and or-ing them together. String arguments are treated as
|
|
32
|
+
* RegExp sources and will be escaped with an implicit '^...$' wrapper
|
|
33
|
+
* unless it starts with a '(' or '^'
|
|
34
|
+
*
|
|
35
|
+
* @param {(ToMatcher | ToMatcher[])[]} args
|
|
36
|
+
* @returns {IdentifierMatcher}
|
|
37
|
+
*/
|
|
38
|
+
module.exports.buildMatcher = function buildMatcher(...toMatchers) {
|
|
39
|
+
/** @type {Matcher[]} */
|
|
40
|
+
const matchFuns = [];
|
|
41
|
+
/** @type {string[]} */
|
|
42
|
+
const regExpSources = [];
|
|
43
|
+
for (const arg of toMatchers.flat(1)) {
|
|
44
|
+
if (!arg) {
|
|
45
|
+
continue;
|
|
46
|
+
} else if (typeof arg === 'string') {
|
|
47
|
+
regExpSources.push(/^[(^]/.test(arg) ? arg : `^${escapeRegExp(arg)}$`);
|
|
48
|
+
} else if (arg && arg instanceof RegExp) {
|
|
49
|
+
if (arg.flags) {
|
|
50
|
+
matchFuns.push(s => arg.test(s));
|
|
51
|
+
} else {
|
|
52
|
+
regExpSources.push(arg.source);
|
|
53
|
+
}
|
|
54
|
+
} else if (typeof arg === 'function') {
|
|
55
|
+
matchFuns.push(arg);
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
const pattern = regExpSources.map(s => `(?:${s})`).join('|');
|
|
59
|
+
if (pattern) {
|
|
60
|
+
const re = new RegExp(pattern);
|
|
61
|
+
matchFuns.push(s => re.test(s));
|
|
62
|
+
}
|
|
63
|
+
return node => {
|
|
64
|
+
if (node) {
|
|
65
|
+
if (node.type !== 'Identifier') {
|
|
66
|
+
// Runtime type invariant check
|
|
67
|
+
throw new Error(`Expecting Identifier, not ${node.type}`);
|
|
68
|
+
}
|
|
69
|
+
for (const matcher of matchFuns) {
|
|
70
|
+
if (matcher(node.name, node)) {
|
|
71
|
+
return true;
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
return false;
|
|
76
|
+
};
|
|
77
|
+
};
|
|
@@ -0,0 +1,43 @@
|
|
|
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
|
+
// @ts-check
|
|
9
|
+
|
|
10
|
+
const {getParentAssignmentName} = require('./getParentAssignmentName');
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Gets the static name of a function AST node. For function declarations it is
|
|
14
|
+
* easy. For anonymous function expressions it is much harder. If you search for
|
|
15
|
+
* `IsAnonymousFunctionDefinition()` in the ECMAScript spec you'll find places
|
|
16
|
+
* where JS gives anonymous function expressions names. We roughly detect the
|
|
17
|
+
* same AST nodes with some exceptions to better fit our use case.
|
|
18
|
+
*
|
|
19
|
+
* @param {import('eslint').Rule.Node} node
|
|
20
|
+
*/
|
|
21
|
+
module.exports.getFunctionName = function getFunctionName(node) {
|
|
22
|
+
if (
|
|
23
|
+
node.type === 'FunctionDeclaration' ||
|
|
24
|
+
(node.type === 'FunctionExpression' && node.id)
|
|
25
|
+
) {
|
|
26
|
+
// function $function() {}
|
|
27
|
+
// const whatever = function $function() {};
|
|
28
|
+
//
|
|
29
|
+
// Function declaration or function expression names win over any
|
|
30
|
+
// assignment statements or other renames.
|
|
31
|
+
return node.id;
|
|
32
|
+
} else if (
|
|
33
|
+
node.type === 'FunctionExpression' ||
|
|
34
|
+
node.type === 'ArrowFunctionExpression'
|
|
35
|
+
) {
|
|
36
|
+
// This checks for assignments such as
|
|
37
|
+
// const $function = function () {};
|
|
38
|
+
// const $function = () => {};
|
|
39
|
+
return getParentAssignmentName(node);
|
|
40
|
+
} else {
|
|
41
|
+
return undefined;
|
|
42
|
+
}
|
|
43
|
+
};
|
|
@@ -0,0 +1,44 @@
|
|
|
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
|
+
// @ts-check
|
|
9
|
+
'use strict';
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Gets the static name of an AST node's parent, used to determine the name of an
|
|
13
|
+
* anonymous function declaration, possibly through a higher order function call.
|
|
14
|
+
* This was extracted from the body of getFunctionName so it could also be used
|
|
15
|
+
* in the context of `useCallback` or `useMemo`, e.g.
|
|
16
|
+
* `const $fun = useCallback(() => {}, [])` where the name is not the direct
|
|
17
|
+
* parent of the anonymous function.
|
|
18
|
+
*
|
|
19
|
+
* @param {import('eslint').Rule.Node} node
|
|
20
|
+
*/
|
|
21
|
+
module.exports.getParentAssignmentName = function getParentAssignmentName(
|
|
22
|
+
node,
|
|
23
|
+
) {
|
|
24
|
+
// Unlike React's rules of hooks, this does not check property assignment.
|
|
25
|
+
// The rules of lexical $function convention only applies to functions,
|
|
26
|
+
// not methods or properties.
|
|
27
|
+
const parentNode = node.parent;
|
|
28
|
+
if (parentNode == null) {
|
|
29
|
+
return undefined;
|
|
30
|
+
}
|
|
31
|
+
if (parentNode.type === 'VariableDeclarator' && parentNode.init === node) {
|
|
32
|
+
// const $function = () => {};
|
|
33
|
+
return parentNode.id;
|
|
34
|
+
} else if (
|
|
35
|
+
parentNode.type === 'AssignmentExpression' &&
|
|
36
|
+
parentNode.right === node &&
|
|
37
|
+
parentNode.operator === '='
|
|
38
|
+
) {
|
|
39
|
+
// $function = () => {};
|
|
40
|
+
return parentNode.left;
|
|
41
|
+
} else {
|
|
42
|
+
return undefined;
|
|
43
|
+
}
|
|
44
|
+
};
|
|
@@ -1,9 +0,0 @@
|
|
|
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";function e(e,n){for(var t=0;t<n.length;t++){var r=n[t];if("string"!=typeof r&&!Array.isArray(r))for(var i in r)"default"===i||i in e||(e[i]=r[i])}return e}function n(e){return e&&e.__esModule&&Object.prototype.hasOwnProperty.call(e,"default")?e.default:e}var t,r,i={name:"@lexical/eslint-plugin",version:"0.44.1-nightly.20260519.0"},o={},s={},a={};function l(){return t||(t=1,a.getParentAssignmentName=function(e){const n=e.parent;if(null!=n)return"VariableDeclarator"===n.type&&n.init===e?n.id:"AssignmentExpression"===n.type&&n.right===e&&"="===n.operator?n.left:void 0}),a}function c(){if(r)return s;r=1;const{getParentAssignmentName:e}=l();return s.getFunctionName=function(n){return"FunctionDeclaration"===n.type||"FunctionExpression"===n.type&&n.id?n.id:"FunctionExpression"===n.type||"ArrowFunctionExpression"===n.type?e(n):void 0},s}var u,f,p,d,g={};function x(){if(u)return g;function e(e){return e.replace(/[.*+?^${}()|[\]\\]/g,"\\$&")}return u=1,g.buildMatcher=function(...n){const t=[],r=[];for(const i of n.flat(1))i&&("string"==typeof i?r.push(/^[(^]/.test(i)?i:`^${e(i)}$`):i&&i instanceof RegExp?i.flags?t.push(e=>i.test(e)):r.push(i.source):"function"==typeof i&&t.push(i));const i=r.map(e=>`(?:${e})`).join("|");if(i){const e=new RegExp(i);t.push(n=>e.test(n))}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}},g}function m(){if(f)return o;f=1;const{getFunctionName:e}=c(),{getParentAssignmentName:n}=l(),{buildMatcher:t}=x();const r={isDollarFunction:[/^\$[a-z_]/],isIgnoredFunction:[],isLexicalProvider:["parseEditorState","read","registerCommand","registerNodeTransform","update"],isSafeDollarFunction:[/^\$is[A-Z_]/]};function i(e){if(e)return"Identifier"===e.type?e:"MemberExpression"!==e.type||e.computed?void 0:i(e.property)}function s(e,n){const t=Array.isArray(e.options)?e.options[0]:void 0;return t&&n in t?t[n]:void 0}const a={oneOf:[{type:"string"},{contains:{type:"string"},type:"array"}]};return o.rulesOfLexical={create(o){const a=function(e){return e.sourceCode?e.sourceCode:e.getSourceCode()}(o),l=function(e){const n={};for(const i in r){const o=i;n[o]=t(r[o],s(e,o))}return n}(o),c=new Set,u=new Set,f=[],p=e=>c.add(e),d=e=>c.delete(e),g=t=>{const r=i(function(t){const r=e(t);if(r)return r;const o=t.parent;if(null!=o&&"CallExpression"===o.type&&o.arguments[0]===t&&function(e){return e&&"Identifier"===e.type&&/^use([A-Z]|$)/.test(e.name)}(i(o.callee)))return n(o)}(t));f.push({name:r,node:t}),(l.isDollarFunction(r)||l.isIgnoredFunction(r)||l.isLexicalProvider(r))&&p(t)},x=e=>{f.pop(),d(e)};return{ArrowFunctionExpression:g,"ArrowFunctionExpression:exit":x,CallExpression:e=>{if((()=>{if(c.size>0)return!0;const e=f[f.length-1];return e&&null!=e.node.parent&&"Property"===e.node.parent.type})())return;const n=i(e.callee);if(l.isLexicalProvider(n)||l.isSafeDollarFunction(n))return void p(e);if(!l.isDollarFunction(n))return;const t=(()=>{const e=f[f.length-1];return e?e.name:void 0})();if(!t||u.has(t))return;u.add(t);const r=function(e,n){const t=e.scopeManager;for(let e=n;e;e=e.parent){const r=t.getDeclaredVariables(e).find(e=>e.identifiers.includes(n));if(r)return r;const i=t.acquire(e);if(i)return i.set.get(n.name)||(i.upper?i.upper.set.get(n.name):void 0)}}(a,t),s=function(e,n){const t=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(n)for(let e=n.scope;e;e=e.upper)if(e.set.has(t))return t+"_";return t}(t,r),d=function(e){if(e&&1===e.defs.length){const n=e.defs[0].node.parent;if(null!=n&&"ExportNamedDeclaration"===n.type)return n;if(null!=n&&"VariableDeclaration"===n.type&&null!=n.parent&&"ExportNamedDeclaration"===n.parent.type)return n.parent}}(r),g={callee:a.getText(e.callee),caller:a.getText(t),suggestName:s},x=e=>{const n=new Set,i=[],o=t=>{n.has(t)||(n.add(t),i.push(e.replaceText(t,s)))};if(o(t),d&&i.push(e.insertTextAfter(d,function({caller:e,suggestName:n}){return`\n/** @deprecated renamed to {@link ${n}} by @lexical/eslint-plugin rules-of-lexical */\nexport const ${e} = ${n};`}(g))),r)for(const e of r.references)o(e.identifier);return i};o.report({data:g,fix:x,messageId:"rulesOfLexicalReport",node:t,suggest:[{data:g,fix:x,messageId:"rulesOfLexicalSuggestion"}]})},"CallExpression:exit":d,ClassBody:p,"ClassBody:exit":d,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:a,isIgnoredFunction:a,isLexicalProvider:a,isSafeDollarFunction:a},type:"object"}],type:"suggestion"}},o}function y(){if(d)return p;d=1;const{name:e,version:n}=i,{rulesOfLexical:t}=m(),r={plugins:["@lexical"],rules:{"@lexical/rules-of-lexical":"warn"}},o={configs:{all:r,"flat/all":null,"flat/recommended":null,"legacy-all":r,"legacy-recommended":r,recommended:r},meta:{name:e,version:n},rules:{"rules-of-lexical":t}},s={plugins:{"@lexical":o},rules:{"@lexical/rules-of-lexical":"warn"}};return o.configs["flat/all"]=s,o.configs["flat/recommended"]=s,p=o}var h=y();const v=e({__proto__:null,default:n(h)},[h]);module.exports=v;
|
|
@@ -1,9 +0,0 @@
|
|
|
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,n){for(var t=0;t<n.length;t++){var r=n[t];if("string"!=typeof r&&!Array.isArray(r))for(var i in r)"default"===i||i in e||(e[i]=r[i])}return e}function n(e){return e&&e.__esModule&&Object.prototype.hasOwnProperty.call(e,"default")?e.default:e}var t,r,i={name:"@lexical/eslint-plugin",version:"0.44.1-nightly.20260519.0"},o={},s={},a={};function l(){return t||(t=1,a.getParentAssignmentName=function(e){const n=e.parent;if(null!=n)return"VariableDeclarator"===n.type&&n.init===e?n.id:"AssignmentExpression"===n.type&&n.right===e&&"="===n.operator?n.left:void 0}),a}function c(){if(r)return s;r=1;const{getParentAssignmentName:e}=l();return s.getFunctionName=function(n){return"FunctionDeclaration"===n.type||"FunctionExpression"===n.type&&n.id?n.id:"FunctionExpression"===n.type||"ArrowFunctionExpression"===n.type?e(n):void 0},s}var u,f,p,d,g={};function x(){if(u)return g;function e(e){return e.replace(/[.*+?^${}()|[\]\\]/g,"\\$&")}return u=1,g.buildMatcher=function(...n){const t=[],r=[];for(const i of n.flat(1))i&&("string"==typeof i?r.push(/^[(^]/.test(i)?i:`^${e(i)}$`):i&&i instanceof RegExp?i.flags?t.push(e=>i.test(e)):r.push(i.source):"function"==typeof i&&t.push(i));const i=r.map(e=>`(?:${e})`).join("|");if(i){const e=new RegExp(i);t.push(n=>e.test(n))}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}},g}function m(){if(f)return o;f=1;const{getFunctionName:e}=c(),{getParentAssignmentName:n}=l(),{buildMatcher:t}=x();const r={isDollarFunction:[/^\$[a-z_]/],isIgnoredFunction:[],isLexicalProvider:["parseEditorState","read","registerCommand","registerNodeTransform","update"],isSafeDollarFunction:[/^\$is[A-Z_]/]};function i(e){if(e)return"Identifier"===e.type?e:"MemberExpression"!==e.type||e.computed?void 0:i(e.property)}function s(e,n){const t=Array.isArray(e.options)?e.options[0]:void 0;return t&&n in t?t[n]:void 0}const a={oneOf:[{type:"string"},{contains:{type:"string"},type:"array"}]};return o.rulesOfLexical={create(o){const a=function(e){return e.sourceCode?e.sourceCode:e.getSourceCode()}(o),l=function(e){const n={};for(const i in r){const o=i;n[o]=t(r[o],s(e,o))}return n}(o),c=new Set,u=new Set,f=[],p=e=>c.add(e),d=e=>c.delete(e),g=t=>{const r=i(function(t){const r=e(t);if(r)return r;const o=t.parent;if(null!=o&&"CallExpression"===o.type&&o.arguments[0]===t&&function(e){return e&&"Identifier"===e.type&&/^use([A-Z]|$)/.test(e.name)}(i(o.callee)))return n(o)}(t));f.push({name:r,node:t}),(l.isDollarFunction(r)||l.isIgnoredFunction(r)||l.isLexicalProvider(r))&&p(t)},x=e=>{f.pop(),d(e)};return{ArrowFunctionExpression:g,"ArrowFunctionExpression:exit":x,CallExpression:e=>{if((()=>{if(c.size>0)return!0;const e=f[f.length-1];return e&&null!=e.node.parent&&"Property"===e.node.parent.type})())return;const n=i(e.callee);if(l.isLexicalProvider(n)||l.isSafeDollarFunction(n))return void p(e);if(!l.isDollarFunction(n))return;const t=(()=>{const e=f[f.length-1];return e?e.name:void 0})();if(!t||u.has(t))return;u.add(t);const r=function(e,n){const t=e.scopeManager;for(let e=n;e;e=e.parent){const r=t.getDeclaredVariables(e).find(e=>e.identifiers.includes(n));if(r)return r;const i=t.acquire(e);if(i)return i.set.get(n.name)||(i.upper?i.upper.set.get(n.name):void 0)}}(a,t),s=function(e,n){const t=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(n)for(let e=n.scope;e;e=e.upper)if(e.set.has(t))return t+"_";return t}(t,r),d=function(e){if(e&&1===e.defs.length){const n=e.defs[0].node.parent;if(null!=n&&"ExportNamedDeclaration"===n.type)return n;if(null!=n&&"VariableDeclaration"===n.type&&null!=n.parent&&"ExportNamedDeclaration"===n.parent.type)return n.parent}}(r),g={callee:a.getText(e.callee),caller:a.getText(t),suggestName:s},x=e=>{const n=new Set,i=[],o=t=>{n.has(t)||(n.add(t),i.push(e.replaceText(t,s)))};if(o(t),d&&i.push(e.insertTextAfter(d,function({caller:e,suggestName:n}){return`\n/** @deprecated renamed to {@link ${n}} by @lexical/eslint-plugin rules-of-lexical */\nexport const ${e} = ${n};`}(g))),r)for(const e of r.references)o(e.identifier);return i};o.report({data:g,fix:x,messageId:"rulesOfLexicalReport",node:t,suggest:[{data:g,fix:x,messageId:"rulesOfLexicalSuggestion"}]})},"CallExpression:exit":d,ClassBody:p,"ClassBody:exit":d,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:a,isIgnoredFunction:a,isLexicalProvider:a,isSafeDollarFunction:a},type:"object"}],type:"suggestion"}},o}function y(){if(d)return p;d=1;const{name:e,version:n}=i,{rulesOfLexical:t}=m(),r={plugins:["@lexical"],rules:{"@lexical/rules-of-lexical":"warn"}},o={configs:{all:r,"flat/all":null,"flat/recommended":null,"legacy-all":r,"legacy-recommended":r,recommended:r},meta:{name:e,version:n},rules:{"rules-of-lexical":t}},s={plugins:{"@lexical":o},rules:{"@lexical/rules-of-lexical":"warn"}};return o.configs["flat/all"]=s,o.configs["flat/recommended"]=s,p=o}var h=y();const v=e({__proto__:null,default:n(h)},[h]);export{v as default};
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|