Skip to content

Commit

Permalink
Add script to run prettier and eslint globally (SAP#1763)
Browse files Browse the repository at this point in the history
Addresses Issue 1721
  • Loading branch information
legteodav authored Mar 25, 2021
1 parent 3aa684b commit 48f7f1f
Show file tree
Hide file tree
Showing 6 changed files with 13,647 additions and 11,688 deletions.
9 changes: 9 additions & 0 deletions .eslintrc
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
{
"extends": ["standard", "plugin:cypress/recommended"],
"rules": {
"semi": [2, "always"],
"no-tabs": "off",
"indent": "off",
"no-ex-assign": "warn"
}
}
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -18,3 +18,4 @@ whitesource.config.json
plugin*.js
core/dev-tools/latest_build.log
core/dev-tools/simple-app
/full_eslint_report.html
363 changes: 363 additions & 0 deletions code_quality.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,363 @@
const gitChangedFiles = require('git-changed-files');
const prettier = require('prettier');
const prettierConfig = require('./prettier_config.json');
const codeQualityConfig = require('./package.json').codeQuality || {};
const path = require('path');

const fs = require('fs');
const { ESLint } = require('eslint');
const eslint = new ESLint({ fix: true });

const AnsiUp = require('ansi_up').default;
const ansiup = new AnsiUp();

/**
* Get all files, excluding automatically generated or imported from external libraries.
* @param dir: path from where recursively get all the files
* @returns {[]}: list of files (absolut path)
*/
const getAllFiles = () => {
const options = getOptions();
if (!options.sourcePaths) {
return getAllFilesRecoursive(__dirname);
}
const sourcePaths = getSourcePaths(options.sourcePaths);
let results = [];
sourcePaths.forEach(sourcePath => {
const files = getAllFilesRecoursive(sourcePath);
results = results.concat(files);
});
return results;
};

const getSourcePaths = sourcePaths => {
return sourcePaths.split(',').map(sourcePath => path.resolve(__dirname, ...sourcePath.split('/')));
};

const getAllFilesRecoursive = dir => {
let results = [];
const list = fs.readdirSync(dir);
list.forEach(function (file) {
file = dir + '/' + file;
const stat = fs.statSync(file);
if (fileToExclude(file, stat)) {
return [];
}
if (stat && stat.isDirectory()) {
/* Recurse into a subdirectory */
results = results.concat(getAllFilesRecoursive(file));
} else {
/* Is a file */
results.push(file);
}
});
return results;
};

const fileToExclude = (file, stat) => {
if (stat && stat.isDirectory()) {
return (
file.endsWith('node_modules') ||
file.indexOf('.git') !== -1 ||
file.indexOf('/dist/') !== -1 ||
file.indexOf('\\dist\\') !== -1
);
}
const fileName = path.basename(file);
return /(.git|.min.css|.min.js)$/.test(fileName);
};

/**
* Return all changed files that will be commit to git branch.
* @returns {Promise<*>}: promise wiht list of files.
*/
const getChangedFiles = async () => {
const committedGitFiles = await gitChangedFiles();
return committedGitFiles.unCommittedFiles.filter(file => fs.existsSync(file) && !file.endsWith('package-lock.json'));
};

/**
* Group list of file paths by extension.
* @param files: array of file absolute paths
* @returns {key: 'File extension:, value: Array of file absolute paths}
*/
const groupFilesByExtension = files => {
return files.reduce((map, file) => {
try {
if (file.startsWith('./') || file.indexOf('.') === -1) {
return map;
}

const extension = file.substring(file.lastIndexOf('.') + 1).toLowerCase();
const array = map[extension] || [];
array.push(file);
map[extension] = array;
return map;
} catch (e) {
console.error('error --> ', e);
return {};
}
}, {});
};

/**
* Execute prettier and write result on same file.
* @param file: absolute class path
* @param config: configuration that will be used to prettier the file.
*/
const prettifyFile = (file, config) => {
try {
const text = fs.readFileSync(file).toString();
const pretty = prettier.format(text, config);
if (text === pretty) {
return;
}
console.log('Running prettier the file ' + file);
fs.writeFileSync(file, pretty);
} catch (error) {
console.log('Error in prettier the file ' + file + ': \n' + error);
}
};

/**
* Applying prettier on several files. We have a specific configuration for file extension on file prettier_config.json.
* @param filesByExtension: we pass a Map json object where the key is the extension, value is an Array with absolute file paths
*/
const prettifyFiles = filesByExtension => {
if (!codeQualityConfig.usePrettier) {
return; // no need to use pretty;
}
Object.keys(filesByExtension).forEach(extension => {
const files = filesByExtension[extension];
const config = prettierConfig[extension];
if (!config) {
console.log(
"We don't have any configuration for extension " +
extension +
', please add it in file prettier_config.json'
);
return;
}
files.forEach(file => prettifyFile(file, config));
});
};

/**
* ESlint analysis and fix on an Array of files.
* @param files: array with absolute file paths
* @returns {Promise<{}|{report: string, error: boolean}>}
*/
const eslintFiles = async files => {
if (!codeQualityConfig.useEslint) {
return {};
}

const formatter = await eslint.loadFormatter('stylish');
let error = false;
let report = '';
for (const file of files) {
try {
const result = await eslint.lintFiles(file);
error = error || result.some(res => res.errorCount > 0);
await ESLint.outputFixes(result);
const resultText = formatter.format(result);
if (!!resultText && resultText.trim().length > 0) {
report += resultText;
}
} catch (exception) {
error = true;
report += 'Error in eslint file ' + file + ':\n' + exception;
}
}

return { error, report };
};

/**
* Run `prettier` on changed files before commit;
* You can also call this function using: npm run code-quality-prettier
*/
const preCommitPrettier = async filesByExtension => {
if (!filesByExtension) {
const files = await getChangedFiles();
if (!files) {
console.log("Couldn't find any file that hand been changed");
return;
}
console.log('File to be analyzed before commit:\n' + files.join('\n'));
filesByExtension = groupFilesByExtension(files);
}

prettifyFiles(filesByExtension);
};

/**
* Run `eslint` on changed files before commit;
* You can also call this function using: npm run code-quality-eslint
*/
const preCommitEslint = async filesByExtension => {
if (!filesByExtension) {
const files = await getChangedFiles();
if (!files) {
console.log("Couldn't find any file that hand been changed");
return;
}
console.log('File to be analyzed before commit:\n' + files.join('\n'));
filesByExtension = groupFilesByExtension(files);
}

const esLintResult = await eslintFilesByExtension(filesByExtension);
if (esLintResult.error) {
console.log('Please fix these errors before to commit:' + esLintResult.report);
console.log('Eslint executed in ' + esLintResult.numberFiles + ' files ');
if (codeQualityConfig.eslintStopCommit) {
process.exit(1);
}
} else {
console.log('Eslint executed in ' + esLintResult.numberFiles + ' files ');
}
};

/**
* Run `prettier` and `eslint` on changed files before commit;
* You can also call this function using: npm run code-quality
*/
const preCommit = async () => {
const files = await getChangedFiles();
if (!files) {
console.log("Couldn't find any file that hand been changed");
return;
}
console.log('File to be analyzed before commit:\n' + files.join('\n'));
const filesByExtension = groupFilesByExtension(files);
if (codeQualityConfig.usePrettier) {
await preCommitPrettier(filesByExtension);
} else {
console.log('Prettier is disabled. Skipping...');
}

if (codeQualityConfig.usePrettier) {
await preCommitEslint(filesByExtension);
} else {
console.log('ESlint is disabled. Skipping...');
}
};

/**
* Execute ESlint analysis and try to fix problems
* @param filesByExtension: we pass a Map json object where the key is the extension, value is an Array with absolute file paths
* @returns {Promise<{report: string, numberFiles: number, error: boolean}>}
*/
const eslintFilesByExtension = async filesByExtension => {
let error = false;
let report = '';
let numberFiles = 0;
const extensions = Object.keys(filesByExtension);
for (const extension of extensions.filter(extension => extension === 'ts' || extension === 'js')) {
const esLintFiles = filesByExtension[extension];
numberFiles += esLintFiles.length;
const eslintResult = await eslintFiles(esLintFiles);
error = error || eslintResult.error;
if (!!eslintResult.report && eslintResult.report.trim().length > 0) {
report += eslintResult.report;
}
}

return { error, report, numberFiles };
};

/**
* Run `prettier` and `eslint` on all project files;
* You can also call this function using: npm run full-code-quality
*/
const full = async () => {
const files = getAllFiles();
const filesByExtension = groupFilesByExtension(files);
await fullPrettier(filesByExtension);
console.log("Running EsLint on all files. Please wait...")
await fullEslint(filesByExtension);
};

/**
* Run `prettier` on all project files;
* You can also call this function using: npm run full-code-quality
*/
const fullPrettier = async filesByExtension => {
if (!filesByExtension) {
const files = getAllFiles();
filesByExtension = groupFilesByExtension(files);
}
prettifyFiles(filesByExtension);
};

/**
* Run `eslint` on all project files;
* You can also call this function using: npm run full-code-quality
*/
const fullEslint = async filesByExtension => {
if (!filesByExtension) {
const files = getAllFiles();
filesByExtension = groupFilesByExtension(files);
}

const esLintResult = await eslintFilesByExtension(filesByExtension);
const options = getOptions();
const reportFile = options.report || 'full_eslint_report.html';
if (esLintResult.error) {
fs.writeFileSync(reportFile, ansiup.ansi_to_html(esLintResult.report).replace(/(?:\r\n|\r|\n)/g, '<br/>'));
console.log('Wrote eslint report to file ' + path.resolve(reportFile));
}
console.log("Eslint executed in ' + esLintResult.numberFiles + ' files. Results written in 'full_eslint_report.html' ");
};

/**
* We parse application node parameters; they must be in format -- key1=value1 key2=value2
* @returns {{}} an json Map object
*/
const getOptions = () => {
const options = {};

for (let i = 0; i < process.argv.length; i++) {
const arg = process.argv[i];
if (!arg.includes('=')) {
continue;
}

const key = process.argv[i].substring(0, process.argv[i].indexOf('='));
const value = process.argv[i].substring(process.argv[i].indexOf('=') + 1);
options[key] = value;
}
return options;
};

/**
* This is the method that will be executed with node code_quality.js
*/
(async () => {
const options = getOptions();
if (options.mode === 'pre_commit') {
return await preCommit();
}
if (options.mode === 'pre_commit_prettier') {
return await preCommitPrettier();
}
if (options.mode === 'pre_commit_eslint') {
return await preCommitEslint();
}
if (options.mode === 'full') {
return await full();
}
if (options.mode === 'full_prettier') {
return await fullPrettier();
}
if (options.mode === 'full_eslint') {
return await fullEslint();
}

console.error(
'You need to pass application parameter -- mode=pre_commit|pre_commit-prettier|pre_commit-eslint|full|full-prettier|full-eslint'
);
})().catch(err => {
console.log(err);
process.exit(1);
});
Loading

0 comments on commit 48f7f1f

Please sign in to comment.