vite-plugin-solid-undestructure 0.1.1 → 0.2.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/README.md CHANGED
@@ -202,3 +202,54 @@ function TestComponent(_props) {
202
202
  ```bash
203
203
  bun test
204
204
  ```
205
+
206
+ ## ESLint Integration
207
+
208
+ Since `eslint-plugin-solid`'s `solid/reactivity` rule doesn't know about this plugin, it will flag destructured props as non-reactive. The bundled ESLint processor fixes this by teaching the rule about destructured props.
209
+
210
+ ### Setup
211
+
212
+ Install `eslint-plugin-solid`:
213
+
214
+ ```bash
215
+ bun add -D eslint-plugin-solid
216
+ ```
217
+
218
+ Add the processor to your ESLint config:
219
+
220
+ ```js
221
+ // eslint.config.js
222
+ import solid from 'eslint-plugin-solid'
223
+ import solidUndestructure from 'vite-plugin-solid-undestructure/eslint'
224
+
225
+ export default [
226
+ solidUndestructure.configs.recommended,
227
+ solid.configs['flat/typescript'],
228
+ {
229
+ rules: {
230
+ 'solid/no-destructure': 'off'
231
+ }
232
+ }
233
+ ]
234
+ ```
235
+
236
+ ### How it works
237
+
238
+ The processor transparently rewrites destructured props into `props.X` member expressions before the linter runs, so the existing `solid/reactivity` rule can correctly identify untracked reactive usages. Error messages are adjusted to reference the original destructured name.
239
+
240
+ ```tsx
241
+ // Without the processor, solid/reactivity ignores `size` (not recognized as reactive)
242
+ // With the processor, it correctly warns:
243
+ function ExampleComponent({ size }: { size: 'sm' | 'lg' }) {
244
+ const dimensions =
245
+ // ↓ The reactive variable 'size' should be used within JSX, a tracked scope
246
+ // (like createEffect), or inside an event handler. [solid/reactivity]
247
+ size === 'sm' ? { width: 4, height: 4 } : { width: 8, height: 8 }
248
+
249
+ // Correct usages that don't cause warnings:
250
+ // const dimensions = () => size === 'sm' ? ...
251
+ // const dimensions = createMemo(() => size === 'sm' ? ...)
252
+
253
+ return <img src="..." alt="..." {...dimensions()} />
254
+ }
255
+ ```
@@ -0,0 +1,28 @@
1
+ declare const plugin: {
2
+ meta: {
3
+ name: string;
4
+ version: string;
5
+ };
6
+ processors: {
7
+ 'solid-undestructure': {
8
+ meta: {
9
+ name: string;
10
+ version: string;
11
+ };
12
+ preprocess(text: string, filename: string): string[];
13
+ postprocess(messages: {
14
+ ruleId: string | null;
15
+ message: string;
16
+ }[][], filename: string): {
17
+ ruleId: string | null;
18
+ message: string;
19
+ }[];
20
+ supportsAutofix: false;
21
+ };
22
+ };
23
+ configs: Record<string, {
24
+ plugins: Record<string, unknown>;
25
+ processor: string;
26
+ }>;
27
+ };
28
+ export default plugin;
@@ -0,0 +1 @@
1
+ import{checkIfComponent as E}from"@/modules/component-detector";import X from"@babel/generator";import{parse as T}from"@babel/parser";import H from"@babel/traverse";import*as u from"@babel/types";var v=H.default??H,C=X.default??X;function L(q){if(!/\(\s*\{/.test(q))return null;let z=new Map,B=!1;try{let Z=T(q,{sourceType:"module",plugins:["typescript","jsx"]});if(v(Z,{Function(G){let _=G.node.params;if(_.length!==1)return;let U=_[0];if(!u.isObjectPattern(U))return;if(!E(G))return;I(G,U,z),B=!0}}),!B)return null;return{code:C(Z,{retainLines:!0,compact:!1}).code,propMappings:z}}catch(Q){return console.warn("Failed to transform:",Q),null}}function I(q,z,B){let Q=u.identifier("props");if(z.typeAnnotation)Q.typeAnnotation=z.typeAnnotation;let Z=[],W=new Map,G=new Map;function _($,V=[]){for(let R of $.properties){if(u.isRestElement(R))continue;if(!u.isObjectProperty(R))continue;let J=null;if(u.isIdentifier(R.key))J=R.key.name;else if(u.isStringLiteral(R.key))J=R.key.value;if(!J)continue;let Y=[...V,J];if(u.isObjectPattern(R.value)){_(R.value,Y);continue}let w=null;if(u.isIdentifier(R.value))w=R.value.name;else if(u.isAssignmentPattern(R.value)&&u.isIdentifier(R.value.left))w=R.value.left.name;if(!w)continue;if(V.length===0)Z.push(w),W.set(w,J),B.set(w,J);else G.set(w,Y),B.set(w,Y.join("."))}}_(z),q.node.params[0]=Q;let U=q.get("body");if(Array.isArray(U))return;let D={Identifier($){let V=$.parent;if(u.isMemberExpression(V)&&V.property===$.node&&!V.computed||u.isObjectProperty(V)&&V.key===$.node&&!V.computed)return;if($.isBindingIdentifier())return;let R=$,J=R.node.name,Y=G.get(J);if(Y){let w=u.memberExpression(u.identifier("props"),u.identifier(Y[0]));for(let F=1;F<Y.length;F++)w=u.memberExpression(w,u.identifier(Y[F]));R.replaceWith(w)}else if(Z.includes(J)){let w=W.get(J)??J;R.replaceWith(u.memberExpression(u.identifier("props"),u.identifier(w)))}}};U.traverse(D)}var A=new Map,O={meta:{name:"solid-undestructure",version:"0.1.1"},preprocess(q,z){if(!/\.(tsx?|jsx?)$/.test(z))return[q];let B=L(q);if(!B)return A.delete(z),[q];return A.set(z,B),[B.code]},postprocess(q,z){let B=A.get(z);if(!B)return q[0];A.delete(z);let{propMappings:Q}=B;if(Q.size===0)return q[0];let Z=new Map;for(let[W,G]of Q)Z.set(G,W);return q[0].map((W)=>{let{message:G}=W;for(let[_,U]of Q){let D=U.includes(".")?`props.${U}`:`props.${U}`;G=G.replaceAll(`'${D}'`,`'${_}'`)}return{...W,message:G}})},supportsAutofix:!1};var S={meta:{name:"eslint-plugin-solid-undestructure",version:"0.1.1"},processors:{"solid-undestructure":O},configs:{}};S.configs.recommended={plugins:{"solid-undestructure":S},processor:"solid-undestructure/solid-undestructure"};var i=S;export{i as default};
@@ -0,0 +1,15 @@
1
+ export declare const processor: {
2
+ meta: {
3
+ name: string;
4
+ version: string;
5
+ };
6
+ preprocess(text: string, filename: string): string[];
7
+ postprocess(messages: {
8
+ ruleId: string | null;
9
+ message: string;
10
+ }[][], filename: string): {
11
+ ruleId: string | null;
12
+ message: string;
13
+ }[];
14
+ supportsAutofix: false;
15
+ };
@@ -0,0 +1,11 @@
1
+ export type TransformResult = {
2
+ code: string;
3
+ /** Maps original local name → prop key (e.g. "localName" → "propKey") */
4
+ propMappings: Map<string, string>;
5
+ };
6
+ /**
7
+ * Transforms destructured component props into `props.X` member expressions
8
+ * for linting purposes. This is a minimal transformation — no mergeProps/splitProps/imports
9
+ * are added, only the patterns the eslint-plugin-solid reactivity rule needs to see.
10
+ */
11
+ export declare function transformForLinting(code: string): TransformResult | null;
package/dist/index.js CHANGED
@@ -1 +1 @@
1
- import S from"@babel/generator";import{parse as I}from"@babel/parser";import k from"@babel/traverse";import*as b from"@babel/types";import*as H from"@babel/types";function v(Q){if(H.isArrowFunctionExpression(Q.node)&&!H.isBlockStatement(Q.node.body))return H.isJSXElement(Q.node.body)||H.isJSXFragment(Q.node.body);let $=!1;return Q.traverse({ReturnStatement(W){if(W.getFunctionParent()?.node!==Q.node)return;let L=W.node.argument;if(!L)return;if(H.isJSXElement(L)||H.isJSXFragment(L))$=!0,W.stop();if(H.isConditionalExpression(L)){if(H.isJSXElement(L.consequent)||H.isJSXFragment(L.consequent)||H.isJSXElement(L.alternate)||H.isJSXFragment(L.alternate))$=!0,W.stop()}if(H.isLogicalExpression(L)){if(H.isJSXElement(L.left)||H.isJSXFragment(L.left)||H.isJSXElement(L.right)||H.isJSXFragment(L.right))$=!0,W.stop()}if(H.isParenthesizedExpression(L)){let U=L.expression;if(H.isJSXElement(U)||H.isJSXFragment(U))$=!0,W.stop()}}}),$}import*as q from"@babel/types";import*as _ from"@babel/types";function T(Q,$,W){let L=[];if($)L.push("mergeProps");if(W)L.push("splitProps");if(L.length===0)return;let U=Q.node.body,D=new Set;for(let Z of U){if(!_.isImportDeclaration(Z)||Z.source.value!=="solid-js")continue;for(let M of Z.specifiers)if(_.isImportSpecifier(M)&&_.isIdentifier(M.imported))D.add(M.imported.name)}let A=L.filter((Z)=>!D.has(Z));if(A.length===0)return;let R=-1;for(let Z=0;Z<U.length;Z++)if(_.isImportDeclaration(U[Z]))R=Z;let V=A.map((Z)=>_.importDeclaration([_.importSpecifier(_.identifier(Z),_.identifier(Z))],_.stringLiteral("solid-js")));if(R>=0)U.splice(R+1,0,...V);else U.unshift(...V)}function N(Q,$){let W=Q.scope.generateUidIdentifier("props"),L=$.properties,U=[],D=[],A=new Map,R=new Map,V={},Z=!1,M=null;function K(B,F=[]){B.properties.forEach((G)=>{if(q.isObjectProperty(G)){let Y=null;if(q.isIdentifier(G.key))Y=G.key.name;else if(q.isStringLiteral(G.key))Y=G.key.value;if(!Y)return;let C=[...F,Y];if(q.isObjectPattern(G.value)){if(F.length===0)U.push(Y);K(G.value,C)}else{let z=null;if(q.isIdentifier(G.value))z=G.value.name;else if(q.isAssignmentPattern(G.value)){if(q.isIdentifier(G.value.left)){if(z=G.value.left.name,q.isExpression(G.value.right)){if(F.length===0)V[Y]=G.value.right}}}if(F.length===0)if(U.push(Y),z)D.push(z),A.set(z,Y);else D.push(Y),A.set(Y,Y);else if(z)R.set(z,C);else R.set(Y,C)}}})}for(let B of L)if(q.isRestElement(B)){if(Z=!0,q.isIdentifier(B.argument))M=B.argument}else if(q.isObjectProperty(B)){let F=null,G=null;if(q.isIdentifier(B.key))F=B.key.name;else if(q.isStringLiteral(B.key))F=B.key.value;if(!F)continue;if(q.isObjectPattern(B.value)){U.push(F),K(B.value,[F]);continue}if(q.isIdentifier(B.value))G=B.value.name;else if(q.isAssignmentPattern(B.value)&&q.isIdentifier(B.value.left))G=B.value.left.name;if(U.push(F),G)D.push(G),A.set(G,F);else D.push(F),A.set(F,F);if(q.isAssignmentPattern(B.value)&&q.isExpression(B.value.right))V[F]=B.value.right}Q.node.params[0]=W;let j=[],X=Object.keys(V).length>0,E=U.length>0||Z,O;if(X){let B=q.objectExpression(Object.entries(V).map(([F,G])=>q.objectProperty(q.identifier(F),G)));if(E)O=Q.scope.generateUidIdentifier("merged"),j.push(q.variableDeclaration("const",[q.variableDeclarator(O,q.callExpression(q.identifier("mergeProps"),[B,W]))]));else O=Q.scope.generateUidIdentifier("merged"),j.push(q.variableDeclaration("const",[q.variableDeclarator(O,q.callExpression(q.identifier("mergeProps"),[B,W]))]));if(E&&M){let F=q.arrayPattern([null,M]);j.push(q.variableDeclaration("const",[q.variableDeclarator(F,q.callExpression(q.identifier("splitProps"),[O,q.arrayExpression(U.map((G)=>q.stringLiteral(G)))]))]))}}else if(E){if(O=W,M){let B=q.arrayPattern([null,M]);j.push(q.variableDeclaration("const",[q.variableDeclarator(B,q.callExpression(q.identifier("splitProps"),[W,q.arrayExpression(U.map((F)=>q.stringLiteral(F)))]))]))}}else O=W;let w=Q.get("body");if(Array.isArray(w))return;let f={Identifier(B){let F=B.parent;if(q.isMemberExpression(F)&&F.property===B.node&&!F.computed||q.isObjectProperty(F)&&F.key===B.node&&!F.computed)return;let G=B,Y=G.node.name;if(B.isBindingIdentifier())return;let C=R.get(Y);if(C){let z=q.memberExpression(q.identifier(O.name),q.identifier(C[0]));for(let J=1;J<C.length;J++)z=q.memberExpression(z,q.identifier(C[J]));G.replaceWith(z)}else if(D.includes(Y)){let z=A.get(Y)??Y;G.replaceWith(q.memberExpression(q.identifier(O.name),q.identifier(z)))}}};if(w.traverse(f),q.isBlockStatement(Q.node.body))Q.node.body.body.unshift(...j);else if(q.isExpression(Q.node.body)){let B=q.returnStatement(Q.node.body);Q.node.body=q.blockStatement([...j,B])}let x=Q.findParent((B)=>B.isProgram());if(x)T(x,X,E)}var y=k.default??k,c=S.default??S,o=()=>({name:"solid-undestructure",enforce:"pre",transform(Q,$){if(!/\.(tsx?|jsx?)$/.test($))return null;if($.includes("node_modules"))return null;if(!/\(\s*\{/.test(Q))return null;try{let L=I(Q,{sourceType:"module",plugins:["typescript","jsx"]});y(L,{Function(D){let A=D.node.params;if(A.length!==1)return;let R=A[0];if(!b.isObjectPattern(R))return;if(!v(D))return;N(D,R)}});let U=c(L,{retainLines:!0,compact:!1});return{code:U.code,map:U.map}}catch(W){return console.warn(`Failed to transform ${$}:`,W),null}}});export{o as default};
1
+ import S from"@babel/generator";import{parse as f}from"@babel/parser";import k from"@babel/traverse";import*as I from"@babel/types";import*as H from"@babel/types";function v(Q){if(H.isArrowFunctionExpression(Q.node)&&!H.isBlockStatement(Q.node.body))return H.isJSXElement(Q.node.body)||H.isJSXFragment(Q.node.body);let $=!1;return Q.traverse({ReturnStatement(W){if(W.getFunctionParent()?.node!==Q.node)return;let L=W.node.argument;if(!L)return;if(H.isJSXElement(L)||H.isJSXFragment(L))$=!0,W.stop();if(H.isConditionalExpression(L)){if(H.isJSXElement(L.consequent)||H.isJSXFragment(L.consequent)||H.isJSXElement(L.alternate)||H.isJSXFragment(L.alternate))$=!0,W.stop()}if(H.isLogicalExpression(L)){if(H.isJSXElement(L.left)||H.isJSXFragment(L.left)||H.isJSXElement(L.right)||H.isJSXFragment(L.right))$=!0,W.stop()}if(H.isParenthesizedExpression(L)){let U=L.expression;if(H.isJSXElement(U)||H.isJSXFragment(U))$=!0,W.stop()}}}),$}import*as q from"@babel/types";import*as _ from"@babel/types";function T(Q,$,W){let L=[];if($)L.push("mergeProps");if(W)L.push("splitProps");if(L.length===0)return;let U=Q.node.body,A=new Set;for(let Z of U){if(!_.isImportDeclaration(Z)||Z.source.value!=="solid-js")continue;for(let O of Z.specifiers)if(_.isImportSpecifier(O)&&_.isIdentifier(O.imported))A.add(O.imported.name)}let D=L.filter((Z)=>!A.has(Z));if(D.length===0)return;let R=-1;for(let Z=0;Z<U.length;Z++)if(_.isImportDeclaration(U[Z]))R=Z;let M=D.map((Z)=>_.importDeclaration([_.importSpecifier(_.identifier(Z),_.identifier(Z))],_.stringLiteral("solid-js")));if(R>=0)U.splice(R+1,0,...M);else U.unshift(...M)}function N(Q,$){let W=Q.scope.generateUidIdentifier("props"),L=$.properties,U=[],A=[],D=new Map,R=new Map,M={},Z=!1,O=null;function K(B,F=[]){B.properties.forEach((G)=>{if(q.isObjectProperty(G)){let Y=null;if(q.isIdentifier(G.key))Y=G.key.name;else if(q.isStringLiteral(G.key))Y=G.key.value;if(!Y)return;let C=[...F,Y];if(q.isObjectPattern(G.value)){if(F.length===0)U.push(Y);K(G.value,C)}else{let z=null;if(q.isIdentifier(G.value))z=G.value.name;else if(q.isAssignmentPattern(G.value)){if(q.isIdentifier(G.value.left)){if(z=G.value.left.name,q.isExpression(G.value.right)){if(F.length===0)M[Y]=G.value.right}}}if(F.length===0)if(U.push(Y),z)A.push(z),D.set(z,Y);else A.push(Y),D.set(Y,Y);else if(z)R.set(z,C);else R.set(Y,C)}}})}for(let B of L)if(q.isRestElement(B)){if(Z=!0,q.isIdentifier(B.argument))O=B.argument}else if(q.isObjectProperty(B)){let F=null,G=null;if(q.isIdentifier(B.key))F=B.key.name;else if(q.isStringLiteral(B.key))F=B.key.value;if(!F)continue;if(q.isObjectPattern(B.value)){U.push(F),K(B.value,[F]);continue}if(q.isIdentifier(B.value))G=B.value.name;else if(q.isAssignmentPattern(B.value)&&q.isIdentifier(B.value.left))G=B.value.left.name;if(U.push(F),G)A.push(G),D.set(G,F);else A.push(F),D.set(F,F);if(q.isAssignmentPattern(B.value)&&q.isExpression(B.value.right))M[F]=B.value.right}Q.node.params[0]=W;let j=[],X=Object.keys(M).length>0,E=U.length>0||Z,V;if(X){let B=q.objectExpression(Object.entries(M).map(([F,G])=>q.objectProperty(q.identifier(F),G)));if(E)V=Q.scope.generateUidIdentifier("merged"),j.push(q.variableDeclaration("const",[q.variableDeclarator(V,q.callExpression(q.identifier("mergeProps"),[B,W]))]));else V=Q.scope.generateUidIdentifier("merged"),j.push(q.variableDeclaration("const",[q.variableDeclarator(V,q.callExpression(q.identifier("mergeProps"),[B,W]))]));if(E&&O){let F=q.arrayPattern([null,O]);j.push(q.variableDeclaration("const",[q.variableDeclarator(F,q.callExpression(q.identifier("splitProps"),[V,q.arrayExpression(U.map((G)=>q.stringLiteral(G)))]))]))}}else if(E){if(V=W,O){let B=q.arrayPattern([null,O]);j.push(q.variableDeclaration("const",[q.variableDeclarator(B,q.callExpression(q.identifier("splitProps"),[W,q.arrayExpression(U.map((F)=>q.stringLiteral(F)))]))]))}}else V=W;let w=Q.get("body");if(Array.isArray(w))return;let b={Identifier(B){let F=B.parent;if(q.isMemberExpression(F)&&F.property===B.node&&!F.computed||q.isObjectProperty(F)&&F.key===B.node&&!F.computed)return;let G=B,Y=G.node.name;if(B.isBindingIdentifier())return;let C=R.get(Y);if(C){let z=q.memberExpression(q.identifier(V.name),q.identifier(C[0]));for(let J=1;J<C.length;J++)z=q.memberExpression(z,q.identifier(C[J]));G.replaceWith(z)}else if(A.includes(Y)){let z=D.get(Y)??Y;G.replaceWith(q.memberExpression(q.identifier(V.name),q.identifier(z)))}}};if(w.traverse(b),q.isBlockStatement(Q.node.body))Q.node.body.body.unshift(...j);else if(q.isExpression(Q.node.body)){let B=q.returnStatement(Q.node.body);Q.node.body=q.blockStatement([...j,B])}let x=Q.findParent((B)=>B.isProgram());if(x)T(x,X,E)}var g=k.default??k,y=S.default??S,o=()=>({name:"solid-undestructure",enforce:"pre",transform(Q,$){if(!/\.(tsx?|jsx?)$/.test($))return null;if($.includes("node_modules"))return null;if(!/\(\s*\{/.test(Q))return null;let W=!1;try{let U=f(Q,{sourceType:"module",plugins:["typescript","jsx"]});if(g(U,{Function(D){let R=D.node.params;if(R.length!==1)return;let M=R[0];if(!I.isObjectPattern(M))return;if(!v(D))return;N(D,M),W=!0}}),!W)return null;let A=y(U,{retainLines:!0,compact:!1});return{code:A.code,map:A.map}}catch(L){return console.warn(`Failed to transform ${$}:`,L),null}}});export{o as default};
package/package.json CHANGED
@@ -1,11 +1,21 @@
1
1
  {
2
2
  "name": "vite-plugin-solid-undestructure",
3
- "version": "0.1.1",
3
+ "version": "0.2.0",
4
4
  "description": "Automatically transforms props destructuring in SolidJS components",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
7
7
  "module": "./dist/index.js",
8
8
  "types": "./dist/index.d.ts",
9
+ "exports": {
10
+ ".": {
11
+ "import": "./dist/index.js",
12
+ "types": "./dist/index.d.ts"
13
+ },
14
+ "./eslint": {
15
+ "import": "./dist/eslint/index.js",
16
+ "types": "./dist/eslint/index.d.ts"
17
+ }
18
+ },
9
19
  "files": [
10
20
  "dist"
11
21
  ],
@@ -21,12 +31,13 @@
21
31
  ],
22
32
  "license": "MIT",
23
33
  "scripts": {
24
- "build": "bun build src --minify --packages=external --outfile=dist/index.js && tsc --declaration --emitDeclarationOnly --outDir dist",
34
+ "build": "bun build src/index.ts --minify --packages=external --outfile=dist/index.js && bun build src/eslint/index.ts --minify --packages=external --outfile=dist/eslint/index.js && tsc --declaration --emitDeclarationOnly --outDir dist",
25
35
  "type": "tsc --noEmit",
26
36
  "lint": "eslint --cache src",
27
37
  "format": "prettier --write *",
28
38
  "test": "bun test tests",
29
- "check:format": "prettier --check *"
39
+ "check:format": "prettier --check *",
40
+ "pre": "bun type && bun lint && bun format"
30
41
  },
31
42
  "dependencies": {
32
43
  "@babel/generator": "^7.29.1",
@@ -47,6 +58,16 @@
47
58
  "typescript-eslint": "^8.56.1"
48
59
  },
49
60
  "peerDependencies": {
61
+ "eslint": "^9.0.0",
62
+ "eslint-plugin-solid": "^0.14.5",
50
63
  "vite": "^7.3.1"
64
+ },
65
+ "peerDependenciesMeta": {
66
+ "eslint": {
67
+ "optional": true
68
+ },
69
+ "eslint-plugin-solid": {
70
+ "optional": true
71
+ }
51
72
  }
52
73
  }