Skip to content

Instantly share code, notes, and snippets.

@FermiDirak
Created November 30, 2020 12:13
Show Gist options
  • Star 3 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save FermiDirak/49798c59a6a09ea40843cca5749a35ac to your computer and use it in GitHub Desktop.
Save FermiDirak/49798c59a6a09ea40843cca5749a35ac to your computer and use it in GitHub Desktop.
Learn static code analysis in React!
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