From 283c575c11ea72a2f287cd603669edc05d3ccc55 Mon Sep 17 00:00:00 2001 From: Francisco Lopez Date: Mon, 20 Nov 2023 22:40:09 -0500 Subject: [PATCH 1/4] working on file selector and ast --- extension.js | 6 +++++- package-lock.json | 7 ++++--- package.json | 8 +++++++- src/panel.js | 17 ++++++++++++++++- 4 files changed, 32 insertions(+), 6 deletions(-) diff --git a/extension.js b/extension.js index d061161..3b13c1b 100644 --- a/extension.js +++ b/extension.js @@ -1,5 +1,5 @@ const vscode = require('vscode'); -const {createPanel} = require('./src/panel'); +const {createPanel, grabFile} = require('./src/panel'); // This method is called when your extension is activated // Your extension is activated the very first time the command is executed @@ -16,6 +16,10 @@ function activate(context) { createPanel(context); }); + let file = vscode.commands.registerCommand('myExtension.pickFile', () => { + grabFile(context); + }) + context.subscriptions.push(disposable, result); } diff --git a/package-lock.json b/package-lock.json index 8639791..9d99686 100644 --- a/package-lock.json +++ b/package-lock.json @@ -9,6 +9,7 @@ "version": "0.0.1", "dependencies": { "@babel/core": "^7.23.3", + "@babel/parser": "^7.23.4", "@babel/preset-env": "^7.23.3", "@babel/preset-react": "^7.23.3", "babel": "^6.23.0", @@ -613,9 +614,9 @@ } }, "node_modules/@babel/parser": { - "version": "7.23.3", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.23.3.tgz", - "integrity": "sha512-uVsWNvlVsIninV2prNz/3lHCb+5CJ+e+IUBfbjToAHODtfGYLfCFuY4AU7TskI+dAKk+njsPiBjq1gKTvZOBaw==", + "version": "7.23.4", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.23.4.tgz", + "integrity": "sha512-vf3Xna6UEprW+7t6EtOmFpHNAuxw3xqPZghy+brsnusscJRW5BMUzzHZc5ICjULee81WeUV2jjakG09MDglJXQ==", "bin": { "parser": "bin/babel-parser.js" }, diff --git a/package.json b/package.json index eb7aebb..1f82b06 100644 --- a/package.json +++ b/package.json @@ -23,6 +23,11 @@ "command": "myExtension.showPanel", "title": "Show Panel", "category": "React Labyrinth" + }, + { + "command": "myExtension.pickFile", + "title": "Pick File", + "category": "React Labyrinth" } ], "viewsContainers": { @@ -49,7 +54,7 @@ "viewsWelcome": [ { "view": "metrics-file", - "contents": "View tree to see where improvements can be made!\n[View Tree](command:myExtension.showPanel)\n" + "contents": "View tree to see where improvements can be made!\n[View Tree](command:myExtension.pickFile)\n" } ] }, @@ -72,6 +77,7 @@ }, "dependencies": { "@babel/core": "^7.23.3", + "@babel/parser": "^7.23.4", "@babel/preset-env": "^7.23.3", "@babel/preset-react": "^7.23.3", "babel": "^6.23.0", diff --git a/src/panel.js b/src/panel.js index c17f994..4c79269 100644 --- a/src/panel.js +++ b/src/panel.js @@ -1,4 +1,7 @@ const vscode = require('vscode'); +const fs = require('fs'); +const path = require('path'); +const babel = require('@babel/parser'); function createPanel(context) { // utilize method on vscode.window object to create webview @@ -45,4 +48,16 @@ function createWebviewHTML(URI) { ) } -module.exports = {createPanel}; \ No newline at end of file +let ast; +async function grabFile() { + + const file = await vscode.window.showOpenDialog({canSelectFolders: true, canSelectFiles: true, canSelectMany: true}) + .then(data => { + fs.readFile(data[0].path, 'utf-8', (err, data) => { + console.log(typeof data) + console.log(data) + }) + }) +} + +module.exports = {createPanel, grabFile}; \ No newline at end of file From 0e360a0d2ecabcee941bba3d88fa6cf7efa87ff8 Mon Sep 17 00:00:00 2001 From: Francisco Lopez Date: Mon, 20 Nov 2023 22:46:30 -0500 Subject: [PATCH 2/4] added console log to display AST --- src/panel.js | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/src/panel.js b/src/panel.js index 4c79269..95edcd4 100644 --- a/src/panel.js +++ b/src/panel.js @@ -52,11 +52,16 @@ let ast; async function grabFile() { const file = await vscode.window.showOpenDialog({canSelectFolders: true, canSelectFiles: true, canSelectMany: true}) - .then(data => { - fs.readFile(data[0].path, 'utf-8', (err, data) => { - console.log(typeof data) - console.log(data) - }) + .then(async (data) => { + ast = await babel.parse( + fs.readFileSync(path.resolve(data[0].path), 'utf-8'), + { + sourceType: 'module', + tokens: true, + plugins: ['jsx', 'typescript'], + } + ); + console.log(ast) }) } From 17d6de91663890eae717e0c4f863583f6f3f0d21 Mon Sep 17 00:00:00 2001 From: ash-t-luu Date: Fri, 24 Nov 2023 16:23:30 -0800 Subject: [PATCH 3/4] submitting new additions of traverseAST with client conditional functionality --- extension.js | 9 +-- package-lock.json | 16 +++--- package.json | 2 +- src/panel.js | 22 +------- src/parser.js | 136 ++++++++++++++++++++++++++++++++++++++++++++++ 5 files changed, 151 insertions(+), 34 deletions(-) create mode 100644 src/parser.js diff --git a/extension.js b/extension.js index 3b13c1b..dd49863 100644 --- a/extension.js +++ b/extension.js @@ -1,5 +1,6 @@ const vscode = require('vscode'); -const {createPanel, grabFile} = require('./src/panel'); +const { createPanel } = require('./src/panel'); +const { grabFile } = require('./src/parser'); // This method is called when your extension is activated // Your extension is activated the very first time the command is executed @@ -16,9 +17,9 @@ function activate(context) { createPanel(context); }); - let file = vscode.commands.registerCommand('myExtension.pickFile', () => { - grabFile(context); - }) + vscode.commands.registerCommand('myExtension.pickFile', () => { + grabFile(); + }); context.subscriptions.push(disposable, result); } diff --git a/package-lock.json b/package-lock.json index 9d99686..bf2ac2a 100644 --- a/package-lock.json +++ b/package-lock.json @@ -27,7 +27,7 @@ "@types/node": "18.x", "@types/vscode": "^1.84.0", "@vscode/test-electron": "^2.3.6", - "eslint": "^8.52.0", + "eslint": "^8.54.0", "glob": "^10.3.10", "mocha": "^10.2.0", "typescript": "^5.2.2", @@ -1964,9 +1964,9 @@ } }, "node_modules/@eslint/js": { - "version": "8.53.0", - "resolved": "https://registry.npmjs.org/@eslint/js/-/js-8.53.0.tgz", - "integrity": "sha512-Kn7K8dx/5U6+cT1yEhpX1w4PCSg0M+XyRILPgvwcEBjerFWCwQj5sbr3/VmxqV0JGHCBCzyd6LxypEuehypY1w==", + "version": "8.54.0", + "resolved": "https://registry.npmjs.org/@eslint/js/-/js-8.54.0.tgz", + "integrity": "sha512-ut5V+D+fOoWPgGGNj83GGjnntO39xDy6DWxO0wb7Jp3DcMX0TfIqdzHF85VTQkerdyGmuuMD9AKAo5KiNlf/AQ==", "dev": true, "engines": { "node": "^12.22.0 || ^14.17.0 || >=16.0.0" @@ -3583,15 +3583,15 @@ } }, "node_modules/eslint": { - "version": "8.53.0", - "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.53.0.tgz", - "integrity": "sha512-N4VuiPjXDUa4xVeV/GC/RV3hQW9Nw+Y463lkWaKKXKYMvmRiRDAtfpuPFLN+E1/6ZhyR8J2ig+eVREnYgUsiag==", + "version": "8.54.0", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.54.0.tgz", + "integrity": "sha512-NY0DfAkM8BIZDVl6PgSa1ttZbx3xHgJzSNJKYcQglem6CppHyMhRIQkBVSSMaSRnLhig3jsDbEzOjwCVt4AmmA==", "dev": true, "dependencies": { "@eslint-community/eslint-utils": "^4.2.0", "@eslint-community/regexpp": "^4.6.1", "@eslint/eslintrc": "^2.1.3", - "@eslint/js": "8.53.0", + "@eslint/js": "8.54.0", "@humanwhocodes/config-array": "^0.11.13", "@humanwhocodes/module-importer": "^1.0.1", "@nodelib/fs.walk": "^1.2.8", diff --git a/package.json b/package.json index 1f82b06..3af3309 100644 --- a/package.json +++ b/package.json @@ -69,7 +69,7 @@ "@types/node": "18.x", "@types/vscode": "^1.84.0", "@vscode/test-electron": "^2.3.6", - "eslint": "^8.52.0", + "eslint": "^8.54.0", "glob": "^10.3.10", "mocha": "^10.2.0", "typescript": "^5.2.2", diff --git a/src/panel.js b/src/panel.js index 95edcd4..c17f994 100644 --- a/src/panel.js +++ b/src/panel.js @@ -1,7 +1,4 @@ const vscode = require('vscode'); -const fs = require('fs'); -const path = require('path'); -const babel = require('@babel/parser'); function createPanel(context) { // utilize method on vscode.window object to create webview @@ -48,21 +45,4 @@ function createWebviewHTML(URI) { ) } -let ast; -async function grabFile() { - - const file = await vscode.window.showOpenDialog({canSelectFolders: true, canSelectFiles: true, canSelectMany: true}) - .then(async (data) => { - ast = await babel.parse( - fs.readFileSync(path.resolve(data[0].path), 'utf-8'), - { - sourceType: 'module', - tokens: true, - plugins: ['jsx', 'typescript'], - } - ); - console.log(ast) - }) -} - -module.exports = {createPanel, grabFile}; \ No newline at end of file +module.exports = {createPanel}; \ No newline at end of file diff --git a/src/parser.js b/src/parser.js new file mode 100644 index 0000000..e14783d --- /dev/null +++ b/src/parser.js @@ -0,0 +1,136 @@ +const vscode = require('vscode'); +const fs = require('fs'); +const path = require('path'); +const babel = require('@babel/parser'); + +let ast; +async function grabFile() { + try { + const file = await vscode.window.showOpenDialog({ canSelectFolders: true, canSelectFiles: true, canSelectMany: true }); + console.log('file path', file[0].path); + const name = path.basename(file[0].path); + console.log('name of file', name); + ast = await babel.parse( + fs.readFileSync(path.resolve(file[0].path), 'utf-8'), + { + sourceType: 'module', + tokens: true, + plugins: ['jsx', 'typescript'], + }); + // console.log('ast', ast); + const result = await traverseAST(ast); + console.log('res', result); + } catch (error) { + console.error(`Error processing file: ${error}`) + } +} + +// create a set to reduce redundancy in console logs during recursive call +const processedNodes = new Set(); + +// traverse the ast nodes, passing in node +async function traverseAST(node) { + // identify which are jsx elements type and then extract info about them (like the component name) and store it in var + if (node.type === 'JSXElement' && !processedNodes.has(node)) { + processedNodes.add(node); + // console.log('JSX Node', node); + + // im guessing that jsx elements will never contain the use client or hook declaration, so i wouldnt need to call the functions here + + // property on node to obtain component name (could be tag or component name) + const elementName = node.openingElement.name.name; + // console.log('JSX Name', elementName); + + if (node.children) { + // if node children exist, then recursively call the child nodes with this func + for (const child of node.children) { + await traverseAST(child); + } + } + } else if (!processedNodes.has(node)) { + processedNodes.add(node); + + // call the function to determine if it is a client component and store it in var + const isClientComp = await checkForClientString(node); + const isReactHook = await checkReactHooks(node); + + // recursively iterate through the other non-jsx types if the jsx node children doesnt exist + for (const key in node) { + if (node[key] && typeof node[key] === 'object' && key !== 'tokens') { + await traverseAST(node[key]); + } + } + } + return processedNodes; +} + +// function to determine server or client component (can look for 'use client' and 'hooks') + +// also might want to consider functionality for child components of the current node to be classifed as client component (except for server clients rendered tree) + +function checkForClientString(node) { + if (node.type === 'Directive') { + console.log('node', node); + + // access the value property of the Directive node + console.log('Directive Value:', node.value); + + // check if the node.value is a 'DirectiveLiteral' node + if (node.value && node.value.type === 'DirectiveLiteral') { + + // check the value to see if it is 'use client' + if (typeof node.value.value === 'string' && node.value.value.trim() === 'use client') { + // access the value property of the 'DirectiveLiteral' node + console.log('DirectiveLiteral Value:', node.value.value); + + // might need to do something else here to make it known as client type + console.log(`this node above has 'use client': `, true); + return true; + } + } + } + return false; +} + +function checkReactHooks(node) { + // for just the mvp, look up for the FIRST client component and make every child as a client component + + // function to determine if component uses react hooks (this only checks if its BEING CALLED IN COMPONENT, not IMPORTED) + // console.log('node', node); + if (node.type === 'CallExpression') { + console.log('nodeCall', node) + if (node.callee && node.callee.name) { + if (node.callee.name.startsWith('use')) { + // if the node.type is CallExpression (dealing with function or method call) (callee is prop on callexpression - an identifier), return true + console.log('node.callee', node.callee); + console.log('Node with Hook', node.callee.name); + console.log(`this node above uses hooks: `, true); + return true; + } + } + } + + // function to determine if hooks are being IMPORTED + // if (node.type === 'ImportDeclaration') { + // console.log('node import', node); + // if (node.specifiers) { + // // filter through the array to see which ones uses hooks + // const clientNodes = node.specifiers.filter((nodeImport) => { + // return nodeImport.type === 'ImportSpecifier' && nodeImport.imported.name.startsWith('use'); + // }); + // // mapped over to console log the name of hook + // clientNodes.map((nodeImport) => console.log('Names of Hooks', nodeImport.imported.name)); + // console.log(clientNodes); + // // we'll wanna change this to use it somehow + // return clientNodes; + // } + return false; +} + +// function to determine if the client component imports server components or call server hooks/utils, if it does, then return 'is not valid client comp' + +// function to determine if the component is server + +// render component tree using react flow, passing in node and recursvely call on child nodes + +module.exports = { grabFile }; \ No newline at end of file From 4d35b74610e90d0eb3b8693b9a495e4a07fd6d11 Mon Sep 17 00:00:00 2001 From: Francisco Lopez Date: Sat, 25 Nov 2023 13:22:30 -0500 Subject: [PATCH 4/4] changed parser to look for folder import statements --- src/parser.js | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/src/parser.js b/src/parser.js index e14783d..09c02e0 100644 --- a/src/parser.js +++ b/src/parser.js @@ -20,6 +20,8 @@ async function grabFile() { // console.log('ast', ast); const result = await traverseAST(ast); console.log('res', result); + // const tokens = ast.tokens; + // console.log('tokens are:', tokens); } catch (error) { console.error(`Error processing file: ${error}`) } @@ -31,15 +33,16 @@ const processedNodes = new Set(); // traverse the ast nodes, passing in node async function traverseAST(node) { // identify which are jsx elements type and then extract info about them (like the component name) and store it in var - if (node.type === 'JSXElement' && !processedNodes.has(node)) { + if (node.type === 'ImportDeclaration' && !processedNodes.has(node)) { processedNodes.add(node); // console.log('JSX Node', node); // im guessing that jsx elements will never contain the use client or hook declaration, so i wouldnt need to call the functions here // property on node to obtain component name (could be tag or component name) - const elementName = node.openingElement.name.name; - // console.log('JSX Name', elementName); + const elementName = node.source.value; + if(elementName.startsWith('./') || elementName.startsWith('../')) console.log('file path:', elementName); + if (node.children) { // if node children exist, then recursively call the child nodes with this func @@ -51,8 +54,8 @@ async function traverseAST(node) { processedNodes.add(node); // call the function to determine if it is a client component and store it in var - const isClientComp = await checkForClientString(node); - const isReactHook = await checkReactHooks(node); + // const isClientComp = await checkForClientString(node); + // const isReactHook = await checkReactHooks(node); // recursively iterate through the other non-jsx types if the jsx node children doesnt exist for (const key in node) {