linguist-js 2.3.1 → 2.3.2

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 CHANGED
@@ -6,7 +6,6 @@ Object.defineProperty(exports, "__esModule", { value: true });
6
6
  const VERSION = require('../package.json').version;
7
7
  const commander_1 = require("commander");
8
8
  const index_1 = __importDefault(require("./index"));
9
- const walk_tree_1 = __importDefault(require("./helpers/walk-tree"));
10
9
  const colouredMsg = ([r, g, b], msg) => `\u001B[${38};2;${r};${g};${b}m${msg}${'\u001b[0m'}`;
11
10
  const hexToRgb = (hex) => [parseInt(hex.slice(1, 3), 16), parseInt(hex.slice(3, 5), 16), parseInt(hex.slice(5, 7), 16)];
12
11
  commander_1.program
@@ -53,13 +52,11 @@ if (args.analyze)
53
52
  const root = args.analyze === true ? '.' : args.analyze;
54
53
  const data = await (0, index_1.default)(root, args);
55
54
  const { files, languages, unknown } = data;
56
- // Get file count
57
- const totalFiles = (0, walk_tree_1.default)(root).files.length;
58
55
  // Print output
59
56
  if (!args.json) {
60
57
  const sortedEntries = Object.entries(languages.results).sort((a, b) => a[1].bytes < b[1].bytes ? +1 : -1);
61
58
  const totalBytes = languages.bytes;
62
- console.log(`\n Analysed ${files.bytes.toLocaleString()} B from ${totalFiles} files (${totalFiles - files.count} ignored) with linguist-js`);
59
+ console.log(`\n Analysed ${files.bytes.toLocaleString()} B from ${files.count} files with linguist-js`);
63
60
  console.log(`\n Language analysis results:`);
64
61
  let count = 0;
65
62
  if (sortedEntries.length === 0)
@@ -8,28 +8,50 @@ const path_1 = __importDefault(require("path"));
8
8
  const allFiles = new Set();
9
9
  const allFolders = new Set();
10
10
  /** Generate list of files in a directory. */
11
- function walk(folder, ignored = []) {
12
- if (Array.isArray(folder)) {
13
- for (const path of folder)
14
- walk(path, ignored);
15
- }
16
- else {
17
- const files = fs_1.default.readdirSync(folder);
11
+ function walk(root, folders, ignored = []) {
12
+ // Switch out paths that expect being in root
13
+ ignored = ignored.map(match => match.replace(/^\^/, '^\\./'));
14
+ // Walk tree of a folder
15
+ if (folders.length === 1) {
16
+ const folder = folders[0];
17
+ // Get list of files and folders inside this folder
18
+ const files = fs_1.default.readdirSync(folder).map(file => {
19
+ // Create path relative to root
20
+ const base = path_1.default.resolve(folder, file).replace(/\\/g, '/').replace(root, '.');
21
+ // Add trailing slash to mark directories
22
+ const isDir = fs_1.default.lstatSync(path_1.default.resolve(root, base)).isDirectory();
23
+ return isDir ? `${base}/` : base;
24
+ });
25
+ // Loop through files and folders
18
26
  for (const file of files) {
19
- const path = path_1.default.resolve(folder, file).replace(/\\/g, '/');
20
- if (!fs_1.default.existsSync(path) || ignored.some(pattern => pattern.test(path)))
27
+ // Create absolute path for disc operations
28
+ const path = path_1.default.resolve(root, file).replace(/\\/g, '/');
29
+ // Skip if nonexistant or ignored
30
+ if (!fs_1.default.existsSync(path) || ignored.some(pattern => RegExp(pattern).test(file)))
21
31
  continue;
22
- allFolders.add(folder.replace(/\\/g, '/'));
23
- if (fs_1.default.lstatSync(path).isDirectory()) {
32
+ // Add absolute folder path to list
33
+ allFolders.add(path_1.default.resolve(folder).replace(/\\/g, '/'));
34
+ // Check if this is a folder or file
35
+ if (file.endsWith('/')) {
36
+ // Recurse into subfolders
24
37
  allFolders.add(path);
25
- walk(path, ignored);
26
- continue;
38
+ walk(root, [path], ignored);
39
+ }
40
+ else {
41
+ // Add relative file path to list
42
+ allFiles.add(path);
27
43
  }
28
- allFiles.add(path);
29
44
  }
30
45
  }
46
+ // Recurse into all folders
47
+ else {
48
+ for (const path of folders) {
49
+ walk(root, [path], ignored);
50
+ }
51
+ }
52
+ // Return absolute files and folders lists
31
53
  return {
32
- files: [...allFiles],
54
+ files: [...allFiles].map(file => file.replace(/^\./, root)),
33
55
  folders: [...allFolders],
34
56
  };
35
57
  }
package/dist/index.js CHANGED
@@ -6,6 +6,7 @@ const fs_1 = __importDefault(require("fs"));
6
6
  const path_1 = __importDefault(require("path"));
7
7
  const js_yaml_1 = __importDefault(require("js-yaml"));
8
8
  const glob_to_regexp_1 = __importDefault(require("glob-to-regexp"));
9
+ const common_path_prefix_1 = __importDefault(require("common-path-prefix"));
9
10
  const binary_extensions_1 = __importDefault(require("binary-extensions"));
10
11
  const isbinaryfile_1 = require("isbinaryfile");
11
12
  const walk_tree_1 = __importDefault(require("./helpers/walk-tree"));
@@ -14,14 +15,19 @@ const read_file_1 = __importDefault(require("./helpers/read-file"));
14
15
  const convert_pcre_1 = __importDefault(require("./helpers/convert-pcre"));
15
16
  const convert_glob_1 = __importDefault(require("./helpers/convert-glob"));
16
17
  async function analyse(input, opts = {}) {
17
- var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m, _o, _p, _q, _r;
18
- var _s, _t, _u;
18
+ var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m, _o, _p, _q;
19
+ var _r, _s, _t;
19
20
  const useRawContent = opts.fileContent !== undefined;
21
+ input = [input !== null && input !== void 0 ? input : []].flat();
22
+ opts.fileContent = [(_a = opts.fileContent) !== null && _a !== void 0 ? _a : []].flat();
23
+ // Load data from github-linguist web repo
20
24
  const langData = await (0, load_data_1.default)('languages.yml').then(js_yaml_1.default.load);
21
25
  const vendorData = await (0, load_data_1.default)('vendor.yml').then(js_yaml_1.default.load);
26
+ const docData = await (0, load_data_1.default)('documentation.yml').then(js_yaml_1.default.load);
22
27
  const heuristicsData = await (0, load_data_1.default)('heuristics.yml').then(js_yaml_1.default.load);
23
28
  const generatedData = await (0, load_data_1.default)('generated.rb').then(text => { var _a; return (_a = text.match(/(?<=name\.match\(\/).+?(?=(?<!\\)\/\))/gm)) !== null && _a !== void 0 ? _a : []; });
24
- vendorData.push(...generatedData);
29
+ const vendorPaths = [...vendorData, ...docData, ...generatedData];
30
+ // Setup main variables
25
31
  const fileAssociations = {};
26
32
  const extensions = {};
27
33
  const overrides = {};
@@ -30,25 +36,34 @@ async function analyse(input, opts = {}) {
30
36
  languages: { count: 0, bytes: 0, results: {} },
31
37
  unknown: { count: 0, bytes: 0, extensions: {}, filenames: {} },
32
38
  };
33
- const ignoredFiles = [
34
- /\/\.git\//,
35
- opts.keepVendored ? [] : vendorData.map(path => RegExp(path)),
36
- (_b = (_a = opts.ignoredFiles) === null || _a === void 0 ? void 0 : _a.map(path => (0, glob_to_regexp_1.default)('*' + path + '*', { extended: true }))) !== null && _b !== void 0 ? _b : [],
37
- ].flat();
39
+ // Prepare list of ignored files
40
+ const ignoredFiles = ['(^|\\/)\\.git\\/'];
41
+ if (!opts.keepVendored) {
42
+ ignoredFiles.push(...vendorPaths);
43
+ }
44
+ if (opts.ignoredFiles) {
45
+ ignoredFiles.push(...opts.ignoredFiles.map(path => (0, glob_to_regexp_1.default)('*' + path + '*', { extended: true }).source));
46
+ }
47
+ // Load file paths and folders
38
48
  let files, folders;
39
- if (opts.fileContent) {
40
- opts.fileContent = Array.isArray(opts.fileContent) ? opts.fileContent : [opts.fileContent];
41
- files = [`${input}`];
49
+ if (useRawContent) {
50
+ // Uses raw file content
51
+ files = input;
42
52
  folders = [''];
43
53
  }
44
54
  else {
45
- const data = (0, walk_tree_1.default)(input !== null && input !== void 0 ? input : '.', ignoredFiles);
46
- ({ files, folders } = data);
55
+ // Uses directory on disc
56
+ // Set a common root path so that vendor paths do not incorrectly match parent folders
57
+ const resolvedInput = input.map(path => path_1.default.resolve(path).replace(/\\/g, '/'));
58
+ const commonRoot = (input.length > 1 ? (0, common_path_prefix_1.default)(resolvedInput) : resolvedInput[0]).replace(/\/?$/, '');
59
+ const data = (0, walk_tree_1.default)(commonRoot, input, ignoredFiles);
60
+ files = data.files;
61
+ folders = data.folders;
47
62
  }
48
63
  // Apply aliases
49
64
  opts = { checkIgnored: !opts.quick, checkAttributes: !opts.quick, checkHeuristics: !opts.quick, checkShebang: !opts.quick, ...opts };
50
65
  // Ignore specific languages
51
- for (const lang of (_c = opts.ignoredLanguages) !== null && _c !== void 0 ? _c : []) {
66
+ for (const lang of (_b = opts.ignoredLanguages) !== null && _b !== void 0 ? _b : []) {
52
67
  for (const key in langData) {
53
68
  if (lang.toLowerCase() === key.toLowerCase()) {
54
69
  delete langData[key];
@@ -60,7 +75,7 @@ async function analyse(input, opts = {}) {
60
75
  const customIgnored = [];
61
76
  const customBinary = [];
62
77
  const customText = [];
63
- if (!useRawContent && !opts.quick) {
78
+ if (!useRawContent && opts.checkAttributes) {
64
79
  for (const folder of folders) {
65
80
  // Skip if folder is marked in gitattributes
66
81
  if (customIgnored.some(path => (0, convert_glob_1.default)(path).test(folder)))
@@ -126,8 +141,8 @@ async function analyse(input, opts = {}) {
126
141
  // List all languages that could be associated with a given file
127
142
  for (const file of files) {
128
143
  let firstLine;
129
- if (opts.fileContent) {
130
- firstLine = (_f = (_e = (_d = opts.fileContent) === null || _d === void 0 ? void 0 : _d[files.indexOf(file)]) === null || _e === void 0 ? void 0 : _e.split('\n')[0]) !== null && _f !== void 0 ? _f : null;
144
+ if (useRawContent) {
145
+ firstLine = (_e = (_d = (_c = opts.fileContent) === null || _c === void 0 ? void 0 : _c[files.indexOf(file)]) === null || _d === void 0 ? void 0 : _d.split('\n')[0]) !== null && _e !== void 0 ? _e : null;
131
146
  }
132
147
  else {
133
148
  if (!fs_1.default.existsSync(file) || fs_1.default.lstatSync(file).isDirectory())
@@ -160,7 +175,7 @@ async function analyse(input, opts = {}) {
160
175
  let skipExts = false;
161
176
  for (const lang in langData) {
162
177
  // Check if filename is a match
163
- const matchesName = (_g = langData[lang].filenames) === null || _g === void 0 ? void 0 : _g.some(name => path_1.default.basename(file.toLowerCase()) === name.toLowerCase());
178
+ const matchesName = (_f = langData[lang].filenames) === null || _f === void 0 ? void 0 : _f.some(name => path_1.default.basename(file.toLowerCase()) === name.toLowerCase());
164
179
  if (matchesName) {
165
180
  addResult(file, lang);
166
181
  skipExts = true;
@@ -169,7 +184,7 @@ async function analyse(input, opts = {}) {
169
184
  if (!skipExts)
170
185
  for (const lang in langData) {
171
186
  // Check if extension is a match
172
- const matchesExt = (_h = langData[lang].extensions) === null || _h === void 0 ? void 0 : _h.some(ext => file.toLowerCase().endsWith(ext.toLowerCase()));
187
+ const matchesExt = (_g = langData[lang].extensions) === null || _g === void 0 ? void 0 : _g.some(ext => file.toLowerCase().endsWith(ext.toLowerCase()));
173
188
  if (matchesExt)
174
189
  addResult(file, lang);
175
190
  }
@@ -202,14 +217,14 @@ async function analyse(input, opts = {}) {
202
217
  heuristic.language = heuristic.language[0];
203
218
  }
204
219
  // Make sure the results includes this language
205
- const languageGroup = (_j = langData[heuristic.language]) === null || _j === void 0 ? void 0 : _j.group;
220
+ const languageGroup = (_h = langData[heuristic.language]) === null || _h === void 0 ? void 0 : _h.group;
206
221
  const matchesLang = fileAssociations[file].includes(heuristic.language);
207
222
  const matchesParent = languageGroup && fileAssociations[file].includes(languageGroup);
208
223
  if (!matchesLang && !matchesParent)
209
224
  continue;
210
225
  // Normalise heuristic data
211
226
  const patterns = [];
212
- const normalise = (contents) => patterns.push(...(Array.isArray(contents) ? contents : [contents]));
227
+ const normalise = (contents) => patterns.push(...[contents].flat());
213
228
  if (heuristic.pattern)
214
229
  normalise(heuristic.pattern);
215
230
  if (heuristic.named_pattern)
@@ -225,10 +240,10 @@ async function analyse(input, opts = {}) {
225
240
  }
226
241
  }
227
242
  // If no heuristics, assign a language
228
- (_k = (_s = results.files.results)[file]) !== null && _k !== void 0 ? _k : (_s[file] = fileAssociations[file][0]);
243
+ (_j = (_r = results.files.results)[file]) !== null && _j !== void 0 ? _j : (_r[file] = fileAssociations[file][0]);
229
244
  }
230
245
  // Skip specified categories
231
- if ((_l = opts.categories) === null || _l === void 0 ? void 0 : _l.length) {
246
+ if ((_k = opts.categories) === null || _k === void 0 ? void 0 : _k.length) {
232
247
  const categories = ['data', 'markup', 'programming', 'prose'];
233
248
  const hiddenCategories = categories.filter(cat => !opts.categories.includes(cat));
234
249
  for (const [file, lang] of Object.entries(results.files.results)) {
@@ -260,21 +275,21 @@ async function analyse(input, opts = {}) {
260
275
  for (const [file, lang] of Object.entries(results.files.results)) {
261
276
  if (lang && !langData[lang])
262
277
  continue;
263
- const fileSize = (_p = (_o = (_m = opts.fileContent) === null || _m === void 0 ? void 0 : _m[files.indexOf(file)]) === null || _o === void 0 ? void 0 : _o.length) !== null && _p !== void 0 ? _p : fs_1.default.statSync(file).size;
278
+ const fileSize = (_o = (_m = (_l = opts.fileContent) === null || _l === void 0 ? void 0 : _l[files.indexOf(file)]) === null || _m === void 0 ? void 0 : _m.length) !== null && _o !== void 0 ? _o : fs_1.default.statSync(file).size;
264
279
  results.files.bytes += fileSize;
265
280
  // If no language found, add extension in other section
266
281
  if (!lang) {
267
282
  const ext = path_1.default.extname(file);
268
283
  const unknownType = ext === '' ? 'filenames' : 'extensions';
269
284
  const name = ext === '' ? path_1.default.basename(file) : ext;
270
- (_q = (_t = results.unknown[unknownType])[name]) !== null && _q !== void 0 ? _q : (_t[name] = 0);
285
+ (_p = (_s = results.unknown[unknownType])[name]) !== null && _p !== void 0 ? _p : (_s[name] = 0);
271
286
  results.unknown[unknownType][name] += fileSize;
272
287
  results.unknown.bytes += fileSize;
273
288
  continue;
274
289
  }
275
290
  // Add language and bytes data to corresponding section
276
291
  const { type } = langData[lang];
277
- (_r = (_u = results.languages.results)[lang]) !== null && _r !== void 0 ? _r : (_u[lang] = { type, bytes: 0, color: langData[lang].color });
292
+ (_q = (_t = results.languages.results)[lang]) !== null && _q !== void 0 ? _q : (_t[lang] = { type, bytes: 0, color: langData[lang].color });
278
293
  if (opts.childLanguages)
279
294
  results.languages.results[lang].parent = langData[lang].group;
280
295
  results.languages.results[lang].bytes += fileSize;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "linguist-js",
3
- "version": "2.3.1",
3
+ "version": "2.3.2",
4
4
  "description": "Analyse languages used in a folder. Powered by GitHub Linguist, although it doesn't need to be installed.",
5
5
  "main": "dist/index.js",
6
6
  "bin": {
@@ -38,6 +38,7 @@
38
38
  "dependencies": {
39
39
  "binary-extensions": "^2.2.0",
40
40
  "commander": "^9.0.0",
41
+ "common-path-prefix": "^3.0.0",
41
42
  "cross-fetch": "^3.1.5",
42
43
  "glob-to-regexp": "~0.4.1",
43
44
  "isbinaryfile": "^4.0.8",