Created
November 30, 2020 12:13
-
-
Save FermiDirak/49798c59a6a09ea40843cca5749a35ac to your computer and use it in GitHub Desktop.
Learn static code analysis in React!
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import jscodeshift from "jscodeshift"; | |
import fs from "fs"; | |
import path from "path"; | |
const projectDirectory = "my/project/root/directory"; | |
/////////////////////////////////////////////////////////////////////////////// | |
// util functions | |
/////////////////////////////////////////////////////////////////////////////// | |
/** | |
* Walks through a directory tree and creates a list of every component file path | |
* note: all React components end in `.jsx` in this codebase | |
*/ | |
function walkSync(dir, filelist = []) { | |
const files = fs.readdirSync(dir); | |
files.forEach((file: any) => { | |
if (fs.statSync(`${dir}/${file}`).isDirectory()) { | |
filelist = walkSync(`${dir}/${file}`, filelist); | |
} else if (path.extname(file) === ".jsx") filelist.push(`${dir}/${file}`); | |
}); | |
return filelist; | |
} | |
/////////////////////////////////////////////////////////////////////////////// | |
// Example 1: | |
// Create a list of form components using and not using form UI components | |
/////////////////////////////////////////////////////////////////////////////// | |
// list of form components in codebase | |
const formComponentsFilePaths = walkSync(projectDirectory) | |
.filter(filepath => new RegExp(".*Form\.jsx$").test(filepath, "g")); | |
const formsUsingFormUI = []; | |
const formsNotUsingFormUI = []; | |
formComponentsFilePaths.forEach(filepath => { | |
try { | |
const fileSource = fs.readFileSync(filepath, "utf8"); | |
const tree = jscodeshift.withParser("javascript")(fileSource); | |
// look at the files imports and determine if any form components are imported | |
// note: this approach assumes all imports are absolute | |
let usesFormUIComponents = false; | |
tree | |
.find(jscodeshift.ImportDeclaration) | |
.find(jscodeshift.Literal) | |
.forEach(importPathNode => { | |
const importPath = importPathNode.value.value; | |
if (formUIComponentImportPaths.has(importPath)) { | |
usesFormUIComponents = true; | |
} | |
}); | |
if (usesFormUIComponents) { | |
formsUsingFormUI.push(filepath); | |
} else { | |
formsNotUsingFormUI.push(filepath); | |
} | |
} catch(error) { | |
throw new Error(error); | |
} | |
}); | |
const stats1 = { | |
formsUsingFormUI, | |
formsNotUsingFormUI, | |
usagePercentage: formsUsingFormUI.length / (formsUsingFormUI.length + formsNotUsingFormUI.length), | |
}; | |
/////////////////////////////////////////////////////////////////////////////// | |
// Example 2: | |
// Find and list all instances of text literals so teams can go and update | |
// them to use i18n | |
/////////////////////////////////////////////////////////////////////////////// | |
const textLiteralLineNumbersByFile = { | |
/* componentPath : list of line numbers, */ | |
}; | |
const componentFilePaths = walkSync(projectDirectory); | |
componentFilePaths.forEach(filepath => { | |
try { | |
const fileSource = fs.readFileSync(filepath, "utf8"); | |
// an AST representation of your source code | |
const tree = jscodeshift.withParser("javascript")(fileSource); | |
// record the line numbers of any text literals within React nodes. | |
// note: this approach only considers text literals passed as children and | |
// not text literals passed as props or by other means | |
const textLiteralLineNumbers = []; | |
tree | |
.find(jscodeshift.JSXElement) | |
.forEach(jsxElement => { | |
let textLiteralChildren = jsxElement.value.children | |
.filter(node => node.type === "JSXText") | |
.filter(node => !new RegExp("^\\s+$").test(node.value, "g")); | |
textLiteralChildren.forEach(node => { | |
textLiteralLineNumbers.push(node.loc.start.line); | |
}); | |
}); | |
textLiteralLineNumbersByFile[filepath] = textLiteralLineNumbers; | |
} catch (error) { | |
console.error(error); | |
} | |
}); | |
const stats2 = { | |
filesWithTextLiterals: textLiteralLineNumbersByFile | |
.entries() | |
.filter(([_filepath, lineNumbers]) => lineNumbers.length !== 0), | |
}; |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment