bob-group-frontend-coding-standards
Advanced tools
Comparing version 1.0.2 to 1.0.3
202
index.js
@@ -29,2 +29,3 @@ "use strict"; | ||
incorrectComponentFileNames: [], | ||
missingFontawesomeIconImports: [], | ||
}; | ||
@@ -34,2 +35,4 @@ let warnings = { | ||
incorrectlyNamedVariables: [], | ||
incorrectlyNamedStateVariables: [], | ||
incorrectlyNamedShowModalVariables: [], | ||
incorrectTruthy: [], | ||
@@ -42,2 +45,21 @@ classComponents: [], | ||
const upperSnakeCaseRegex = /^[A-Z0-9_]+$/; | ||
let allImportNames = []; | ||
let setupIconsContent; | ||
function keyToHumanReadable(key) { | ||
if (!key) | ||
return ""; | ||
// @ts-ignore | ||
let keyHumanReadable = key.replaceAll("_", " "); | ||
keyHumanReadable = keyHumanReadable.replaceAll("sender", "collection"); | ||
keyHumanReadable = keyHumanReadable.replaceAll("receiver", "delivery"); | ||
keyHumanReadable = keyHumanReadable.replaceAll("-", " "); | ||
// camel case to sentence case | ||
keyHumanReadable = keyHumanReadable.replace(/([A-Z])/g, " $1").trim(); | ||
let sentenceCaseKey = keyHumanReadable.charAt(0).toUpperCase() + | ||
keyHumanReadable.slice(1).toLowerCase(); | ||
sentenceCaseKey = sentenceCaseKey.replaceAll("Bob box", "Bob Box"); | ||
sentenceCaseKey = sentenceCaseKey.replaceAll("Bob pay", "Bob Pay"); | ||
sentenceCaseKey = sentenceCaseKey.replaceAll("Bob go", "Bob Go"); | ||
return sentenceCaseKey; | ||
} | ||
function writeOutput(type, content) { | ||
@@ -81,5 +103,9 @@ let colors = { | ||
data = addRenderMethodsComment(data, filePath); | ||
// data = makeCommentsSentenceCase(data); // todo needs more testing | ||
// Checks for files | ||
data = fixLodashImports(data, filePath); | ||
data = listMissingFontawesomeImports(data); | ||
// // data = makeCommentsSentenceCase(data); // todo needs more testing | ||
// // Checks for files | ||
checkStateVariableNamingConventions(data, file, filePath); | ||
checkVariableNamingConventions(data, file, filePath); | ||
// // checkShowModalNamingConventions(data, file, filePath); // todo needs to be defined better | ||
checkForRenderFunction(data, filePath); | ||
@@ -195,2 +221,21 @@ checkForBooleanTruthyDetection(data, filePath); | ||
} | ||
function checkStateVariableNamingConventions(data, file, filePath) { | ||
// CRITERIA: State variables should be camel case | ||
const stateVariableRegex = /(?<=\[\s*)(\w+)(?=\s*,\s*set\w+\s*\])/gm; | ||
const variableNames = []; | ||
let match; | ||
while ((match = stateVariableRegex.exec(data)) !== null) { | ||
variableNames.push(match[1]); | ||
} | ||
variableNames.forEach((variableName) => { | ||
if (!camelCaseRegex.test(variableName) && | ||
variableName !== file.split(".tsx").join("") && | ||
!(filePath.includes("/Routes") && variableName.includes("Page"))) { | ||
warnings.incorrectlyNamedStateVariables.push({ | ||
file: filePath, | ||
error: variableName, | ||
}); | ||
} | ||
}); | ||
} | ||
function checkVariableNamingConventions(data, file, filePath) { | ||
@@ -216,2 +261,19 @@ // CRITERIA: Variables should be camel case or upper snake case | ||
} | ||
function checkShowModalNamingConventions(data, file, filePath) { | ||
// CRITERIA: When naming state variables to show/hide modals, make use of const [modalToShow, setModalToShow] = useState<string>("") or const [shouldShowXModal, setShouldShowXModal] = useState<boolean>(false) | ||
const stateVariableRegex = /(?<=\[\s*)(\w+)(?=\s*,\s*set\w+\s*\])/gm; | ||
let match; | ||
const shouldShowXModalRegex = /^shouldShow[A-Z][a-zA-Z]*Modal$/; | ||
while ((match = stateVariableRegex.exec(data)) !== null) { | ||
let stateVariableName = match[1]; | ||
if (stateVariableName.toLowerCase().includes("modal") && | ||
!shouldShowXModalRegex.test(stateVariableName) && | ||
stateVariableName !== "modalToShow") { | ||
warnings.incorrectlyNamedShowModalVariables.push({ | ||
file: filePath, | ||
error: stateVariableName, | ||
}); | ||
} | ||
} | ||
} | ||
function addRenderMethodsComment(data, filePath) { | ||
@@ -234,2 +296,48 @@ // CRITERIA: All components should have a comment indicating where the render methods section starts | ||
} | ||
function fixLodashImports(data, filePath) { | ||
let importAll = 'import _ from "lodash";'; | ||
if (data.includes(importAll)) { | ||
const lodashFunctionRegex = /_\.\w+[A-Za-z]*\(/g; | ||
const lodashFunctions = data.match(lodashFunctionRegex); | ||
let functionNames = []; | ||
lodashFunctions === null || lodashFunctions === void 0 ? void 0 : lodashFunctions.forEach((functionString) => { | ||
let functionName = functionString.substring(2, functionString.length - 1); | ||
functionNames.push(functionName); | ||
data = data.replace(functionString, functionName + "("); | ||
let newImport = `import ${functionName} from "lodash/${functionName}";`; | ||
if (!data.includes(newImport)) { | ||
// prevent newImport from being added multiple times | ||
data = data.replace(importAll, `${importAll}\n${newImport}`); | ||
} | ||
}); | ||
data = data.replace(importAll, ""); | ||
} | ||
return data; | ||
} | ||
function kebabToUpperCase(str) { | ||
// Split the string into individual words | ||
const words = str.split("-"); | ||
// Capitalize each word (except the first one) | ||
const upperCamelCaseWords = words.map((word) => { | ||
return word.charAt(0).toUpperCase() + word.slice(1); | ||
}); | ||
// Join the words and return the upper camel case string | ||
return upperCamelCaseWords.join(""); | ||
} | ||
function listMissingFontawesomeImports(data) { | ||
let regex = /icon="[^"]*"/g; | ||
let matches = data.match(regex); | ||
matches === null || matches === void 0 ? void 0 : matches.forEach((match) => { | ||
let iconString = match.replace('icon="', "").replace('"', ""); | ||
let importName = "fa" + kebabToUpperCase(iconString); | ||
if (!allImportNames.includes(importName) && | ||
!setupIconsContent.includes(importName)) { | ||
errors.missingFontawesomeIconImports.push({ | ||
error: `Missing import for ${importName}`, | ||
}); | ||
allImportNames.push(importName); | ||
} | ||
}); | ||
return data; | ||
} | ||
function makeCommentsSentenceCase(data) { | ||
@@ -273,5 +381,25 @@ // CRITERIA: All comments should be sentence case | ||
errors.forEach((err) => { | ||
console.log("\t" + err.file + (err.error ? ` - ${err.error}` : "")); | ||
var _a; | ||
console.log("\t" + | ||
((_a = err.file) !== null && _a !== void 0 ? _a : "") + | ||
(err.error && err.file ? " - " : "") + | ||
(err.error ? `${err.error}` : "")); | ||
}); | ||
} | ||
function getSetupIconsContent() { | ||
return __awaiter(this, void 0, void 0, function* () { | ||
return new Promise((resolve, reject) => { | ||
let filePath = folderPath + "/setupIcons.ts"; | ||
fs.readFile(filePath, "utf8", (err, data) => { | ||
if (err) { | ||
reject(`Error reading file: ${filePath}`); | ||
} | ||
else { | ||
setupIconsContent = data; | ||
resolve(true); | ||
} | ||
}); | ||
}); | ||
}); | ||
} | ||
function run() { | ||
@@ -284,43 +412,35 @@ return __awaiter(this, void 0, void 0, function* () { | ||
writeOutput("info", "Running code checker..."); | ||
return readTSXFilesRecursively(folderPath) | ||
.then(() => { | ||
errors.incorrectInterfaceFileNames.length > 0 && | ||
logErrors("error", "Interface files named incorrectly", errors.incorrectInterfaceFileNames); | ||
errors.incorrectInterfaceNames.length > 0 && | ||
logErrors("error", "Interfaces named incorrectly", errors.incorrectInterfaceNames); | ||
errors.incorrectComponentFileNames.length > 0 && | ||
logErrors("error", "Component files named incorrectly", errors.incorrectComponentFileNames); | ||
errors.incorrectComponentNames.length > 0 && | ||
logErrors("error", "Components named incorrectly", errors.incorrectComponentNames); | ||
warnings.filesMissingRenderFunction.length > 0 && | ||
logErrors("warning", "Missing render function", warnings.filesMissingRenderFunction); | ||
warnings.incorrectlyNamedVariables.length > 0 && | ||
logErrors("warning", "Variables that are not camel case or upper snake case", warnings.incorrectlyNamedVariables); | ||
warnings.incorrectTruthy.length > 0 && | ||
logErrors("warning", "Prefer boolean truthy detection Boolean(x) over double !!", warnings.incorrectTruthy); | ||
warnings.classComponents.length > 0 && | ||
logErrors("warning", "Class components should be functional components", warnings.classComponents); | ||
warnings.forgottenTodos.length > 0 && | ||
logErrors("warning", "Forgotten Todos", warnings.forgottenTodos); | ||
let errorCount = 0; | ||
Object.keys(errors).forEach((key) => { | ||
var _a; | ||
// @ts-ignore | ||
errorCount += (_a = errors[key]) === null || _a === void 0 ? void 0 : _a.length; | ||
return getSetupIconsContent().then(() => __awaiter(this, void 0, void 0, function* () { | ||
return readTSXFilesRecursively(folderPath) | ||
.then(() => { | ||
let errorCount = 0; | ||
Object.keys(errors).forEach((key) => { | ||
// @ts-ignore | ||
let errorArray = errors[key]; | ||
errorCount += errorArray === null || errorArray === void 0 ? void 0 : errorArray.length; | ||
errorArray.length > 0 && | ||
logErrors("error", keyToHumanReadable(key), errorArray); | ||
}); | ||
Object.keys(warnings).forEach((key) => { | ||
// @ts-ignore | ||
let warningArray = warnings[key]; | ||
warningArray.length > 0 && | ||
logErrors("warning", keyToHumanReadable(key), warningArray); | ||
}); | ||
if (errorCount === 0) { | ||
writeOutput("info", "Done running code checker."); | ||
process.exit(0); | ||
} | ||
else { | ||
writeOutput("error", "Done running code checker."); | ||
process.exit(1); | ||
} | ||
}) | ||
.catch((err) => { | ||
writeOutput("error", err); | ||
process.exit(1); | ||
}); | ||
if (errorCount === 0) { | ||
writeOutput("info", "Done running code checker."); | ||
process.exit(0); | ||
} | ||
else { | ||
writeOutput("error", "Done running code checker."); | ||
process.exit(1); | ||
} | ||
}) | ||
.catch((err) => { | ||
writeOutput("error", err); | ||
process.exit(1); | ||
}); | ||
})); | ||
}); | ||
} | ||
run(); |
284
index.ts
@@ -18,3 +18,3 @@ const fs = require("fs"); | ||
interface IErrorObject { | ||
file: string; | ||
file?: string; | ||
error?: any; | ||
@@ -28,2 +28,3 @@ } | ||
incorrectComponentFileNames: IErrorObject[]; | ||
missingFontawesomeIconImports: IErrorObject[]; | ||
} = { | ||
@@ -34,2 +35,3 @@ incorrectInterfaceNames: [], | ||
incorrectComponentFileNames: [], | ||
missingFontawesomeIconImports: [], | ||
}; | ||
@@ -39,2 +41,4 @@ let warnings: { | ||
incorrectlyNamedVariables: IErrorObject[]; | ||
incorrectlyNamedStateVariables: IErrorObject[]; | ||
incorrectlyNamedShowModalVariables: IErrorObject[]; | ||
incorrectTruthy: IErrorObject[]; | ||
@@ -46,2 +50,4 @@ classComponents: IErrorObject[]; | ||
incorrectlyNamedVariables: [], | ||
incorrectlyNamedStateVariables: [], | ||
incorrectlyNamedShowModalVariables: [], | ||
incorrectTruthy: [], | ||
@@ -56,2 +62,27 @@ classComponents: [], | ||
let allImportNames: string[] = []; | ||
let setupIconsContent: any; | ||
function keyToHumanReadable(key: string | undefined): string { | ||
if (!key) return ""; | ||
// @ts-ignore | ||
let keyHumanReadable = key.replaceAll("_", " "); | ||
keyHumanReadable = keyHumanReadable.replaceAll("sender", "collection"); | ||
keyHumanReadable = keyHumanReadable.replaceAll("receiver", "delivery"); | ||
keyHumanReadable = keyHumanReadable.replaceAll("-", " "); | ||
// camel case to sentence case | ||
keyHumanReadable = keyHumanReadable.replace(/([A-Z])/g, " $1").trim(); | ||
let sentenceCaseKey = | ||
keyHumanReadable.charAt(0).toUpperCase() + | ||
keyHumanReadable.slice(1).toLowerCase(); | ||
sentenceCaseKey = sentenceCaseKey.replaceAll("Bob box", "Bob Box"); | ||
sentenceCaseKey = sentenceCaseKey.replaceAll("Bob pay", "Bob Pay"); | ||
sentenceCaseKey = sentenceCaseKey.replaceAll("Bob go", "Bob Go"); | ||
return sentenceCaseKey; | ||
} | ||
function writeOutput( | ||
@@ -104,6 +135,9 @@ type: "success" | "error" | "warning" | "info", | ||
data = addRenderMethodsComment(data, filePath); | ||
// data = makeCommentsSentenceCase(data); // todo needs more testing | ||
// Checks for files | ||
data = fixLodashImports(data, filePath); | ||
data = listMissingFontawesomeImports(data); | ||
// // data = makeCommentsSentenceCase(data); // todo needs more testing | ||
// // Checks for files | ||
checkStateVariableNamingConventions(data, file, filePath); | ||
checkVariableNamingConventions(data, file, filePath); | ||
// // checkShowModalNamingConventions(data, file, filePath); // todo needs to be defined better | ||
checkForRenderFunction(data, filePath); | ||
@@ -116,7 +150,5 @@ checkForBooleanTruthyDetection(data, filePath); | ||
} | ||
if (isComponentFile(data, filePath)) { | ||
checkComponentNamingConventions(data, file, filePath); | ||
} | ||
fs.writeFile(filePath, data, "utf8", (err: any) => { | ||
@@ -240,2 +272,31 @@ if (err) { | ||
function checkStateVariableNamingConventions( | ||
data: string, | ||
file: string, | ||
filePath: string | ||
) { | ||
// CRITERIA: State variables should be camel case | ||
const stateVariableRegex = /(?<=\[\s*)(\w+)(?=\s*,\s*set\w+\s*\])/gm; | ||
const variableNames = []; | ||
let match; | ||
while ((match = stateVariableRegex.exec(data)) !== null) { | ||
variableNames.push(match[1]); | ||
} | ||
variableNames.forEach((variableName) => { | ||
if ( | ||
!camelCaseRegex.test(variableName) && | ||
variableName !== file.split(".tsx").join("") && | ||
!(filePath.includes("/Routes") && variableName.includes("Page")) | ||
) { | ||
warnings.incorrectlyNamedStateVariables.push({ | ||
file: filePath, | ||
error: variableName, | ||
}); | ||
} | ||
}); | ||
} | ||
function checkVariableNamingConventions( | ||
@@ -271,2 +332,29 @@ data: string, | ||
function checkShowModalNamingConventions( | ||
data: string, | ||
file: string, | ||
filePath: string | ||
) { | ||
// CRITERIA: When naming state variables to show/hide modals, make use of const [modalToShow, setModalToShow] = useState<string>("") or const [shouldShowXModal, setShouldShowXModal] = useState<boolean>(false) | ||
const stateVariableRegex = /(?<=\[\s*)(\w+)(?=\s*,\s*set\w+\s*\])/gm; | ||
let match; | ||
const shouldShowXModalRegex = /^shouldShow[A-Z][a-zA-Z]*Modal$/; | ||
while ((match = stateVariableRegex.exec(data)) !== null) { | ||
let stateVariableName = match[1]; | ||
if ( | ||
stateVariableName.toLowerCase().includes("modal") && | ||
!shouldShowXModalRegex.test(stateVariableName) && | ||
stateVariableName !== "modalToShow" | ||
) { | ||
warnings.incorrectlyNamedShowModalVariables.push({ | ||
file: filePath, | ||
error: stateVariableName, | ||
}); | ||
} | ||
} | ||
} | ||
function addRenderMethodsComment(data: string, filePath: string) { | ||
@@ -292,2 +380,62 @@ // CRITERIA: All components should have a comment indicating where the render methods section starts | ||
function fixLodashImports(data: string, filePath: string) { | ||
let importAll = 'import _ from "lodash";'; | ||
if (data.includes(importAll)) { | ||
const lodashFunctionRegex = /_\.\w+[A-Za-z]*\(/g; | ||
const lodashFunctions = data.match(lodashFunctionRegex); | ||
let functionNames: string[] = []; | ||
lodashFunctions?.forEach((functionString: string) => { | ||
let functionName = functionString.substring(2, functionString.length - 1); | ||
functionNames.push(functionName); | ||
data = data.replace(functionString, functionName + "("); | ||
let newImport = `import ${functionName} from "lodash/${functionName}";`; | ||
if (!data.includes(newImport)) { | ||
// prevent newImport from being added multiple times | ||
data = data.replace(importAll, `${importAll}\n${newImport}`); | ||
} | ||
}); | ||
data = data.replace(importAll, ""); | ||
} | ||
return data; | ||
} | ||
function kebabToUpperCase(str: string) { | ||
// Split the string into individual words | ||
const words = str.split("-"); | ||
// Capitalize each word (except the first one) | ||
const upperCamelCaseWords = words.map((word) => { | ||
return word.charAt(0).toUpperCase() + word.slice(1); | ||
}); | ||
// Join the words and return the upper camel case string | ||
return upperCamelCaseWords.join(""); | ||
} | ||
function listMissingFontawesomeImports(data: string) { | ||
let regex = /icon="[^"]*"/g; | ||
let matches = data.match(regex); | ||
matches?.forEach((match: string) => { | ||
let iconString = match.replace('icon="', "").replace('"', ""); | ||
let importName = "fa" + kebabToUpperCase(iconString); | ||
if ( | ||
!allImportNames.includes(importName) && | ||
!setupIconsContent.includes(importName) | ||
) { | ||
errors.missingFontawesomeIconImports.push({ | ||
error: `Missing import for ${importName}`, | ||
}); | ||
allImportNames.push(importName); | ||
} | ||
}); | ||
return data; | ||
} | ||
function makeCommentsSentenceCase(data: string) { | ||
@@ -344,6 +492,25 @@ // CRITERIA: All comments should be sentence case | ||
errors.forEach((err) => { | ||
console.log("\t" + err.file + (err.error ? ` - ${err.error}` : "")); | ||
console.log( | ||
"\t" + | ||
(err.file ?? "") + | ||
(err.error && err.file ? " - " : "") + | ||
(err.error ? `${err.error}` : "") | ||
); | ||
}); | ||
} | ||
async function getSetupIconsContent() { | ||
return new Promise((resolve, reject) => { | ||
let filePath = folderPath + "/setupIcons.ts"; | ||
fs.readFile(filePath, "utf8", (err: any, data: string) => { | ||
if (err) { | ||
reject(`Error reading file: ${filePath}`); | ||
} else { | ||
setupIconsContent = data; | ||
resolve(true); | ||
} | ||
}); | ||
}); | ||
} | ||
async function run() { | ||
@@ -355,82 +522,37 @@ if (!args.folderPath) { | ||
writeOutput("info", "Running code checker..."); | ||
return readTSXFilesRecursively(folderPath) | ||
.then(() => { | ||
errors.incorrectInterfaceFileNames.length > 0 && | ||
logErrors( | ||
"error", | ||
"Interface files named incorrectly", | ||
errors.incorrectInterfaceFileNames | ||
); | ||
return getSetupIconsContent().then(async () => { | ||
return readTSXFilesRecursively(folderPath) | ||
.then(() => { | ||
let errorCount: number = 0; | ||
errors.incorrectInterfaceNames.length > 0 && | ||
logErrors( | ||
"error", | ||
"Interfaces named incorrectly", | ||
errors.incorrectInterfaceNames | ||
); | ||
Object.keys(errors).forEach((key) => { | ||
// @ts-ignore | ||
let errorArray = errors[key]; | ||
errorCount += errorArray?.length; | ||
errors.incorrectComponentFileNames.length > 0 && | ||
logErrors( | ||
"error", | ||
"Component files named incorrectly", | ||
errors.incorrectComponentFileNames | ||
); | ||
errorArray.length > 0 && | ||
logErrors("error", keyToHumanReadable(key), errorArray); | ||
}); | ||
Object.keys(warnings).forEach((key) => { | ||
// @ts-ignore | ||
let warningArray = warnings[key]; | ||
warningArray.length > 0 && | ||
logErrors("warning", keyToHumanReadable(key), warningArray); | ||
}); | ||
errors.incorrectComponentNames.length > 0 && | ||
logErrors( | ||
"error", | ||
"Components named incorrectly", | ||
errors.incorrectComponentNames | ||
); | ||
warnings.filesMissingRenderFunction.length > 0 && | ||
logErrors( | ||
"warning", | ||
"Missing render function", | ||
warnings.filesMissingRenderFunction | ||
); | ||
warnings.incorrectlyNamedVariables.length > 0 && | ||
logErrors( | ||
"warning", | ||
"Variables that are not camel case or upper snake case", | ||
warnings.incorrectlyNamedVariables | ||
); | ||
warnings.incorrectTruthy.length > 0 && | ||
logErrors( | ||
"warning", | ||
"Prefer boolean truthy detection Boolean(x) over double !!", | ||
warnings.incorrectTruthy | ||
); | ||
warnings.classComponents.length > 0 && | ||
logErrors( | ||
"warning", | ||
"Class components should be functional components", | ||
warnings.classComponents | ||
); | ||
warnings.forgottenTodos.length > 0 && | ||
logErrors("warning", "Forgotten Todos", warnings.forgottenTodos); | ||
let errorCount: number = 0; | ||
Object.keys(errors).forEach((key) => { | ||
// @ts-ignore | ||
errorCount += errors[key]?.length; | ||
if (errorCount === 0) { | ||
writeOutput("info", "Done running code checker."); | ||
process.exit(0); | ||
} else { | ||
writeOutput("error", "Done running code checker."); | ||
process.exit(1); | ||
} | ||
}) | ||
.catch((err) => { | ||
writeOutput("error", err); | ||
process.exit(1); | ||
}); | ||
if (errorCount === 0) { | ||
writeOutput("info", "Done running code checker."); | ||
process.exit(0); | ||
} else { | ||
writeOutput("error", "Done running code checker."); | ||
process.exit(1); | ||
} | ||
}) | ||
.catch((err) => { | ||
writeOutput("error", err); | ||
process.exit(1); | ||
}); | ||
}); | ||
} | ||
run(); |
{ | ||
"name": "bob-group-frontend-coding-standards", | ||
"version": "1.0.2", | ||
"version": "1.0.3", | ||
"description": "Script to check that frontend code follows coding standards", | ||
@@ -5,0 +5,0 @@ "main": "index.js", |
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
37666
919