linguist-js 2.7.0 → 2.8.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli.js +7 -3
- package/dist/helpers/norm-path.d.ts +2 -0
- package/dist/helpers/norm-path.js +15 -0
- package/dist/helpers/parse-gitattributes.d.ts +1 -0
- package/dist/helpers/parse-gitattributes.js +7 -8
- package/dist/helpers/parse-gitignore.d.ts +1 -0
- package/dist/helpers/parse-gitignore.js +12 -0
- package/dist/helpers/read-file.d.ts +1 -1
- package/dist/helpers/read-file.js +2 -2
- package/dist/helpers/walk-tree.js +18 -4
- package/dist/index.js +114 -62
- package/dist/types.d.ts +22 -0
- package/ext/generated.rb +6 -0
- package/ext/heuristics.yml +58 -6
- package/ext/languages.yml +314 -12
- package/license.md +1 -1
- package/package.json +4 -4
- package/readme.md +29 -5
package/dist/cli.js
CHANGED
|
@@ -8,6 +8,7 @@ const fs_1 = __importDefault(require("fs"));
|
|
|
8
8
|
const path_1 = __importDefault(require("path"));
|
|
9
9
|
const commander_1 = require("commander");
|
|
10
10
|
const index_1 = __importDefault(require("./index"));
|
|
11
|
+
const norm_path_1 = require("./helpers/norm-path");
|
|
11
12
|
const colouredMsg = ([r, g, b], msg) => `\u001B[${38};2;${r};${g};${b}m${msg}${'\u001b[0m'}`;
|
|
12
13
|
const hexToRgb = (hex) => [parseInt(hex.slice(1, 3), 16), parseInt(hex.slice(3, 5), 16), parseInt(hex.slice(5, 7), 16)];
|
|
13
14
|
commander_1.program
|
|
@@ -23,11 +24,13 @@ commander_1.program
|
|
|
23
24
|
.option('-F|--listFiles [bool]', 'Whether to list every matching file under the language results', false)
|
|
24
25
|
.option('-q|--quick [bool]', 'Skip complex language analysis (alias for -{A|I|H|S}=false)', false)
|
|
25
26
|
.option('-o|--offline [bool]', 'Use packaged data files instead of fetching latest from GitHub', false)
|
|
27
|
+
.option('-L|--calculateLines [bool]', 'Calculate lines of code totals', true)
|
|
26
28
|
.option('-V|--keepVendored [bool]', 'Prevent skipping over vendored/generated files', false)
|
|
27
29
|
.option('-B|--keepBinary [bool]', 'Prevent skipping over binary files', false)
|
|
28
30
|
.option('-r|--relativePaths [bool]', 'Convert absolute file paths to relative', false)
|
|
29
31
|
.option('-A|--checkAttributes [bool]', 'Force the checking of gitattributes files', true)
|
|
30
32
|
.option('-I|--checkIgnored [bool]', 'Force the checking of gitignore files', true)
|
|
33
|
+
.option('-D|--checkDetected [bool]', 'Force files marked with linguist-detectable to always appear in output', true)
|
|
31
34
|
.option('-H|--checkHeuristics [bool]', 'Apply heuristics to ambiguous languages', true)
|
|
32
35
|
.option('-S|--checkShebang [bool]', 'Check shebang lines for explicit classification', true)
|
|
33
36
|
.option('-M|--checkModeline [bool]', 'Check modelines for explicit classification', true)
|
|
@@ -78,21 +81,22 @@ if (args.analyze)
|
|
|
78
81
|
}
|
|
79
82
|
}
|
|
80
83
|
// List parsed results
|
|
81
|
-
for (const [lang, { bytes, color }] of sortedEntries) {
|
|
84
|
+
for (const [lang, { bytes, lines, color }] of sortedEntries) {
|
|
82
85
|
const percent = (bytes) => bytes / (totalBytes || 1) * 100;
|
|
83
86
|
const fmtd = {
|
|
84
87
|
index: (++count).toString().padStart(2, ' '),
|
|
85
88
|
lang: lang.padEnd(24, ' '),
|
|
86
89
|
percent: percent(bytes).toFixed(2).padStart(5, ' '),
|
|
87
90
|
bytes: bytes.toLocaleString().padStart(10, ' '),
|
|
91
|
+
loc: lines.code.toLocaleString().padStart(10, ' '),
|
|
88
92
|
icon: colouredMsg(hexToRgb(color !== null && color !== void 0 ? color : '#ededed'), '\u2588'),
|
|
89
93
|
};
|
|
90
|
-
console.log(` ${fmtd.index}. ${fmtd.icon} ${fmtd.lang} ${fmtd.percent}% ${fmtd.bytes} B`);
|
|
94
|
+
console.log(` ${fmtd.index}. ${fmtd.icon} ${fmtd.lang} ${fmtd.percent}% ${fmtd.bytes} B ${fmtd.loc} LOC`);
|
|
91
95
|
// If using `listFiles` option, list all files tagged as this language
|
|
92
96
|
if (args.listFiles) {
|
|
93
97
|
console.log(); // padding
|
|
94
98
|
for (const file of filesPerLanguage[lang]) {
|
|
95
|
-
let relFile = path_1.default.relative(path_1.default.resolve('.'), file)
|
|
99
|
+
let relFile = (0, norm_path_1.normPath)(path_1.default.relative(path_1.default.resolve('.'), file));
|
|
96
100
|
if (!relFile.startsWith('../'))
|
|
97
101
|
relFile = './' + relFile;
|
|
98
102
|
const bytes = (await fs_1.default.promises.stat(file)).size;
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.normAbsPath = exports.normPath = void 0;
|
|
7
|
+
const path_1 = __importDefault(require("path"));
|
|
8
|
+
const normPath = function normalisedPath(...inputPaths) {
|
|
9
|
+
return path_1.default.join(...inputPaths).replace(/\\/g, '/');
|
|
10
|
+
};
|
|
11
|
+
exports.normPath = normPath;
|
|
12
|
+
const normAbsPath = function normalisedAbsolutePath(...inputPaths) {
|
|
13
|
+
return path_1.default.resolve(...inputPaths).replace(/\\/g, '/');
|
|
14
|
+
};
|
|
15
|
+
exports.normAbsPath = normAbsPath;
|
|
@@ -1,9 +1,6 @@
|
|
|
1
1
|
"use strict";
|
|
2
|
-
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
-
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
-
};
|
|
5
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
-
const
|
|
3
|
+
const norm_path_1 = require("./norm-path");
|
|
7
4
|
/**
|
|
8
5
|
* Parses a gitattributes file.
|
|
9
6
|
*/
|
|
@@ -16,7 +13,7 @@ function parseAttributes(content, folderRoot = '.') {
|
|
|
16
13
|
continue;
|
|
17
14
|
const parts = line.split(/\s+/g);
|
|
18
15
|
const fileGlob = parts[0];
|
|
19
|
-
const relFileGlob =
|
|
16
|
+
const relFileGlob = (0, norm_path_1.normPath)(folderRoot, fileGlob);
|
|
20
17
|
const attrParts = parts.slice(1);
|
|
21
18
|
const isTrue = (str) => !str.startsWith('-') && !str.endsWith('=false');
|
|
22
19
|
const isFalse = (str) => str.startsWith('-') || str.endsWith('=false');
|
|
@@ -24,10 +21,12 @@ function parseAttributes(content, folderRoot = '.') {
|
|
|
24
21
|
const falseParts = (str) => attrParts.filter(part => part.includes(str) && isFalse(part));
|
|
25
22
|
const hasTrueParts = (str) => trueParts(str).length > 0;
|
|
26
23
|
const hasFalseParts = (str) => falseParts(str).length > 0;
|
|
24
|
+
const boolOrNullVal = (str) => hasTrueParts(str) ? true : hasFalseParts(str) ? false : null;
|
|
27
25
|
const attrs = {
|
|
28
|
-
'generated':
|
|
29
|
-
'vendored':
|
|
30
|
-
'documentation':
|
|
26
|
+
'generated': boolOrNullVal('linguist-generated'),
|
|
27
|
+
'vendored': boolOrNullVal('linguist-vendored'),
|
|
28
|
+
'documentation': boolOrNullVal('linguist-documentation'),
|
|
29
|
+
'detectable': boolOrNullVal('linguist-detectable'),
|
|
31
30
|
'binary': hasTrueParts('binary') || hasFalseParts('text') ? true : hasFalseParts('binary') || hasTrueParts('text') ? false : null,
|
|
32
31
|
'language': (_b = (_a = trueParts('linguist-language').at(-1)) === null || _a === void 0 ? void 0 : _a.split('=')[1]) !== null && _b !== void 0 ? _b : null,
|
|
33
32
|
};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export default function parseGitignore(content: string): string[];
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
function parseGitignore(content) {
|
|
4
|
+
const readableData = content
|
|
5
|
+
// Remove comments unless escaped
|
|
6
|
+
.replace(/(?<!\\)#.+/g, '')
|
|
7
|
+
// Remove whitespace unless escaped
|
|
8
|
+
.replace(/(?:(?<!\\)\s)+$/g, '');
|
|
9
|
+
const arrayData = readableData.split(/\r?\n/).filter(data => data);
|
|
10
|
+
return arrayData;
|
|
11
|
+
}
|
|
12
|
+
exports.default = parseGitignore;
|
|
@@ -2,4 +2,4 @@
|
|
|
2
2
|
* Read part of a file on disc.
|
|
3
3
|
* @throws 'EPERM' if the file is not readable.
|
|
4
4
|
*/
|
|
5
|
-
export default function
|
|
5
|
+
export default function readFileChunk(filename: string, onlyFirstLine?: boolean): Promise<string>;
|
|
@@ -8,7 +8,7 @@ const fs_1 = __importDefault(require("fs"));
|
|
|
8
8
|
* Read part of a file on disc.
|
|
9
9
|
* @throws 'EPERM' if the file is not readable.
|
|
10
10
|
*/
|
|
11
|
-
async function
|
|
11
|
+
async function readFileChunk(filename, onlyFirstLine = false) {
|
|
12
12
|
const chunkSize = 100;
|
|
13
13
|
const stream = fs_1.default.createReadStream(filename, { highWaterMark: chunkSize });
|
|
14
14
|
let content = '';
|
|
@@ -20,4 +20,4 @@ async function readFile(filename, onlyFirstLine = false) {
|
|
|
20
20
|
}
|
|
21
21
|
return content;
|
|
22
22
|
}
|
|
23
|
-
exports.default =
|
|
23
|
+
exports.default = readFileChunk;
|
|
@@ -5,6 +5,8 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
5
5
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
6
|
const fs_1 = __importDefault(require("fs"));
|
|
7
7
|
const path_1 = __importDefault(require("path"));
|
|
8
|
+
const parse_gitignore_1 = __importDefault(require("./parse-gitignore"));
|
|
9
|
+
const norm_path_1 = require("./norm-path");
|
|
8
10
|
let allFiles;
|
|
9
11
|
let allFolders;
|
|
10
12
|
;
|
|
@@ -24,26 +26,38 @@ function walk(data) {
|
|
|
24
26
|
// Get list of files and folders inside this folder
|
|
25
27
|
const files = fs_1.default.readdirSync(folder).map(file => {
|
|
26
28
|
// Create path relative to root
|
|
27
|
-
const base =
|
|
29
|
+
const base = (0, norm_path_1.normAbsPath)(folder, file).replace(commonRoot, '.');
|
|
28
30
|
// Add trailing slash to mark directories
|
|
29
31
|
const isDir = fs_1.default.lstatSync(path_1.default.resolve(commonRoot, base)).isDirectory();
|
|
30
32
|
return isDir ? `${base}/` : base;
|
|
31
33
|
});
|
|
34
|
+
// Read and apply gitignores
|
|
35
|
+
const gitignoreFilename = (0, norm_path_1.normPath)(folder, '.gitignore');
|
|
36
|
+
if (fs_1.default.existsSync(gitignoreFilename)) {
|
|
37
|
+
const gitignoreContents = fs_1.default.readFileSync(gitignoreFilename, 'utf-8');
|
|
38
|
+
const ignoredPaths = (0, parse_gitignore_1.default)(gitignoreContents);
|
|
39
|
+
ignored.add(ignoredPaths);
|
|
40
|
+
}
|
|
41
|
+
// Add gitattributes if present
|
|
42
|
+
const gitattributesPath = (0, norm_path_1.normPath)(folder, '.gitattributes');
|
|
43
|
+
if (fs_1.default.existsSync(gitattributesPath)) {
|
|
44
|
+
allFiles.add(gitattributesPath);
|
|
45
|
+
}
|
|
32
46
|
// Loop through files and folders
|
|
33
47
|
for (const file of files) {
|
|
34
48
|
// Create absolute path for disc operations
|
|
35
|
-
const path =
|
|
49
|
+
const path = (0, norm_path_1.normAbsPath)(commonRoot, file);
|
|
36
50
|
const localPath = localRoot ? file.replace(`./${localRoot}/`, '') : file.replace('./', '');
|
|
37
51
|
// Skip if nonexistant
|
|
38
52
|
const nonExistant = !fs_1.default.existsSync(path);
|
|
39
53
|
if (nonExistant)
|
|
40
54
|
continue;
|
|
41
|
-
// Skip if marked
|
|
55
|
+
// Skip if marked in gitignore
|
|
42
56
|
const isIgnored = ignored.test(localPath).ignored;
|
|
43
57
|
if (isIgnored)
|
|
44
58
|
continue;
|
|
45
59
|
// Add absolute folder path to list
|
|
46
|
-
allFolders.add(
|
|
60
|
+
allFolders.add((0, norm_path_1.normAbsPath)(folder));
|
|
47
61
|
// Check if this is a folder or file
|
|
48
62
|
if (file.endsWith('/')) {
|
|
49
63
|
// Recurse into subfolders
|
package/dist/index.js
CHANGED
|
@@ -37,15 +37,18 @@ const load_data_1 = __importStar(require("./helpers/load-data"));
|
|
|
37
37
|
const read_file_1 = __importDefault(require("./helpers/read-file"));
|
|
38
38
|
const parse_gitattributes_1 = __importDefault(require("./helpers/parse-gitattributes"));
|
|
39
39
|
const convert_pcre_1 = __importDefault(require("./helpers/convert-pcre"));
|
|
40
|
+
const norm_path_1 = require("./helpers/norm-path");
|
|
40
41
|
async function analyse(rawPaths, opts = {}) {
|
|
41
|
-
var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m, _o, _p, _q;
|
|
42
|
-
var
|
|
42
|
+
var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m, _o, _p, _q, _r, _s, _t;
|
|
43
|
+
var _u, _v;
|
|
43
44
|
const useRawContent = opts.fileContent !== undefined;
|
|
44
45
|
const input = [rawPaths !== null && rawPaths !== void 0 ? rawPaths : []].flat();
|
|
45
46
|
const manualFileContent = [(_a = opts.fileContent) !== null && _a !== void 0 ? _a : []].flat();
|
|
46
47
|
// Normalise input option arguments
|
|
47
48
|
opts = {
|
|
49
|
+
calculateLines: (_b = opts.calculateLines) !== null && _b !== void 0 ? _b : true,
|
|
48
50
|
checkIgnored: !opts.quick,
|
|
51
|
+
checkDetected: !opts.quick,
|
|
49
52
|
checkAttributes: !opts.quick,
|
|
50
53
|
checkHeuristics: !opts.quick,
|
|
51
54
|
checkShebang: !opts.quick,
|
|
@@ -64,17 +67,15 @@ async function analyse(rawPaths, opts = {}) {
|
|
|
64
67
|
const extensions = {};
|
|
65
68
|
const globOverrides = {};
|
|
66
69
|
const results = {
|
|
67
|
-
files: { count: 0, bytes: 0, results: {}, alternatives: {} },
|
|
68
|
-
languages: { count: 0, bytes: 0, results: {} },
|
|
69
|
-
unknown: { count: 0, bytes: 0, extensions: {}, filenames: {} },
|
|
70
|
+
files: { count: 0, bytes: 0, lines: { total: 0, content: 0, code: 0 }, results: {}, alternatives: {} },
|
|
71
|
+
languages: { count: 0, bytes: 0, lines: { total: 0, content: 0, code: 0 }, results: {} },
|
|
72
|
+
unknown: { count: 0, bytes: 0, lines: { total: 0, content: 0, code: 0 }, extensions: {}, filenames: {} },
|
|
70
73
|
};
|
|
71
74
|
// Set a common root path so that vendor paths do not incorrectly match parent folders
|
|
72
|
-
const
|
|
73
|
-
const resolvedInput = input.map(path => normPath(path_1.default.resolve(path)));
|
|
75
|
+
const resolvedInput = input.map(path => (0, norm_path_1.normPath)(path_1.default.resolve(path)));
|
|
74
76
|
const commonRoot = (input.length > 1 ? (0, common_path_prefix_1.default)(resolvedInput) : resolvedInput[0]).replace(/\/?$/, '');
|
|
75
|
-
const
|
|
76
|
-
const
|
|
77
|
-
const unRelPath = (file) => useRawContent ? file : normPath(path_1.default.resolve(commonRoot, file));
|
|
77
|
+
const relPath = (file) => useRawContent ? file : (0, norm_path_1.normPath)(path_1.default.relative(commonRoot, file));
|
|
78
|
+
const unRelPath = (file) => useRawContent ? file : (0, norm_path_1.normPath)(path_1.default.resolve(commonRoot, file));
|
|
78
79
|
// Other helper functions
|
|
79
80
|
const fileMatchesGlobs = (file, ...globs) => (0, ignore_1.default)().add(globs).ignores(relPath(file));
|
|
80
81
|
const filterOutIgnored = (files, ignored) => ignored.filter(files.map(relPath)).map(unRelPath);
|
|
@@ -82,47 +83,40 @@ async function analyse(rawPaths, opts = {}) {
|
|
|
82
83
|
// Prepare list of ignored files
|
|
83
84
|
const ignored = (0, ignore_1.default)();
|
|
84
85
|
ignored.add('.git/');
|
|
85
|
-
ignored.add((
|
|
86
|
-
const regexIgnores = [];
|
|
87
|
-
if (!opts.keepVendored)
|
|
88
|
-
regexIgnores.push(...vendorPaths.map(path => RegExp(path, 'i')));
|
|
86
|
+
ignored.add((_c = opts.ignoredFiles) !== null && _c !== void 0 ? _c : []);
|
|
87
|
+
const regexIgnores = opts.keepVendored ? [] : vendorPaths.map(path => RegExp(path, 'i'));
|
|
89
88
|
// Load file paths and folders
|
|
90
89
|
let files;
|
|
91
|
-
let folders;
|
|
92
90
|
if (useRawContent) {
|
|
93
91
|
// Uses raw file content
|
|
94
92
|
files = input;
|
|
95
|
-
folders = [''];
|
|
96
93
|
}
|
|
97
94
|
else {
|
|
98
95
|
// Uses directory on disc
|
|
99
96
|
const data = (0, walk_tree_1.default)({ init: true, commonRoot, folderRoots: resolvedInput, folders: resolvedInput, ignored });
|
|
100
97
|
files = data.files;
|
|
101
|
-
folders = data.folders;
|
|
102
|
-
}
|
|
103
|
-
// Load gitignore data and apply ignores rules
|
|
104
|
-
if (!useRawContent && opts.checkIgnored) {
|
|
105
|
-
const nestedIgnoreFiles = files.filter(file => file.endsWith('.gitignore'));
|
|
106
|
-
for (const ignoresFile of nestedIgnoreFiles) {
|
|
107
|
-
const relIgnoresFile = relPath(ignoresFile);
|
|
108
|
-
const relIgnoresFolder = path_1.default.dirname(relIgnoresFile);
|
|
109
|
-
// Parse gitignores
|
|
110
|
-
const ignoresDataRaw = await (0, read_file_1.default)(ignoresFile);
|
|
111
|
-
const ignoresData = ignoresDataRaw.replace(/#.+|\s+$/gm, '');
|
|
112
|
-
const absoluteIgnoresData = ignoresData
|
|
113
|
-
// '.file' -> 'root/*/.file'
|
|
114
|
-
.replace(/^(?=[^\s\/\\])/gm, localRoot(relIgnoresFolder) + '/*/')
|
|
115
|
-
// '/folder' -> 'root/folder'
|
|
116
|
-
.replace(/^[\/\\]/gm, localRoot(relIgnoresFolder) + '/');
|
|
117
|
-
ignored.add(absoluteIgnoresData);
|
|
118
|
-
files = filterOutIgnored(files, ignored);
|
|
119
|
-
}
|
|
120
98
|
}
|
|
121
99
|
// Fetch and normalise gitattributes data of all subfolders and save to metadata
|
|
122
100
|
const manualAttributes = {}; // Maps file globs to gitattribute boolean flags
|
|
123
101
|
const getFlaggedGlobs = (attr, val) => {
|
|
124
102
|
return Object.entries(manualAttributes).filter(([, attrs]) => attrs[attr] === val).map(([glob,]) => glob);
|
|
125
103
|
};
|
|
104
|
+
const findAttrsForPath = (filePath) => {
|
|
105
|
+
const resultAttrs = {};
|
|
106
|
+
for (const glob in manualAttributes) {
|
|
107
|
+
if ((0, ignore_1.default)().add(glob).ignores(relPath(filePath))) {
|
|
108
|
+
const matchingAttrs = manualAttributes[glob];
|
|
109
|
+
for (const [attr, val] of Object.entries(matchingAttrs)) {
|
|
110
|
+
if (val !== null)
|
|
111
|
+
resultAttrs[attr] = val;
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
if (!JSON.stringify(resultAttrs)) {
|
|
116
|
+
return null;
|
|
117
|
+
}
|
|
118
|
+
return resultAttrs;
|
|
119
|
+
};
|
|
126
120
|
if (!useRawContent && opts.checkAttributes) {
|
|
127
121
|
const nestedAttrFiles = files.filter(file => file.endsWith('.gitattributes'));
|
|
128
122
|
for (const attrFile of nestedAttrFiles) {
|
|
@@ -135,6 +129,25 @@ async function analyse(rawPaths, opts = {}) {
|
|
|
135
129
|
}
|
|
136
130
|
}
|
|
137
131
|
}
|
|
132
|
+
// Remove files that are linguist-ignored via regex by default unless explicitly unignored in gitattributes
|
|
133
|
+
const filesToIgnore = [];
|
|
134
|
+
for (const file of files) {
|
|
135
|
+
const relFile = relPath(file);
|
|
136
|
+
const isRegexIgnored = regexIgnores.some(pattern => pattern.test(relFile));
|
|
137
|
+
if (!isRegexIgnored) {
|
|
138
|
+
// Checking overrides is moot if file is not even marked as ignored by default
|
|
139
|
+
continue;
|
|
140
|
+
}
|
|
141
|
+
const fileAttrs = findAttrsForPath(file);
|
|
142
|
+
if ((fileAttrs === null || fileAttrs === void 0 ? void 0 : fileAttrs.generated) === false || (fileAttrs === null || fileAttrs === void 0 ? void 0 : fileAttrs.vendored) === false) {
|
|
143
|
+
// File is explicitly marked as *not* to be ignored
|
|
144
|
+
// do nothing
|
|
145
|
+
}
|
|
146
|
+
else {
|
|
147
|
+
filesToIgnore.push(file);
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
files = files.filter(file => !filesToIgnore.includes(file));
|
|
138
151
|
// Apply vendor file path matches and filter out vendored files
|
|
139
152
|
if (!opts.keepVendored) {
|
|
140
153
|
// Get data of files that have been manually marked with metadata
|
|
@@ -165,7 +178,7 @@ async function analyse(rawPaths, opts = {}) {
|
|
|
165
178
|
files.push(...unignoredList);
|
|
166
179
|
}
|
|
167
180
|
// Ignore specific languages
|
|
168
|
-
for (const lang of (
|
|
181
|
+
for (const lang of (_d = opts.ignoredLanguages) !== null && _d !== void 0 ? _d : []) {
|
|
169
182
|
for (const key in langData) {
|
|
170
183
|
if (lang.toLowerCase() === key.toLowerCase()) {
|
|
171
184
|
delete langData[key];
|
|
@@ -218,7 +231,7 @@ async function analyse(rawPaths, opts = {}) {
|
|
|
218
231
|
// Check first line for readability
|
|
219
232
|
let firstLine;
|
|
220
233
|
if (useRawContent) {
|
|
221
|
-
firstLine = (
|
|
234
|
+
firstLine = (_f = (_e = manualFileContent[files.indexOf(file)]) === null || _e === void 0 ? void 0 : _e.split('\n')[0]) !== null && _f !== void 0 ? _f : null;
|
|
222
235
|
}
|
|
223
236
|
else if (fs_1.default.existsSync(file) && !fs_1.default.lstatSync(file).isDirectory()) {
|
|
224
237
|
firstLine = await (0, read_file_1.default)(file, true).catch(() => null);
|
|
@@ -237,7 +250,7 @@ async function analyse(rawPaths, opts = {}) {
|
|
|
237
250
|
const langMatcher = (lang) => `\\b${lang.toLowerCase().replace(/\W/g, '\\$&')}(?![\\w#+*]|-\*-)`;
|
|
238
251
|
// Check for interpreter match
|
|
239
252
|
if (opts.checkShebang && hasShebang) {
|
|
240
|
-
const matchesInterpretor = (
|
|
253
|
+
const matchesInterpretor = (_g = data.interpreters) === null || _g === void 0 ? void 0 : _g.some(interpreter => firstLine.match(`\\b${interpreter}\\b`));
|
|
241
254
|
if (matchesInterpretor)
|
|
242
255
|
matches.push(lang);
|
|
243
256
|
}
|
|
@@ -245,7 +258,7 @@ async function analyse(rawPaths, opts = {}) {
|
|
|
245
258
|
if (opts.checkModeline && hasModeline) {
|
|
246
259
|
const modelineText = firstLine.toLowerCase().replace(/^.*-\*-(.+)-\*-.*$/, '$1');
|
|
247
260
|
const matchesLang = modelineText.match(langMatcher(lang));
|
|
248
|
-
const matchesAlias = (
|
|
261
|
+
const matchesAlias = (_h = data.aliases) === null || _h === void 0 ? void 0 : _h.some(lang => modelineText.match(langMatcher(lang)));
|
|
249
262
|
if (matchesLang || matchesAlias)
|
|
250
263
|
matches.push(lang);
|
|
251
264
|
}
|
|
@@ -264,7 +277,7 @@ async function analyse(rawPaths, opts = {}) {
|
|
|
264
277
|
let skipExts = false;
|
|
265
278
|
// Check if filename is a match
|
|
266
279
|
for (const lang in langData) {
|
|
267
|
-
const matchesName = (
|
|
280
|
+
const matchesName = (_j = langData[lang].filenames) === null || _j === void 0 ? void 0 : _j.some(name => path_1.default.basename(file.toLowerCase()) === name.toLowerCase());
|
|
268
281
|
if (matchesName) {
|
|
269
282
|
addResult(file, lang);
|
|
270
283
|
skipExts = true;
|
|
@@ -274,7 +287,7 @@ async function analyse(rawPaths, opts = {}) {
|
|
|
274
287
|
const possibleExts = [];
|
|
275
288
|
if (!skipExts)
|
|
276
289
|
for (const lang in langData) {
|
|
277
|
-
const extMatches = (
|
|
290
|
+
const extMatches = (_k = langData[lang].extensions) === null || _k === void 0 ? void 0 : _k.filter(ext => file.toLowerCase().endsWith(ext.toLowerCase()));
|
|
278
291
|
if (extMatches === null || extMatches === void 0 ? void 0 : extMatches.length) {
|
|
279
292
|
for (const ext of extMatches)
|
|
280
293
|
possibleExts.push({ ext, lang });
|
|
@@ -320,7 +333,7 @@ async function analyse(rawPaths, opts = {}) {
|
|
|
320
333
|
heuristic.language = heuristic.language[0];
|
|
321
334
|
}
|
|
322
335
|
// Make sure the results includes this language
|
|
323
|
-
const languageGroup = (
|
|
336
|
+
const languageGroup = (_l = langData[heuristic.language]) === null || _l === void 0 ? void 0 : _l.group;
|
|
324
337
|
const matchesLang = fileAssociations[file].includes(heuristic.language);
|
|
325
338
|
const matchesParent = languageGroup && fileAssociations[file].includes(languageGroup);
|
|
326
339
|
if (!matchesLang && !matchesParent)
|
|
@@ -365,17 +378,23 @@ async function analyse(rawPaths, opts = {}) {
|
|
|
365
378
|
}
|
|
366
379
|
}
|
|
367
380
|
// Skip specified categories
|
|
368
|
-
if ((
|
|
381
|
+
if ((_m = opts.categories) === null || _m === void 0 ? void 0 : _m.length) {
|
|
369
382
|
const categories = ['data', 'markup', 'programming', 'prose'];
|
|
370
383
|
const hiddenCategories = categories.filter(cat => !opts.categories.includes(cat));
|
|
371
384
|
for (const [file, lang] of Object.entries(results.files.results)) {
|
|
372
|
-
|
|
385
|
+
// Skip if language is not hidden
|
|
386
|
+
if (!hiddenCategories.some(cat => { var _a; return lang && ((_a = langData[lang]) === null || _a === void 0 ? void 0 : _a.type) === cat; }))
|
|
373
387
|
continue;
|
|
388
|
+
// Skip if language is forced as detectable
|
|
389
|
+
if (opts.checkDetected) {
|
|
390
|
+
const detectable = (0, ignore_1.default)().add(getFlaggedGlobs('detectable', true));
|
|
391
|
+
if (detectable.ignores(relPath(file)))
|
|
392
|
+
continue;
|
|
374
393
|
}
|
|
394
|
+
// Delete result otherwise
|
|
375
395
|
delete results.files.results[file];
|
|
376
|
-
if (lang)
|
|
396
|
+
if (lang)
|
|
377
397
|
delete results.languages.results[lang];
|
|
378
|
-
}
|
|
379
398
|
}
|
|
380
399
|
for (const category of hiddenCategories) {
|
|
381
400
|
for (const [lang, { type }] of Object.entries(results.languages.results)) {
|
|
@@ -389,7 +408,7 @@ async function analyse(rawPaths, opts = {}) {
|
|
|
389
408
|
if (!useRawContent && opts.relativePaths) {
|
|
390
409
|
const newMap = {};
|
|
391
410
|
for (const [file, lang] of Object.entries(results.files.results)) {
|
|
392
|
-
let relPath = path_1.default.relative(process.cwd(), file)
|
|
411
|
+
let relPath = (0, norm_path_1.normPath)(path_1.default.relative(process.cwd(), file));
|
|
393
412
|
if (!relPath.startsWith('../')) {
|
|
394
413
|
relPath = './' + relPath;
|
|
395
414
|
}
|
|
@@ -401,26 +420,59 @@ async function analyse(rawPaths, opts = {}) {
|
|
|
401
420
|
for (const [file, lang] of Object.entries(results.files.results)) {
|
|
402
421
|
if (lang && !langData[lang])
|
|
403
422
|
continue;
|
|
404
|
-
|
|
423
|
+
// Calculate file size
|
|
424
|
+
const fileSize = (_p = (_o = manualFileContent[files.indexOf(file)]) === null || _o === void 0 ? void 0 : _o.length) !== null && _p !== void 0 ? _p : fs_1.default.statSync(file).size;
|
|
425
|
+
// Calculate lines of code
|
|
426
|
+
const loc = { total: 0, content: 0, code: 0 };
|
|
427
|
+
if (opts.calculateLines) {
|
|
428
|
+
const fileContent = (_r = ((_q = manualFileContent[files.indexOf(file)]) !== null && _q !== void 0 ? _q : fs_1.default.readFileSync(file).toString())) !== null && _r !== void 0 ? _r : '';
|
|
429
|
+
const allLines = fileContent.split(/\r?\n/gm);
|
|
430
|
+
loc.total = allLines.length;
|
|
431
|
+
loc.content = allLines.filter(line => line.trim().length > 0).length;
|
|
432
|
+
const codeLines = fileContent
|
|
433
|
+
.replace(/^\s*(\/\/|# |;|--).+/gm, '')
|
|
434
|
+
.replace(/\/\*.+\*\/|<!--.+-->/sg, '');
|
|
435
|
+
loc.code = codeLines.split(/\r?\n/gm).filter(line => line.trim().length > 0).length;
|
|
436
|
+
}
|
|
437
|
+
// Apply to files totals
|
|
405
438
|
results.files.bytes += fileSize;
|
|
406
|
-
|
|
407
|
-
|
|
439
|
+
results.files.lines.total += loc.total;
|
|
440
|
+
results.files.lines.content += loc.content;
|
|
441
|
+
results.files.lines.code += loc.code;
|
|
442
|
+
// Add results to 'languages' section if language match found, or 'unknown' section otherwise
|
|
443
|
+
if (lang) {
|
|
444
|
+
const { type } = langData[lang];
|
|
445
|
+
// set default if unset
|
|
446
|
+
(_s = (_u = results.languages.results)[lang]) !== null && _s !== void 0 ? _s : (_u[lang] = { type, bytes: 0, lines: { total: 0, content: 0, code: 0 }, color: langData[lang].color });
|
|
447
|
+
// apply results to 'languages' section
|
|
448
|
+
if (opts.childLanguages) {
|
|
449
|
+
results.languages.results[lang].parent = langData[lang].group;
|
|
450
|
+
}
|
|
451
|
+
results.languages.results[lang].bytes += fileSize;
|
|
452
|
+
results.languages.bytes += fileSize;
|
|
453
|
+
results.languages.results[lang].lines.total += loc.total;
|
|
454
|
+
results.languages.results[lang].lines.content += loc.content;
|
|
455
|
+
results.languages.results[lang].lines.code += loc.code;
|
|
456
|
+
results.languages.lines.total += loc.total;
|
|
457
|
+
results.languages.lines.content += loc.content;
|
|
458
|
+
results.languages.lines.code += loc.code;
|
|
459
|
+
}
|
|
460
|
+
else {
|
|
408
461
|
const ext = path_1.default.extname(file);
|
|
409
|
-
const unknownType = ext
|
|
410
|
-
const name = ext
|
|
411
|
-
|
|
462
|
+
const unknownType = ext ? 'extensions' : 'filenames';
|
|
463
|
+
const name = ext || path_1.default.basename(file);
|
|
464
|
+
// apply results to 'unknown' section
|
|
465
|
+
(_t = (_v = results.unknown[unknownType])[name]) !== null && _t !== void 0 ? _t : (_v[name] = 0);
|
|
412
466
|
results.unknown[unknownType][name] += fileSize;
|
|
413
467
|
results.unknown.bytes += fileSize;
|
|
414
|
-
|
|
468
|
+
results.unknown.lines.total += loc.total;
|
|
469
|
+
results.unknown.lines.content += loc.content;
|
|
470
|
+
results.unknown.lines.code += loc.code;
|
|
415
471
|
}
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
results.languages.results[lang].parent = langData[lang].group;
|
|
421
|
-
}
|
|
422
|
-
results.languages.results[lang].bytes += fileSize;
|
|
423
|
-
results.languages.bytes += fileSize;
|
|
472
|
+
}
|
|
473
|
+
// Set lines output to NaN when line calculation is disabled
|
|
474
|
+
if (opts.calculateLines === false) {
|
|
475
|
+
results.files.lines = { total: NaN, content: NaN, code: NaN };
|
|
424
476
|
}
|
|
425
477
|
// Set counts
|
|
426
478
|
results.files.count = Object.keys(results.files.results).length;
|
package/dist/types.d.ts
CHANGED
|
@@ -19,7 +19,9 @@ export interface Options {
|
|
|
19
19
|
childLanguages?: boolean;
|
|
20
20
|
quick?: boolean;
|
|
21
21
|
offline?: boolean;
|
|
22
|
+
calculateLines?: boolean;
|
|
22
23
|
checkIgnored?: boolean;
|
|
24
|
+
checkDetected?: boolean;
|
|
23
25
|
checkAttributes?: boolean;
|
|
24
26
|
checkHeuristics?: boolean;
|
|
25
27
|
checkShebang?: boolean;
|
|
@@ -29,6 +31,11 @@ export interface Results {
|
|
|
29
31
|
files: {
|
|
30
32
|
count: Integer;
|
|
31
33
|
bytes: Bytes;
|
|
34
|
+
lines: {
|
|
35
|
+
total: Integer;
|
|
36
|
+
content: Integer;
|
|
37
|
+
code: Integer;
|
|
38
|
+
};
|
|
32
39
|
/** Note: Results use slashes as delimiters even on Windows. */
|
|
33
40
|
results: Record<FilePath, LanguageResult>;
|
|
34
41
|
alternatives: Record<FilePath, LanguageResult[]>;
|
|
@@ -36,8 +43,18 @@ export interface Results {
|
|
|
36
43
|
languages: {
|
|
37
44
|
count: Integer;
|
|
38
45
|
bytes: Bytes;
|
|
46
|
+
lines: {
|
|
47
|
+
total: Integer;
|
|
48
|
+
content: Integer;
|
|
49
|
+
code: Integer;
|
|
50
|
+
};
|
|
39
51
|
results: Record<Language, {
|
|
40
52
|
bytes: Bytes;
|
|
53
|
+
lines: {
|
|
54
|
+
total: Integer;
|
|
55
|
+
content: Integer;
|
|
56
|
+
code: Integer;
|
|
57
|
+
};
|
|
41
58
|
type: Category;
|
|
42
59
|
parent?: Language;
|
|
43
60
|
color?: `#${string}`;
|
|
@@ -46,6 +63,11 @@ export interface Results {
|
|
|
46
63
|
unknown: {
|
|
47
64
|
count: Integer;
|
|
48
65
|
bytes: Bytes;
|
|
66
|
+
lines: {
|
|
67
|
+
total: Integer;
|
|
68
|
+
content: Integer;
|
|
69
|
+
code: Integer;
|
|
70
|
+
};
|
|
49
71
|
extensions: Record<string, Bytes>;
|
|
50
72
|
filenames: Record<string, Bytes>;
|
|
51
73
|
};
|
package/ext/generated.rb
CHANGED
|
@@ -8,18 +8,24 @@
|
|
|
8
8
|
- (Gopkg|glide)\.lock
|
|
9
9
|
- poetry\.lock
|
|
10
10
|
- pdm\.lock
|
|
11
|
+
- uv\.lock
|
|
11
12
|
- (^|\/)(\w+\.)?esy.lock$
|
|
13
|
+
- deno\.lock
|
|
12
14
|
- npm-shrinkwrap\.json
|
|
13
15
|
- package-lock\.json
|
|
16
|
+
- pnpm-lock\.yaml
|
|
14
17
|
- (^|\/)\.pnp\..*$
|
|
15
18
|
- Godeps\/
|
|
16
19
|
- composer\.lock
|
|
17
20
|
- .\.zep\.(?:c|h|php)$
|
|
18
21
|
- Cargo\.lock
|
|
22
|
+
- Cargo\.toml\.orig
|
|
19
23
|
- (^|\/)flake\.lock$
|
|
24
|
+
- (^|\/)MODULE\.bazel\.lock$
|
|
20
25
|
- Pipfile\.lock
|
|
21
26
|
- (?:^|\/)\.terraform\.lock\.hcl$
|
|
22
27
|
- ppport\.h$
|
|
23
28
|
- __generated__\/
|
|
24
29
|
- _tlb\.pas$
|
|
25
30
|
- (?:^|\/)htmlcov\/
|
|
31
|
+
- (?:^|.*\/)\.sqlx\/query-.+\.json$
|