node-sloc
Version:
A small tool for counting SLOC.
187 lines (186 loc) • 7.48 kB
JavaScript
;
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.prettyPrint = exports.fileExtensionAllowed = exports.filterFiles = exports.countSloc = exports.walk = void 0;
const file_extensions_1 = require("./file-extensions");
const path_1 = __importDefault(require("path"));
const readline_1 = __importDefault(require("readline"));
const graceful_fs_1 = __importDefault(require("graceful-fs"));
const util_1 = require("util");
const micromatch_1 = require("micromatch");
const readdirAsync = util_1.promisify(graceful_fs_1.default.readdir);
const statAsync = util_1.promisify(graceful_fs_1.default.stat);
/**
* Walks the directory dir async and returns a promise which
* resolves to an array of the filepaths.
*
* @param {string} dir The directory to walk
* @param {function} [logger] The function outputs extra information to this function if specified.
* @return {Promise} Resolves to an array of filepaths
*/
const walk = (options) => {
return new Promise((resolve, reject) => {
let results = [];
readdirAsync(options.path)
.then((files) => {
let len = files.length;
if (!len) {
resolve(results);
}
files.forEach((file) => {
const dirFile = path_1.default.join(options.path, file);
if (micromatch_1.isMatch(dirFile, options.ignorePaths || [])) {
len--;
if (!len) {
resolve(results);
}
return;
}
statAsync(dirFile)
.then((stat) => {
var _a;
if (stat.isFile()) {
len--;
results.push(dirFile);
(_a = options.logger) === null || _a === void 0 ? void 0 : _a.call(options, `Checking file: ${dirFile}`);
if (!len) {
resolve(results);
}
}
else if (stat.isDirectory()) {
if (micromatch_1.isMatch(dirFile, options.ignorePaths || [])) {
len--;
if (!len) {
resolve(results);
}
return;
}
walk(Object.assign(Object.assign({}, options), { path: dirFile })).then((res) => {
len--;
results = results.concat(res);
if (!len) {
resolve(results);
}
});
}
})
.catch((err) => reject(err));
});
})
.catch((err) => reject(err));
});
};
exports.walk = walk;
/**
* Counts the source lines of code in the given file, specified by the filepath.
*
* @param {string} file The filepath of the file to be read.
* @return {Promise} Resolves to an object containing the SLOC count.
*/
const countSloc = (file) => {
return new Promise((resolve, reject) => {
var _a;
const extension = (_a = file.split('.').pop()) === null || _a === void 0 ? void 0 : _a.toLowerCase(); // get the file extension
if (!extension)
throw new Error(`No file extension on file: ${file}`);
const comments = getCommentChars(extension);
const { start, end } = comments.block || {};
let sloc = 0;
let numComments = 0;
let blankLines = 0;
let inBlock = false;
const rl = readline_1.default.createInterface({
input: graceful_fs_1.default.createReadStream(file),
});
rl.on('line', (l) => {
const line = l.trim(); // Trim the line to remove white space
// Exclude empty lines
if (line.length === 0) {
blankLines++;
return;
}
// Check if a comment block starts
if (start && end && line.startsWith(start)) {
const commentEnd = line.indexOf(end);
numComments++;
// Check if the comment block ends at the same line
if (commentEnd !== -1 && commentEnd !== 0) {
return;
}
// Special case for when block comment tokens are identical
if (start === end) {
inBlock = !inBlock;
return;
}
inBlock = true;
return;
}
// Check if we're in a comment block and if the comment ends on this line
if (inBlock && end && line.indexOf(end) !== -1) {
numComments++;
inBlock = false;
return;
}
if (inBlock) {
numComments++;
return;
}
// Check if the line starts with a comment, exclude if true
if (comments.line && line.indexOf(comments.line) === 0) {
numComments++;
return;
}
// No comments, increase the count
sloc++;
});
rl.on('error', (err) => reject(err));
rl.on('close', () => resolve({
sloc: sloc,
comments: numComments,
blank: blankLines,
loc: numComments + sloc,
}));
});
};
exports.countSloc = countSloc;
/* Checks if a file extension is allowed. */
const fileExtensionAllowed = (file, extensions) => {
return extensions.some((extension) => file.endsWith(extension));
};
exports.fileExtensionAllowed = fileExtensionAllowed;
/* Filters an array of filenames and returns a list of allowed files. */
const filterFiles = (files, extensions) => {
return files.filter((file) => {
return fileExtensionAllowed(file, extensions);
});
};
exports.filterFiles = filterFiles;
const prettyPrint = (obj) => {
const str = `
+---------------------------------------------------+
| SLOC | ${obj.sloc} \t\t|
|-------------------------------|--------------------
| Lines of comments | ${obj.comments} \t\t|
|-------------------------------|--------------------
| Blank lines | ${obj.blank} \t\t|
|-------------------------------|--------------------
| Files counted | ${obj.files} \t\t|
|-------------------------------|--------------------
| Total LOC | ${obj.loc} \t\t|
+---------------------------------------------------+
`;
return str;
};
exports.prettyPrint = prettyPrint;
/* Returns the comment syntax for specific languages */
function getCommentChars(extension) {
const ext = file_extensions_1.extensions.find((x) => x.lang === extension);
if (ext) {
return ext.comments;
}
else {
return file_extensions_1.cStyleComments;
}
}