UNPKG

node-sloc

Version:

A small tool for counting SLOC.

187 lines (186 loc) 7.48 kB
"use strict"; 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; } }