diff --git a/sapling/package.json b/sapling/package.json index 3033e2e..0bd1dc5 100644 --- a/sapling/package.json +++ b/sapling/package.json @@ -54,7 +54,7 @@ "title": "Generate Tree" } ], - "menus" : { + "menus": { "commandPalette": [ { "command": "sapling.generateTree", @@ -138,4 +138,4 @@ "react": "^17.0.2", "react-dom": "^17.0.2" } -} +} \ No newline at end of file diff --git a/sapling/src/SaplingParser.ts b/sapling/src/SaplingParser.ts index 939c171..7742612 100644 --- a/sapling/src/SaplingParser.ts +++ b/sapling/src/SaplingParser.ts @@ -6,7 +6,6 @@ import { Tree } from './types/Tree'; import { ImportObj } from './types/ImportObj'; import { File } from '@babel/types'; - export class SaplingParser { entryFile: string; tree: Tree | undefined; @@ -17,7 +16,7 @@ export class SaplingParser { if (process.platform === 'linux' && this.entryFile.includes('wsl$')) { this.entryFile = path.resolve(filePath.split(path.win32.sep).join(path.posix.sep)); this.entryFile = '/' + this.entryFile.split('/').slice(3).join('/'); - // Fix for when running wsl but selecting files held on windows file system + // Fix for when running wsl but selecting files held on windows file system } else if (process.platform === 'linux' && (/[a-zA-Z]/).test(this.entryFile[0])) { const root = `/mnt/${this.entryFile[0].toLowerCase()}`; this.entryFile = path.join(root, filePath.split(path.win32.sep).slice(1).join(path.posix.sep)); @@ -28,13 +27,13 @@ export class SaplingParser { } // Public method to generate component tree based on current entryFile - public parse() : Tree { + public parse(): Tree { // Create root Tree node const root = { id: getNonce(), name: path.basename(this.entryFile).replace(/\.(t|j)sx?$/, ''), fileName: path.basename(this.entryFile), - filePath : this.entryFile, + filePath: this.entryFile, importPath: '/', // this.entryFile here breaks windows file path on root e.g. C:\\ is detected as third party expanded: false, depth: 0, @@ -53,66 +52,66 @@ export class SaplingParser { return this.tree; } - public getTree() : Tree | undefined { + public getTree(): Tree | undefined { return this.tree; } // Set Sapling Parser with a specific Data Tree (from workspace state) - public setTree(tree : Tree) : void { + public setTree(tree: Tree): void { this.entryFile = tree.filePath; this.tree = tree; } // Updates the tree when a file is saved in VS Code - public updateTree(filePath : string) : Tree | undefined { - if (this.tree === undefined) { - return this.tree; - } + public updateTree(filePath: string): Tree | undefined { + if (this.tree === undefined) { + return this.tree; + } - type ChildInfo = { - depth: number, - filePath: string, - expanded: boolean - }; + type ChildInfo = { + depth: number, + filePath: string, + expanded: boolean + }; - let children : Array = []; + let children: Array = []; - const getChildNodes = (node: Tree) : void => { - const { depth, filePath, expanded } = node; - children.push({ depth, filePath, expanded }); - }; + const getChildNodes = (node: Tree): void => { + const { depth, filePath, expanded } = node; + children.push({ depth, filePath, expanded }); + }; - const matchExpand = (node: Tree) : void => { - for (let i = 0 ; i < children.length ; i += 1) { - const oldNode = children[i]; - if (oldNode.depth === node.depth && oldNode.filePath === node.filePath && oldNode.expanded) { - node.expanded = true; - } + const matchExpand = (node: Tree): void => { + for (let i = 0; i < children.length; i += 1) { + const oldNode = children[i]; + if (oldNode.depth === node.depth && oldNode.filePath === node.filePath && oldNode.expanded) { + node.expanded = true; } - }; + } + }; - const callback = (node: Tree) : void => { - if (node.filePath === filePath) { - node.children.forEach(child => { - this.#traverseTree(getChildNodes, child); - }); + const callback = (node: Tree): void => { + if (node.filePath === filePath) { + node.children.forEach(child => { + this.#traverseTree(getChildNodes, child); + }); - const newNode = this.parser(node); + const newNode = this.parser(node); - this.#traverseTree(matchExpand, newNode); + this.#traverseTree(matchExpand, newNode); - children = []; - } - }; + children = []; + } + }; - this.#traverseTree(callback, this.tree); + this.#traverseTree(callback, this.tree); - return this.tree; - } + return this.tree; + } // Traverses the tree and changes expanded property of node whose id matches provided id - public toggleNode(id : string, expanded : boolean) : Tree | undefined { - const callback = (node : Tree) => { + public toggleNode(id: string, expanded: boolean): Tree | undefined { + const callback = (node: Tree) => { if (node.id === id) { node.expanded = expanded; } @@ -124,20 +123,20 @@ export class SaplingParser { } // Traverses all nodes of current component tree and applies callback to each node - #traverseTree(callback : Function, node : Tree | undefined = this.tree) : void { + #traverseTree(callback: Function, node: Tree | undefined = this.tree): void { if (!node) { return; } callback(node); - node.children.forEach( (childNode) => { + node.children.forEach((childNode) => { this.#traverseTree(callback, childNode); }); } // Recursively builds the React component tree structure starting from root node - private parser(componentTree: Tree) : Tree { + private parser(componentTree: Tree): Tree { // If import is a node module, do not parse any deeper if (!['\\', '/', '.'].includes(componentTree.importPath[0])) { @@ -196,7 +195,7 @@ export class SaplingParser { } // Finds files where import string does not include a file extension - private getFileName(componentTree: Tree) : string | null { + private getFileName(componentTree: Tree): string | null { const ext = path.extname(componentTree.filePath); if (!ext) { @@ -213,16 +212,16 @@ export class SaplingParser { // Extracts Imports from current file // const Page1 = lazy(() => import('./page1')); -> is parsed as 'ImportDeclaration' // import Page2 from './page2'; -> is parsed as 'VariableDeclaration' - private getImports(body : {[key : string]: any}[]) : ImportObj { + private getImports(body: { [key: string]: any }[]): ImportObj { const bodyImports = body.filter(item => item.type === 'ImportDeclaration' || 'VariableDeclaration'); // console.log('bodyImports are: ', bodyImports); return bodyImports.reduce((accum, curr) => { // Import Declarations: if (curr.type === 'ImportDeclaration') { - curr.specifiers.forEach((i : {[key : string]: any}) => { + curr.specifiers.forEach((i: { [key: string]: any }) => { accum[i.local.name] = { importPath: curr.source.value, - importName: (i.imported)? i.imported.name : i.local.name + importName: (i.imported) ? i.imported.name : i.local.name }; }); } @@ -242,7 +241,7 @@ export class SaplingParser { } // Recursive helper method to find import path in Variable Declaration - private findVarDecImports(ast: {[key: string]: any}) : string | boolean { + private findVarDecImports(ast: { [key: string]: any }): string | boolean { // Base Case, find import path in variable declaration and return it, if (ast.hasOwnProperty('callee') && ast.callee.type === 'Import') { return ast.arguments[0].value; @@ -262,24 +261,24 @@ export class SaplingParser { } // Finds JSX React Components in current file - private getJSXChildren(astTokens: any[], importsObj : ImportObj, parentNode: Tree) : Tree[] { - let childNodes: {[key : string]: Tree} = {}; - let props : {[key : string]: boolean} = {}; - let token : {[key: string]: any}; + private getJSXChildren(astTokens: any[], importsObj: ImportObj, parentNode: Tree): Tree[] { + let childNodes: { [key: string]: Tree } = {}; + let props: { [key: string]: boolean } = {}; + let token: { [key: string]: any }; for (let i = 0; i < astTokens.length; i++) { // Case for finding JSX tags eg if (astTokens[i].type.label === 'jsxTagStart' - && astTokens[i + 1].type.label === 'jsxName' - && importsObj[astTokens[i + 1].value]) { + && astTokens[i + 1].type.label === 'jsxName' + && importsObj[astTokens[i + 1].value]) { token = astTokens[i + 1]; props = this.getJSXProps(astTokens, i + 2); childNodes = this.getChildNodes(importsObj, token, props, parentNode, childNodes); // Case for finding components passed in as props e.g. } else if (astTokens[i].type.label === 'jsxName' - && (astTokens[i].value === 'component' || astTokens[i].value === 'children') - && importsObj[astTokens[i + 3].value]) { + && (astTokens[i].value === 'component' || astTokens[i].value === 'children') + && importsObj[astTokens[i + 3].value]) { token = astTokens[i + 3]; childNodes = this.getChildNodes(importsObj, token, props, parentNode, childNodes); } @@ -288,13 +287,13 @@ export class SaplingParser { return Object.values(childNodes); } - private getChildNodes(imports : ImportObj, - astToken : {[key: string]: any}, props : {[key : string]: boolean}, - parent : Tree, children : {[key : string] : Tree}) : {[key : string] : Tree} { + private getChildNodes(imports: ImportObj, + astToken: { [key: string]: any }, props: { [key: string]: boolean }, + parent: Tree, children: { [key: string]: Tree }): { [key: string]: Tree } { if (children[astToken.value]) { children[astToken.value].count += 1; - children[astToken.value].props = {...children[astToken.value].props, ...props}; + children[astToken.value].props = { ...children[astToken.value].props, ...props }; } else { // Add tree node to childNodes if one does not exist children[astToken.value] = { @@ -320,8 +319,8 @@ export class SaplingParser { } // Extracts prop names from a JSX element - private getJSXProps(astTokens: {[key: string]: any}[], j : number) : {[key : string]: boolean} { - const props : {[key : string]: boolean} = {}; + private getJSXProps(astTokens: { [key: string]: any }[], j: number): { [key: string]: boolean } { + const props: { [key: string]: boolean } = {}; while (astTokens[j].type.label !== "jsxTagEnd") { if (astTokens[j].type.label === "jsxName" && astTokens[j + 1].value === "=") { props[astTokens[j].value] = true; @@ -332,11 +331,11 @@ export class SaplingParser { } // Checks if current Node is connected to React-Redux Store - private checkForRedux(astTokens: any[], importsObj : ImportObj) : boolean { + private checkForRedux(astTokens: any[], importsObj: ImportObj): boolean { // Check that react-redux is imported in this file (and we have a connect method or otherwise) let reduxImported = false; let connectAlias; - Object.keys(importsObj).forEach( key => { + Object.keys(importsObj).forEach(key => { if (importsObj[key].importPath === 'react-redux' && importsObj[key].importName === 'connect') { reduxImported = true; connectAlias = key; diff --git a/sapling/src/test/suite/parser.test.ts b/sapling/src/test/suite/parser.test.ts index e7ddc03..ff276e8 100644 --- a/sapling/src/test/suite/parser.test.ts +++ b/sapling/src/test/suite/parser.test.ts @@ -1,6 +1,6 @@ import { SaplingParser } from '../../SaplingParser'; import { Tree } from '../../types/Tree'; -import { describe, suite , test, before} from 'mocha'; +import { describe, suite, test, before } from 'mocha'; import { expect } from 'chai'; import * as path from 'path'; @@ -10,11 +10,11 @@ import * as path from 'path'; // import * as myExtension from '../../extension'; suite('Parser Test Suite', () => { - let parser : SaplingParser, tree : Tree, file : string; + let parser: SaplingParser, tree: Tree, file: string; // UNPARSED TREE TEST describe('It initializes correctly', () => { - before( () => { + before(() => { file = path.join(__dirname, '../../../src/test/test_apps/test_0/index.js'); parser = new SaplingParser(file); }); @@ -31,7 +31,7 @@ suite('Parser Test Suite', () => { // TEST 0: ONE CHILD describe('It works for simple apps', () => { - before( () => { + before(() => { file = path.join(__dirname, '../../../src/test/test_apps/test_0/index.js'); parser = new SaplingParser(file); tree = parser.parse(); @@ -142,7 +142,7 @@ suite('Parser Test Suite', () => { // TEST 5: MISSING EXTENSIONS AND UNUSED IMPORTS describe('It works for extension-less imports', () => { - let names: string[], paths: string [], expectedNames : string[], expectedPaths : string[]; + let names: string[], paths: string[], expectedNames: string[], expectedPaths: string[]; before(() => { file = path.join(__dirname, '../../../src/test/test_apps/test_5/index.js'); parser = new SaplingParser(file); @@ -157,7 +157,7 @@ suite('Parser Test Suite', () => { '../../../src/test/test_apps/test_5/components/JSX.jsx', '../../../src/test/test_apps/test_5/components/TS.ts', '../../../src/test/test_apps/test_5/components/TSX.tsx' - ].map( el => path.resolve(__dirname, el)); + ].map(el => path.resolve(__dirname, el)); }); test('Check children match expected children', () => { diff --git a/sapling/src/types/ImportObj.ts b/sapling/src/types/ImportObj.ts index 917617a..20009aa 100644 --- a/sapling/src/types/ImportObj.ts +++ b/sapling/src/types/ImportObj.ts @@ -1,5 +1,5 @@ // ImportObj type, used by SaplingParser // ImportObj holds data about imports in the current JS/TS file export type ImportObj = { - [key : string]: {importPath: string, importName: string} + [key: string]: { importPath: string, importName: string } };