@eslint-react/jsx 0.5.5
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/dist/index.cjs +594 -0
- package/dist/index.d.mts +188 -0
- package/dist/index.d.ts +188 -0
- package/dist/index.js +594 -0
- package/dist/index.mjs +554 -0
- package/package.json +56 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2023 Eva1ent
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/dist/index.cjs
ADDED
|
@@ -0,0 +1,594 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
var tools = require('@eslint-react/tools');
|
|
4
|
+
var memo = require('micro-memoize');
|
|
5
|
+
var ast = require('@eslint-react/ast');
|
|
6
|
+
var types = require('@typescript-eslint/types');
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Check if a JSXElement or JSXFragment has children
|
|
10
|
+
* @param node The AST node to check
|
|
11
|
+
* @returns `true` if the node has children
|
|
12
|
+
*/ function hasChildren(node) {
|
|
13
|
+
return node.children.length > 0;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
const RE_JSX_ANNOTATION_REGEX = /@jsx\s+(\S+)/u;
|
|
17
|
+
// Does not check for reserved keywords or unicode characters
|
|
18
|
+
const RE_JS_IDENTIFIER_REGEX = /^[$A-Z_a-z][\w$]*$/u;
|
|
19
|
+
function getFragmentFromContext(context) {
|
|
20
|
+
// eslint-disable-next-line prefer-destructuring
|
|
21
|
+
const settings = context.settings;
|
|
22
|
+
const pragma = settings.react?.fragment ?? "Fragment";
|
|
23
|
+
if (!RE_JS_IDENTIFIER_REGEX.test(pragma)) {
|
|
24
|
+
return tools.E.left(new Error(`Fragment pragma ${pragma} is not a valid identifier`));
|
|
25
|
+
}
|
|
26
|
+
return tools.E.right(pragma);
|
|
27
|
+
}
|
|
28
|
+
const getPragmaFromContext = memo((context)=>{
|
|
29
|
+
// eslint-disable-next-line prefer-destructuring
|
|
30
|
+
const settings = context.settings;
|
|
31
|
+
const sourceCode = context.getSourceCode();
|
|
32
|
+
const pragmaNode = sourceCode.getAllComments().find((node)=>RE_JSX_ANNOTATION_REGEX.test(node.value));
|
|
33
|
+
const pragma = settings.react?.pragma ?? tools.F.pipe(tools.O.fromNullable(pragmaNode), tools.O.map((node)=>RE_JSX_ANNOTATION_REGEX.exec(node.value)), tools.O.flatMap((matches)=>tools.O.fromNullable(matches?.[1]?.split(".")[0])), tools.O.getOrElse(()=>"React"));
|
|
34
|
+
if (!RE_JS_IDENTIFIER_REGEX.test(pragma)) {
|
|
35
|
+
return tools.E.left(new Error(`React pragma ${pragma} is not a valid identifier`));
|
|
36
|
+
}
|
|
37
|
+
return tools.E.right(pragma);
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
const t=Symbol.for("@ts-pattern/matcher"),e=Symbol.for("@ts-pattern/isVariadic"),n="@ts-pattern/anonymous-select-key",r=t=>Boolean(t&&"object"==typeof t),i=e=>e&&!!e[t],s=(n,o,c)=>{if(i(n)){const e=n[t](),{matched:r,selections:i}=e.match(o);return r&&i&&Object.keys(i).forEach(t=>c(t,i[t])),r}if(r(n)){if(!r(o))return !1;if(Array.isArray(n)){if(!Array.isArray(o))return !1;let t=[],r=[],a=[];for(const s of n.keys()){const o=n[s];i(o)&&o[e]?a.push(o):a.length?r.push(o):t.push(o);}if(a.length){if(a.length>1)throw new Error("Pattern error: Using `...P.array(...)` several times in a single pattern is not allowed.");if(o.length<t.length+r.length)return !1;const e=o.slice(0,t.length),n=0===r.length?[]:o.slice(-r.length),i=o.slice(t.length,0===r.length?Infinity:-r.length);return t.every((t,n)=>s(t,e[n],c))&&r.every((t,e)=>s(t,n[e],c))&&(0===a.length||s(a[0],i,c))}return n.length===o.length&&n.every((t,e)=>s(t,o[e],c))}return Object.keys(n).every(e=>{const r=n[e];return (e in o||i(a=r)&&"optional"===a[t]().matcherType)&&s(r,o[e],c);var a;})}return Object.is(o,n)},o=e=>{var n,s,a;return r(e)?i(e)?null!=(n=null==(s=(a=e[t]()).getSelectionKeys)?void 0:s.call(a))?n:[]:Array.isArray(e)?c(e,o):c(Object.values(e),o):[]},c=(t,e)=>t.reduce((t,n)=>t.concat(e(n)),[]);function a(...t){if(1===t.length){const[e]=t;return t=>s(e,t,()=>{})}if(2===t.length){const[e,n]=t;return s(e,n,()=>{})}throw new Error(`isMatching wasn't given the right number of arguments: expected 1 or 2, received ${t.length}.`)}function u(t){return Object.assign(t,{optional:()=>l(t),and:e=>m(t,e),or:e=>y(t,e),select:e=>void 0===e?p(t):p(e,t)})}function h(t){return Object.assign((t=>Object.assign(t,{*[Symbol.iterator](){yield Object.assign(t,{[e]:!0});}}))(t),{optional:()=>h(l(t)),select:e=>h(void 0===e?p(t):p(e,t))})}function l(e){return u({[t]:()=>({match:t=>{let n={};const r=(t,e)=>{n[t]=e;};return void 0===t?(o(e).forEach(t=>r(t,void 0)),{matched:!0,selections:n}):{matched:s(e,t,r),selections:n}},getSelectionKeys:()=>o(e),matcherType:"optional"})})}const f=(t,e)=>{for(const n of t)if(!e(n))return !1;return !0},g=(t,e)=>{for(const[n,r]of t.entries())if(!e(r,n))return !1;return !0};function m(...e){return u({[t]:()=>({match:t=>{let n={};const r=(t,e)=>{n[t]=e;};return {matched:e.every(e=>s(e,t,r)),selections:n}},getSelectionKeys:()=>c(e,o),matcherType:"and"})})}function y(...e){return u({[t]:()=>({match:t=>{let n={};const r=(t,e)=>{n[t]=e;};return c(e,o).forEach(t=>r(t,void 0)),{matched:e.some(e=>s(e,t,r)),selections:n}},getSelectionKeys:()=>c(e,o),matcherType:"or"})})}function d(e){return {[t]:()=>({match:t=>({matched:Boolean(e(t))})})}}function p(...e){const r="string"==typeof e[0]?e[0]:void 0,i=2===e.length?e[1]:"string"==typeof e[0]?void 0:e[0];return u({[t]:()=>({match:t=>{let e={[null!=r?r:n]:t};return {matched:void 0===i||s(i,t,(t,n)=>{e[t]=n;}),selections:e}},getSelectionKeys:()=>[null!=r?r:n].concat(void 0===i?[]:o(i))})})}function v(t){return "number"==typeof t}function b(t){return "string"==typeof t}function w(t){return "bigint"==typeof t}const S=u(d(function(t){return !0})),O=S,j=t=>Object.assign(u(t),{startsWith:e=>{return j(m(t,(n=e,d(t=>b(t)&&t.startsWith(n)))));var n;},endsWith:e=>{return j(m(t,(n=e,d(t=>b(t)&&t.endsWith(n)))));var n;},minLength:e=>j(m(t,(t=>d(e=>b(e)&&e.length>=t))(e))),maxLength:e=>j(m(t,(t=>d(e=>b(e)&&e.length<=t))(e))),includes:e=>{return j(m(t,(n=e,d(t=>b(t)&&t.includes(n)))));var n;},regex:e=>{return j(m(t,(n=e,d(t=>b(t)&&Boolean(t.match(n))))));var n;}}),E=j(d(b)),K=t=>Object.assign(u(t),{between:(e,n)=>K(m(t,((t,e)=>d(n=>v(n)&&t<=n&&e>=n))(e,n))),lt:e=>K(m(t,(t=>d(e=>v(e)&&e<t))(e))),gt:e=>K(m(t,(t=>d(e=>v(e)&&e>t))(e))),lte:e=>K(m(t,(t=>d(e=>v(e)&&e<=t))(e))),gte:e=>K(m(t,(t=>d(e=>v(e)&&e>=t))(e))),int:()=>K(m(t,d(t=>v(t)&&Number.isInteger(t)))),finite:()=>K(m(t,d(t=>v(t)&&Number.isFinite(t)))),positive:()=>K(m(t,d(t=>v(t)&&t>0))),negative:()=>K(m(t,d(t=>v(t)&&t<0)))}),A=K(d(v)),x=t=>Object.assign(u(t),{between:(e,n)=>x(m(t,((t,e)=>d(n=>w(n)&&t<=n&&e>=n))(e,n))),lt:e=>x(m(t,(t=>d(e=>w(e)&&e<t))(e))),gt:e=>x(m(t,(t=>d(e=>w(e)&&e>t))(e))),lte:e=>x(m(t,(t=>d(e=>w(e)&&e<=t))(e))),gte:e=>x(m(t,(t=>d(e=>w(e)&&e>=t))(e))),positive:()=>x(m(t,d(t=>w(t)&&t>0))),negative:()=>x(m(t,d(t=>w(t)&&t<0)))}),P=x(d(w)),T=u(d(function(t){return "boolean"==typeof t})),k=u(d(function(t){return "symbol"==typeof t})),B=u(d(function(t){return null==t}));var _={__proto__:null,matcher:t,optional:l,array:function(...e){return h({[t]:()=>({match:t=>{if(!Array.isArray(t))return {matched:!1};if(0===e.length)return {matched:!0};const n=e[0];let r={};if(0===t.length)return o(n).forEach(t=>{r[t]=[];}),{matched:!0,selections:r};const i=(t,e)=>{r[t]=(r[t]||[]).concat([e]);};return {matched:t.every(t=>s(n,t,i)),selections:r}},getSelectionKeys:()=>0===e.length?[]:o(e[0])})})},set:function(...e){return u({[t]:()=>({match:t=>{if(!(t instanceof Set))return {matched:!1};let n={};if(0===t.size)return {matched:!0,selections:n};if(0===e.length)return {matched:!0};const r=(t,e)=>{n[t]=(n[t]||[]).concat([e]);},i=e[0];return {matched:f(t,t=>s(i,t,r)),selections:n}},getSelectionKeys:()=>0===e.length?[]:o(e[0])})})},map:function(...e){return u({[t]:()=>({match:t=>{if(!(t instanceof Map))return {matched:!1};let n={};if(0===t.size)return {matched:!0,selections:n};const r=(t,e)=>{n[t]=(n[t]||[]).concat([e]);};if(0===e.length)return {matched:!0};var i;if(1===e.length)throw new Error(`\`P.map\` wasn't given enough arguments. Expected (key, value), received ${null==(i=e[0])?void 0:i.toString()}`);const[o,c]=e;return {matched:g(t,(t,e)=>{const n=s(o,e,r),i=s(c,t,r);return n&&i}),selections:n}},getSelectionKeys:()=>0===e.length?[]:[...o(e[0]),...o(e[1])]})})},intersection:m,union:y,not:function(e){return u({[t]:()=>({match:t=>({matched:!s(e,t,()=>{})}),getSelectionKeys:()=>[],matcherType:"not"})})},when:d,select:p,any:S,_:O,string:E,number:A,bigint:P,boolean:T,symbol:k,nullish:B,instanceOf:function(t){return u(d(function(t){return e=>e instanceof t}(t)))},shape:function(t){return u(d(a(t)))}};const W={matched:!1,value:void 0};function N(t){return new $(t,W)}class ${constructor(t,e){this.input=void 0,this.state=void 0,this.input=t,this.state=e;}with(...t){if(this.state.matched)return this;const e=t[t.length-1],r=[t[0]];let i;3===t.length&&"function"==typeof t[1]?(r.push(t[0]),i=t[1]):t.length>2&&r.push(...t.slice(1,t.length-1));let o=!1,c={};const a=(t,e)=>{o=!0,c[t]=e;},u=!r.some(t=>s(t,this.input,a))||i&&!Boolean(i(this.input))?W:{matched:!0,value:e(o?n in c?c[n]:c:this.input,this.input)};return new $(this.input,u)}when(t,e){if(this.state.matched)return this;const n=Boolean(t(this.input));return new $(this.input,n?{matched:!0,value:e(this.input,this.input)}:W)}otherwise(t){return this.state.matched?this.state.value:t(this.input)}exhaustive(){return this.run()}run(){if(this.state.matched)return this.state.value;let t;try{t=JSON.stringify(this.input);}catch(e){t=this.input;}throw new Error(`Pattern matching error: no pattern matches value ${t}`)}returnType(){return this}}
|
|
41
|
+
|
|
42
|
+
function isDestructuredFromPragma(variableName, context) {
|
|
43
|
+
const maybePragma = getPragmaFromContext(context);
|
|
44
|
+
const variables = ast.getVariablesUpToGlobal(context.getScope());
|
|
45
|
+
if (tools.E.isLeft(maybePragma)) {
|
|
46
|
+
return false;
|
|
47
|
+
}
|
|
48
|
+
const pragma = maybePragma.right;
|
|
49
|
+
const maybeLatestDef = tools.F.pipe(ast.findVariableByName(variableName)(variables), tools.O.flatMapNullable((variable)=>variable.defs.at(-1)));
|
|
50
|
+
if (tools.O.isNone(maybeLatestDef)) {
|
|
51
|
+
return false;
|
|
52
|
+
}
|
|
53
|
+
const latestDef = maybeLatestDef.value;
|
|
54
|
+
const { node, parent } = latestDef;
|
|
55
|
+
if (node.type === ast.NodeType.VariableDeclarator && node.init) {
|
|
56
|
+
const { init } = node;
|
|
57
|
+
// check for: `variable = pragma.variable`
|
|
58
|
+
if (a({
|
|
59
|
+
type: "MemberExpression",
|
|
60
|
+
object: {
|
|
61
|
+
type: "Identifier",
|
|
62
|
+
name: pragma
|
|
63
|
+
}
|
|
64
|
+
}, init)) {
|
|
65
|
+
return true;
|
|
66
|
+
}
|
|
67
|
+
// check for: `{ variable } = pragma`
|
|
68
|
+
if (a({
|
|
69
|
+
type: "Identifier",
|
|
70
|
+
name: pragma
|
|
71
|
+
}, init)) {
|
|
72
|
+
return true;
|
|
73
|
+
}
|
|
74
|
+
// check if from a require call: `require("react")`
|
|
75
|
+
const maybeRequireExpression = N(init).with({
|
|
76
|
+
type: ast.NodeType.CallExpression
|
|
77
|
+
}, (exp)=>tools.O.some(exp)).with({
|
|
78
|
+
type: ast.NodeType.MemberExpression,
|
|
79
|
+
object: {
|
|
80
|
+
type: ast.NodeType.CallExpression
|
|
81
|
+
}
|
|
82
|
+
}, ({ object })=>tools.O.some(object)).otherwise(tools.O.none);
|
|
83
|
+
if (tools.O.isNone(maybeRequireExpression)) {
|
|
84
|
+
return false;
|
|
85
|
+
}
|
|
86
|
+
const requireExpression = maybeRequireExpression.value;
|
|
87
|
+
if (requireExpression.callee.type !== ast.NodeType.Identifier) {
|
|
88
|
+
return false;
|
|
89
|
+
}
|
|
90
|
+
const calleeName = requireExpression.callee.name;
|
|
91
|
+
const [firstArg] = requireExpression.arguments;
|
|
92
|
+
if (calleeName !== "require" || firstArg?.type !== ast.NodeType.Literal) {
|
|
93
|
+
return false;
|
|
94
|
+
}
|
|
95
|
+
return firstArg.value === pragma.toLowerCase();
|
|
96
|
+
}
|
|
97
|
+
// latest definition is an import declaration: import { variable } from 'react'
|
|
98
|
+
return a({
|
|
99
|
+
type: "ImportDeclaration",
|
|
100
|
+
source: {
|
|
101
|
+
value: pragma.toLowerCase()
|
|
102
|
+
}
|
|
103
|
+
}, parent);
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
/**
|
|
107
|
+
* Checks if the given node is a call expression to the given function or method of the pragma
|
|
108
|
+
* @param name The name of the function or method to check
|
|
109
|
+
* @returns A predicate that checks if the given node is a call expression to the given function or method
|
|
110
|
+
*/ const isCallFromPragma = (name)=>(node, context)=>{
|
|
111
|
+
if (node.type !== ast.NodeType.CallExpression || !("callee" in node)) {
|
|
112
|
+
return false;
|
|
113
|
+
}
|
|
114
|
+
const maybePragma = getPragmaFromContext(context);
|
|
115
|
+
if (tools.E.isLeft(maybePragma)) {
|
|
116
|
+
return false;
|
|
117
|
+
}
|
|
118
|
+
const pragma = maybePragma.right;
|
|
119
|
+
return N(node.callee).with({
|
|
120
|
+
type: ast.NodeType.MemberExpression,
|
|
121
|
+
object: {
|
|
122
|
+
name: pragma
|
|
123
|
+
},
|
|
124
|
+
property: {
|
|
125
|
+
name
|
|
126
|
+
}
|
|
127
|
+
}, tools.F.constTrue).with({
|
|
128
|
+
name
|
|
129
|
+
}, ({ name })=>isDestructuredFromPragma(name, context)).otherwise(tools.F.constFalse);
|
|
130
|
+
};
|
|
131
|
+
|
|
132
|
+
/**
|
|
133
|
+
* Checks if the given node is a call expression to `React.createElement`
|
|
134
|
+
* @param node The AST node to check
|
|
135
|
+
* @param context The rule context
|
|
136
|
+
* @returns `true` if the node is a call expression to `createElement`
|
|
137
|
+
*/ const isCreateElementCall = isCallFromPragma("createElement");
|
|
138
|
+
/**
|
|
139
|
+
* Checks if the given node is a call expression to `React.cloneElement`
|
|
140
|
+
* @param node The AST node to check
|
|
141
|
+
* @param context The rule context
|
|
142
|
+
* @returns `true` if the node is a call expression to `cloneElement`
|
|
143
|
+
*/ const isCloneElementCall = isCallFromPragma("cloneElement");
|
|
144
|
+
|
|
145
|
+
function isChildrenOfCreateElement(node, context) {
|
|
146
|
+
const maybeCallExpression = node.parent;
|
|
147
|
+
if (!maybeCallExpression || !isCreateElementCall(maybeCallExpression, context)) {
|
|
148
|
+
return false;
|
|
149
|
+
}
|
|
150
|
+
return maybeCallExpression.arguments.slice(2).some((child)=>child === node);
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
/**
|
|
154
|
+
* Determines whether inside createElement's props.
|
|
155
|
+
* @param node The AST node to check
|
|
156
|
+
* @param context The rule context
|
|
157
|
+
* @returns `true` if the node is inside createElement's props
|
|
158
|
+
*/ function isInsideCreateElementProps(node, context) {
|
|
159
|
+
const parentCreateElement = ast.traverseUp(node, (n)=>isCreateElementCall(n, context));
|
|
160
|
+
if (parentCreateElement?.type !== ast.NodeType.CallExpression) {
|
|
161
|
+
return false;
|
|
162
|
+
}
|
|
163
|
+
return parentCreateElement.arguments.at(1) === ast.traverseUp(node, ast.is(ast.NodeType.ObjectExpression));
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
function resolveMemberExpressions(object, property) {
|
|
167
|
+
if (object.type === types.AST_NODE_TYPES.JSXMemberExpression) {
|
|
168
|
+
return `${resolveMemberExpressions(object.object, object.property)}.${property.name}`;
|
|
169
|
+
}
|
|
170
|
+
if (object.type === types.AST_NODE_TYPES.JSXNamespacedName) {
|
|
171
|
+
return `${object.namespace.name}:${object.name.name}.${property.name}`;
|
|
172
|
+
}
|
|
173
|
+
return `${object.name}.${property.name}`;
|
|
174
|
+
}
|
|
175
|
+
/**
|
|
176
|
+
* Returns the tag name associated with a JSXOpeningElement.
|
|
177
|
+
* @param node The visited JSXOpeningElement node object.
|
|
178
|
+
* @returns The element's tag name.
|
|
179
|
+
*/ function elementType(node) {
|
|
180
|
+
if (node.type === types.AST_NODE_TYPES.JSXOpeningFragment) {
|
|
181
|
+
return "<>";
|
|
182
|
+
}
|
|
183
|
+
const { name } = node;
|
|
184
|
+
if (name.type === types.AST_NODE_TYPES.JSXMemberExpression) {
|
|
185
|
+
const { object, property } = name;
|
|
186
|
+
return resolveMemberExpressions(object, property);
|
|
187
|
+
}
|
|
188
|
+
if (name.type === types.AST_NODE_TYPES.JSXNamespacedName) {
|
|
189
|
+
return `${name.namespace.name}:${name.name.name}`;
|
|
190
|
+
}
|
|
191
|
+
return name.name;
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
const hdlAnimation = [
|
|
195
|
+
"onAnimationStart",
|
|
196
|
+
"onAnimationEnd",
|
|
197
|
+
"onAnimationIteration"
|
|
198
|
+
];
|
|
199
|
+
const hdlClipboard = [
|
|
200
|
+
"onCopy",
|
|
201
|
+
"onCut",
|
|
202
|
+
"onPaste"
|
|
203
|
+
];
|
|
204
|
+
const hdlComposition = [
|
|
205
|
+
"onCompositionEnd",
|
|
206
|
+
"onCompositionStart",
|
|
207
|
+
"onCompositionUpdate"
|
|
208
|
+
];
|
|
209
|
+
const hdlFocus = [
|
|
210
|
+
"onFocus",
|
|
211
|
+
"onBlur"
|
|
212
|
+
];
|
|
213
|
+
const hdlForm = [
|
|
214
|
+
"onChange",
|
|
215
|
+
"onInput",
|
|
216
|
+
"onSubmit"
|
|
217
|
+
];
|
|
218
|
+
const hdlImage = [
|
|
219
|
+
"onLoad",
|
|
220
|
+
"onError"
|
|
221
|
+
];
|
|
222
|
+
const hdlKeyboard = [
|
|
223
|
+
"onKeyDown",
|
|
224
|
+
"onKeyPress",
|
|
225
|
+
"onKeyUp"
|
|
226
|
+
];
|
|
227
|
+
const hdlMedia = [
|
|
228
|
+
"onAbort",
|
|
229
|
+
"onCanPlay",
|
|
230
|
+
"onCanPlayThrough",
|
|
231
|
+
"onDurationChange",
|
|
232
|
+
"onEmptied",
|
|
233
|
+
"onEncrypted",
|
|
234
|
+
"onEnded",
|
|
235
|
+
"onError",
|
|
236
|
+
"onLoadedData",
|
|
237
|
+
"onLoadedMetadata",
|
|
238
|
+
"onLoadStart",
|
|
239
|
+
"onPause",
|
|
240
|
+
"onPlay",
|
|
241
|
+
"onPlaying",
|
|
242
|
+
"onProgress",
|
|
243
|
+
"onRateChange",
|
|
244
|
+
"onSeeked",
|
|
245
|
+
"onSeeking",
|
|
246
|
+
"onStalled",
|
|
247
|
+
"onSuspend",
|
|
248
|
+
"onTimeUpdate",
|
|
249
|
+
"onVolumeChange",
|
|
250
|
+
"onWaiting"
|
|
251
|
+
];
|
|
252
|
+
const hdlMouse = [
|
|
253
|
+
"onClick",
|
|
254
|
+
"onContextMenu",
|
|
255
|
+
"onDblClick",
|
|
256
|
+
"onDoubleClick",
|
|
257
|
+
"onDrag",
|
|
258
|
+
"onDragEnd",
|
|
259
|
+
"onDragEnter",
|
|
260
|
+
"onDragExit",
|
|
261
|
+
"onDragLeave",
|
|
262
|
+
"onDragOver",
|
|
263
|
+
"onDragStart",
|
|
264
|
+
"onDrop",
|
|
265
|
+
"onMouseDown",
|
|
266
|
+
"onMouseEnter",
|
|
267
|
+
"onMouseLeave",
|
|
268
|
+
"onMouseMove",
|
|
269
|
+
"onMouseOut",
|
|
270
|
+
"onMouseOver",
|
|
271
|
+
"onMouseUp"
|
|
272
|
+
];
|
|
273
|
+
const hdlSelection = [
|
|
274
|
+
"onSelect"
|
|
275
|
+
];
|
|
276
|
+
const hdlTouch = [
|
|
277
|
+
"onTouchCancel",
|
|
278
|
+
"onTouchEnd",
|
|
279
|
+
"onTouchMove",
|
|
280
|
+
"onTouchStart"
|
|
281
|
+
];
|
|
282
|
+
const hdlTransition = [
|
|
283
|
+
"onTransitionEnd"
|
|
284
|
+
];
|
|
285
|
+
const hdlScroll = [
|
|
286
|
+
"onScroll"
|
|
287
|
+
];
|
|
288
|
+
const hdlWheel = [
|
|
289
|
+
"onWheel"
|
|
290
|
+
];
|
|
291
|
+
[
|
|
292
|
+
...hdlAnimation,
|
|
293
|
+
...hdlClipboard,
|
|
294
|
+
...hdlComposition,
|
|
295
|
+
...hdlFocus,
|
|
296
|
+
...hdlForm,
|
|
297
|
+
...hdlImage,
|
|
298
|
+
...hdlKeyboard,
|
|
299
|
+
...hdlMedia,
|
|
300
|
+
...hdlMouse,
|
|
301
|
+
...hdlSelection,
|
|
302
|
+
...hdlTouch,
|
|
303
|
+
...hdlTransition,
|
|
304
|
+
...hdlScroll,
|
|
305
|
+
...hdlWheel
|
|
306
|
+
];
|
|
307
|
+
|
|
308
|
+
/**
|
|
309
|
+
* Tests if a value is a `string`.
|
|
310
|
+
*
|
|
311
|
+
* @param input - The value to test.
|
|
312
|
+
*
|
|
313
|
+
* @example
|
|
314
|
+
* import { isString } from "effect/Predicate"
|
|
315
|
+
*
|
|
316
|
+
* assert.deepStrictEqual(isString("a"), true)
|
|
317
|
+
*
|
|
318
|
+
* assert.deepStrictEqual(isString(1), false)
|
|
319
|
+
*
|
|
320
|
+
* @category guards
|
|
321
|
+
* @since 2.0.0
|
|
322
|
+
*/
|
|
323
|
+
const isString = input => typeof input === "string";
|
|
324
|
+
|
|
325
|
+
const defaultJSXValueCheckOptions = {
|
|
326
|
+
ignoreNull: false,
|
|
327
|
+
strict: false
|
|
328
|
+
};
|
|
329
|
+
/**
|
|
330
|
+
* Check if a node is a JSX value
|
|
331
|
+
* @param node The AST node to check
|
|
332
|
+
* @param context The rule context
|
|
333
|
+
* @param options The `JSXValueCheckOptions` to use
|
|
334
|
+
* @returns boolean
|
|
335
|
+
*/ function isJSXValue(node, context, options = defaultJSXValueCheckOptions) {
|
|
336
|
+
if (!node) {
|
|
337
|
+
return false;
|
|
338
|
+
}
|
|
339
|
+
// TODO: implement strict mode
|
|
340
|
+
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
341
|
+
const { ignoreNull = false, strict = false } = options;
|
|
342
|
+
return N(node.type).with(ast.NodeType.JSXElement, tools.F.constTrue).with(ast.NodeType.JSXFragment, tools.F.constTrue).with(ast.NodeType.Literal, ()=>{
|
|
343
|
+
if (!("value" in node) || ignoreNull) {
|
|
344
|
+
return false;
|
|
345
|
+
}
|
|
346
|
+
return node.value === null;
|
|
347
|
+
}).with(ast.NodeType.ConditionalExpression, ()=>{
|
|
348
|
+
if (!("consequent" in node)) {
|
|
349
|
+
return false;
|
|
350
|
+
}
|
|
351
|
+
const leftHasJSX = N(node.consequent).with(_.array(), (n)=>n.some((n)=>isJSXValue(n, context, options))).with(_.any, (n)=>isJSXValue(n, context, options)).exhaustive();
|
|
352
|
+
if (leftHasJSX) {
|
|
353
|
+
return true;
|
|
354
|
+
}
|
|
355
|
+
return "alternate" in node && isJSXValue(node.alternate, context, options);
|
|
356
|
+
}).with(ast.NodeType.LogicalExpression, ()=>{
|
|
357
|
+
if (!("left" in node)) {
|
|
358
|
+
return false;
|
|
359
|
+
}
|
|
360
|
+
return isJSXValue(node.left, context, options) || isJSXValue(node.right, context, options);
|
|
361
|
+
}).with(ast.NodeType.SequenceExpression, ()=>{
|
|
362
|
+
if (!("expressions" in node)) {
|
|
363
|
+
return false;
|
|
364
|
+
}
|
|
365
|
+
const exp = node.expressions.at(-1);
|
|
366
|
+
return isJSXValue(exp, context, options);
|
|
367
|
+
}).with(ast.NodeType.CallExpression, ()=>isCreateElementCall(node, context)).with(ast.NodeType.Identifier, ()=>{
|
|
368
|
+
if (!("name" in node)) {
|
|
369
|
+
return false;
|
|
370
|
+
}
|
|
371
|
+
if (ast.isJSXTagNameExpression(node)) {
|
|
372
|
+
return true;
|
|
373
|
+
}
|
|
374
|
+
if (!isString(node.name) && node.name.type !== ast.NodeType.Identifier) {
|
|
375
|
+
return ast.isJSXTagNameExpression(node.name);
|
|
376
|
+
}
|
|
377
|
+
const name = N(node.name).with(_.string, tools.F.identity).with({
|
|
378
|
+
type: ast.NodeType.Identifier
|
|
379
|
+
}, (n)=>n.name).exhaustive();
|
|
380
|
+
const variable = ast.findVariableByNameUpToGlobal(name, context.getScope());
|
|
381
|
+
return tools.F.pipe(variable, tools.O.flatMap(ast.getVariableNthDefNodeInit(0)), tools.O.map(ast.isOneOf([
|
|
382
|
+
ast.NodeType.JSXElement,
|
|
383
|
+
ast.NodeType.JSXFragment
|
|
384
|
+
])), tools.O.getOrElse(tools.F.constFalse));
|
|
385
|
+
}).otherwise(tools.F.constFalse);
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
/**
|
|
389
|
+
* Check if function is returning JSX
|
|
390
|
+
* @param node The return statement node to check
|
|
391
|
+
* @param context The rule context
|
|
392
|
+
* @param options The JSX value check options
|
|
393
|
+
* @returns boolean
|
|
394
|
+
*/ function isFunctionReturningJSXValue(node, context, options) {
|
|
395
|
+
if (node.body.type !== ast.NodeType.BlockStatement) {
|
|
396
|
+
return isJSXValue(node.body, context, options);
|
|
397
|
+
}
|
|
398
|
+
const statements = ast.getNestedReturnStatements(node.body);
|
|
399
|
+
return statements.some((statement)=>isJSXValue(statement.argument, context, options));
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
/**
|
|
403
|
+
* Check if the given prop name is present in the given attributes
|
|
404
|
+
* @param attributes The attributes to search in
|
|
405
|
+
* @param propName The prop name to search for
|
|
406
|
+
* @param context The rule context
|
|
407
|
+
* @returns `true` if the given prop name is present in the given properties
|
|
408
|
+
*/ function hasProp(attributes, propName, context) {
|
|
409
|
+
return tools.O.isSome(findPropInAttributes(attributes, context)(propName));
|
|
410
|
+
}
|
|
411
|
+
/**
|
|
412
|
+
* Check if any of the given prop names are present in the given attributes
|
|
413
|
+
* @param attributes The attributes to search in
|
|
414
|
+
* @param propNames The prop names to search for
|
|
415
|
+
* @param context The rule context
|
|
416
|
+
* @returns `true` if any of the given prop names are present in the given attributes
|
|
417
|
+
*/ function hasAnyProp(attributes, propNames, context) {
|
|
418
|
+
return propNames.some((propName)=>hasProp(attributes, propName, context));
|
|
419
|
+
}
|
|
420
|
+
/**
|
|
421
|
+
* Check if all of the given prop names are present in the given attributes
|
|
422
|
+
* @param attributes The attributes to search in
|
|
423
|
+
* @param propNames The prop names to search for
|
|
424
|
+
* @param context The rule context
|
|
425
|
+
* @returns `true` if all of the given prop names are present in the given attributes
|
|
426
|
+
*/ function hasEveryProp(attributes, propNames, context) {
|
|
427
|
+
return propNames.every((propName)=>hasProp(attributes, propName, context));
|
|
428
|
+
}
|
|
429
|
+
/**
|
|
430
|
+
* Get the name of a JSX attribute
|
|
431
|
+
* @param node The JSX attribute node
|
|
432
|
+
* @returns string
|
|
433
|
+
*/ function getPropName(node) {
|
|
434
|
+
return N(node.name).when(ast.is(ast.NodeType.JSXIdentifier), (n)=>n.name).when(ast.is(ast.NodeType.JSXNamespacedName), (n)=>n.name.name).exhaustive();
|
|
435
|
+
}
|
|
436
|
+
/**
|
|
437
|
+
* Get the name of a JSX attribute with namespace
|
|
438
|
+
* @param node The JSX attribute node
|
|
439
|
+
* @returns string
|
|
440
|
+
*/ function getPropNameWithNamespace(node) {
|
|
441
|
+
return N(node.name).when(ast.is(ast.NodeType.JSXIdentifier), (n)=>n.name).when(ast.is(ast.NodeType.JSXNamespacedName), (n)=>`${n.namespace.name}:${n.name.name}`).exhaustive();
|
|
442
|
+
}
|
|
443
|
+
/**
|
|
444
|
+
* Traverses up prop node
|
|
445
|
+
* @param node The AST node to start traversing from
|
|
446
|
+
* @param predicate The predicate to check each node
|
|
447
|
+
* @returns prop node if found
|
|
448
|
+
*/ function traverseUpProp(node, predicate = tools.F.constTrue) {
|
|
449
|
+
const matcher = (node)=>{
|
|
450
|
+
return node.type === ast.NodeType.JSXAttribute && predicate(node);
|
|
451
|
+
};
|
|
452
|
+
return tools.O.fromNullable(ast.traverseUpGuard(node, matcher));
|
|
453
|
+
}
|
|
454
|
+
/**
|
|
455
|
+
* Checks if the node is inside a prop's value
|
|
456
|
+
* @param node The AST node to check
|
|
457
|
+
* @returns `true` if the node is inside a prop's value
|
|
458
|
+
*/ function isInsidePropValue(node) {
|
|
459
|
+
if (ast.isStringLiteral(node)) {
|
|
460
|
+
return node.parent.type === ast.NodeType.JSXAttribute;
|
|
461
|
+
}
|
|
462
|
+
return tools.O.isSome(traverseUpProp(node, (n)=>n.value?.type === ast.NodeType.JSXExpressionContainer));
|
|
463
|
+
}
|
|
464
|
+
/**
|
|
465
|
+
* @param properties The properties to search in
|
|
466
|
+
* @param context The rule context
|
|
467
|
+
* @param seenProps The properties that have already been seen
|
|
468
|
+
* @returns A function that searches for a property in the given properties
|
|
469
|
+
*/ function findPropInProperties(properties, context, seenProps = []) {
|
|
470
|
+
/**
|
|
471
|
+
* Search for a property in the given properties
|
|
472
|
+
* @param propName The name of the property to search for
|
|
473
|
+
* @returns The property if found
|
|
474
|
+
*/ return (propName)=>{
|
|
475
|
+
return tools.O.fromNullable(properties.find((prop)=>{
|
|
476
|
+
if (prop.type === ast.NodeType.Property) {
|
|
477
|
+
return "name" in prop.key && prop.key.name === propName;
|
|
478
|
+
}
|
|
479
|
+
if (prop.type === ast.NodeType.SpreadElement) {
|
|
480
|
+
if (!("argument" in prop && "name" in prop.argument)) {
|
|
481
|
+
return false;
|
|
482
|
+
}
|
|
483
|
+
const { name } = prop.argument;
|
|
484
|
+
const maybeFirstDefNodeInit = tools.F.pipe(ast.findVariableByNameUpToGlobal(name, context.getScope()), tools.O.flatMap(ast.getVariableNthDefNodeInit(0)));
|
|
485
|
+
if (tools.O.isNone(maybeFirstDefNodeInit)) {
|
|
486
|
+
return false;
|
|
487
|
+
}
|
|
488
|
+
const firstDefNodeInit = maybeFirstDefNodeInit.value;
|
|
489
|
+
if (firstDefNodeInit.type !== ast.NodeType.ObjectExpression) {
|
|
490
|
+
return false;
|
|
491
|
+
}
|
|
492
|
+
if (seenProps.includes(name)) {
|
|
493
|
+
return false;
|
|
494
|
+
}
|
|
495
|
+
return tools.O.isSome(findPropInProperties(firstDefNodeInit.properties, context, [
|
|
496
|
+
...seenProps,
|
|
497
|
+
name
|
|
498
|
+
])(propName));
|
|
499
|
+
}
|
|
500
|
+
return false;
|
|
501
|
+
}));
|
|
502
|
+
};
|
|
503
|
+
}
|
|
504
|
+
/**
|
|
505
|
+
* @param attributes The attributes to search in
|
|
506
|
+
* @param context The rule context
|
|
507
|
+
* @returns A function that searches for a property in the given attributes
|
|
508
|
+
*/ function findPropInAttributes(attributes, context) {
|
|
509
|
+
/**
|
|
510
|
+
* Search for a property in the given attributes
|
|
511
|
+
* @param propName The name of the property to search for
|
|
512
|
+
* @returns The property if found
|
|
513
|
+
*/ return (propName)=>{
|
|
514
|
+
return tools.O.fromNullable(attributes.find((attr)=>{
|
|
515
|
+
return N(attr).when(ast.is(ast.NodeType.JSXAttribute), (attr)=>getPropName(attr) === propName).when(ast.is(ast.NodeType.JSXSpreadAttribute), (attr)=>{
|
|
516
|
+
if (!("argument" in attr && "name" in attr.argument)) {
|
|
517
|
+
return false;
|
|
518
|
+
}
|
|
519
|
+
const { name } = attr.argument;
|
|
520
|
+
const maybeFirstDefNodeInit = tools.F.pipe(ast.findVariableByNameUpToGlobal(name, context.getScope()), tools.O.flatMap(ast.getVariableNthDefNodeInit(0)));
|
|
521
|
+
if (tools.O.isNone(maybeFirstDefNodeInit)) {
|
|
522
|
+
return false;
|
|
523
|
+
}
|
|
524
|
+
if (!("properties" in maybeFirstDefNodeInit.value)) {
|
|
525
|
+
return false;
|
|
526
|
+
}
|
|
527
|
+
return tools.O.isSome(findPropInProperties(maybeFirstDefNodeInit.value.properties, context)(propName));
|
|
528
|
+
}).otherwise(tools.F.constFalse);
|
|
529
|
+
}));
|
|
530
|
+
};
|
|
531
|
+
}
|
|
532
|
+
|
|
533
|
+
/**
|
|
534
|
+
* Check if a node is a Literal or JSXText
|
|
535
|
+
* @param node The AST node to check
|
|
536
|
+
* @returns boolean `true` if the node is a Literal or JSXText
|
|
537
|
+
*/ const isLiteral = ast.isOneOf([
|
|
538
|
+
ast.NodeType.Literal,
|
|
539
|
+
ast.NodeType.JSXText
|
|
540
|
+
]);
|
|
541
|
+
/**
|
|
542
|
+
* Check if a Literal or JSXText node is whitespace
|
|
543
|
+
* @param node The AST node to check
|
|
544
|
+
* @returns boolean `true` if the node is whitespace
|
|
545
|
+
*/ function isWhiteSpace(node) {
|
|
546
|
+
return isString(node.value) && node.value.trim() === "";
|
|
547
|
+
}
|
|
548
|
+
/**
|
|
549
|
+
* Check if a Literal or JSXText node is a line break
|
|
550
|
+
* @param node The AST node to check
|
|
551
|
+
* @returns boolean
|
|
552
|
+
*/ function isLineBreak(node) {
|
|
553
|
+
return isLiteral(node) && isWhiteSpace(node) && ast.isMultiLine(node);
|
|
554
|
+
}
|
|
555
|
+
|
|
556
|
+
exports.defaultJSXValueCheckOptions = defaultJSXValueCheckOptions;
|
|
557
|
+
exports.elementType = elementType;
|
|
558
|
+
exports.findPropInAttributes = findPropInAttributes;
|
|
559
|
+
exports.findPropInProperties = findPropInProperties;
|
|
560
|
+
exports.getFragmentFromContext = getFragmentFromContext;
|
|
561
|
+
exports.getPragmaFromContext = getPragmaFromContext;
|
|
562
|
+
exports.getPropName = getPropName;
|
|
563
|
+
exports.getPropNameWithNamespace = getPropNameWithNamespace;
|
|
564
|
+
exports.hasAnyProp = hasAnyProp;
|
|
565
|
+
exports.hasChildren = hasChildren;
|
|
566
|
+
exports.hasEveryProp = hasEveryProp;
|
|
567
|
+
exports.hasProp = hasProp;
|
|
568
|
+
exports.hdlAnimation = hdlAnimation;
|
|
569
|
+
exports.hdlClipboard = hdlClipboard;
|
|
570
|
+
exports.hdlComposition = hdlComposition;
|
|
571
|
+
exports.hdlFocus = hdlFocus;
|
|
572
|
+
exports.hdlForm = hdlForm;
|
|
573
|
+
exports.hdlImage = hdlImage;
|
|
574
|
+
exports.hdlKeyboard = hdlKeyboard;
|
|
575
|
+
exports.hdlMedia = hdlMedia;
|
|
576
|
+
exports.hdlMouse = hdlMouse;
|
|
577
|
+
exports.hdlScroll = hdlScroll;
|
|
578
|
+
exports.hdlSelection = hdlSelection;
|
|
579
|
+
exports.hdlTouch = hdlTouch;
|
|
580
|
+
exports.hdlTransition = hdlTransition;
|
|
581
|
+
exports.hdlWheel = hdlWheel;
|
|
582
|
+
exports.isCallFromPragma = isCallFromPragma;
|
|
583
|
+
exports.isChildrenOfCreateElement = isChildrenOfCreateElement;
|
|
584
|
+
exports.isCloneElementCall = isCloneElementCall;
|
|
585
|
+
exports.isCreateElementCall = isCreateElementCall;
|
|
586
|
+
exports.isDestructuredFromPragma = isDestructuredFromPragma;
|
|
587
|
+
exports.isFunctionReturningJSXValue = isFunctionReturningJSXValue;
|
|
588
|
+
exports.isInsideCreateElementProps = isInsideCreateElementProps;
|
|
589
|
+
exports.isInsidePropValue = isInsidePropValue;
|
|
590
|
+
exports.isJSXValue = isJSXValue;
|
|
591
|
+
exports.isLineBreak = isLineBreak;
|
|
592
|
+
exports.isLiteral = isLiteral;
|
|
593
|
+
exports.isWhiteSpace = isWhiteSpace;
|
|
594
|
+
exports.traverseUpProp = traverseUpProp;
|