linguist-js 2.3.1 → 2.4.1

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
@@ -27,6 +26,7 @@ commander_1.program
27
26
  .option('-I|--checkIgnored [bool]', 'Force the checking of gitignore files', true)
28
27
  .option('-H|--checkHeuristics [bool]', 'Apply heuristics to ambiguous languages', true)
29
28
  .option('-S|--checkShebang [bool]', 'Check shebang lines for explicit classification', true)
29
+ .option('-M|--checkModeline [bool]', 'Check modelines for explicit classification', true)
30
30
  .helpOption(`-h|--help`, 'Display this help message')
31
31
  .version(VERSION, '-v|--version', 'Display the installed version of linguist-js');
32
32
  commander_1.program.parse(process.argv);
@@ -53,13 +53,11 @@ if (args.analyze)
53
53
  const root = args.analyze === true ? '.' : args.analyze;
54
54
  const data = await (0, index_1.default)(root, args);
55
55
  const { files, languages, unknown } = data;
56
- // Get file count
57
- const totalFiles = (0, walk_tree_1.default)(root).files.length;
58
56
  // Print output
59
57
  if (!args.json) {
60
58
  const sortedEntries = Object.entries(languages.results).sort((a, b) => a[1].bytes < b[1].bytes ? +1 : -1);
61
59
  const totalBytes = languages.bytes;
62
- console.log(`\n Analysed ${files.bytes.toLocaleString()} B from ${totalFiles} files (${totalFiles - files.count} ignored) with linguist-js`);
60
+ console.log(`\n Analysed ${files.bytes.toLocaleString()} B from ${files.count} files with linguist-js`);
63
61
  console.log(`\n Language analysis results:`);
64
62
  let count = 0;
65
63
  if (sortedEntries.length === 0)
@@ -8,28 +8,51 @@ 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, gitignores, regexIgnores) {
12
+ // Walk tree of a folder
13
+ if (folders.length === 1) {
14
+ const folder = folders[0];
15
+ // Get list of files and folders inside this folder
16
+ const files = fs_1.default.readdirSync(folder).map(file => {
17
+ // Create path relative to root
18
+ const base = path_1.default.resolve(folder, file).replace(/\\/g, '/').replace(root, '.');
19
+ // Add trailing slash to mark directories
20
+ const isDir = fs_1.default.lstatSync(path_1.default.resolve(root, base)).isDirectory();
21
+ return isDir ? `${base}/` : base;
22
+ });
23
+ // Loop through files and folders
18
24
  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)))
25
+ // Create absolute path for disc operations
26
+ const path = path_1.default.resolve(root, file).replace(/\\/g, '/');
27
+ // Skip if nonexistant or ignored
28
+ const nonExistant = !fs_1.default.existsSync(path);
29
+ const isGitIgnored = gitignores.test(file.replace('./', '')).ignored;
30
+ const isRegexIgnored = regexIgnores.find(match => file.replace('./', '').match(match));
31
+ if (nonExistant || isGitIgnored || isRegexIgnored)
21
32
  continue;
22
- allFolders.add(folder.replace(/\\/g, '/'));
23
- if (fs_1.default.lstatSync(path).isDirectory()) {
33
+ // Add absolute folder path to list
34
+ allFolders.add(path_1.default.resolve(folder).replace(/\\/g, '/'));
35
+ // Check if this is a folder or file
36
+ if (file.endsWith('/')) {
37
+ // Recurse into subfolders
24
38
  allFolders.add(path);
25
- walk(path, ignored);
26
- continue;
39
+ walk(root, [path], gitignores, regexIgnores);
40
+ }
41
+ else {
42
+ // Add relative file path to list
43
+ allFiles.add(path);
27
44
  }
28
- allFiles.add(path);
29
45
  }
30
46
  }
47
+ // Recurse into all folders
48
+ else {
49
+ for (const path of folders) {
50
+ walk(root, [path], gitignores, regexIgnores);
51
+ }
52
+ }
53
+ // Return absolute files and folders lists
31
54
  return {
32
- files: [...allFiles],
55
+ files: [...allFiles].map(file => file.replace(/^\./, root)),
33
56
  folders: [...allFolders],
34
57
  };
35
58
  }
package/dist/index.js CHANGED
@@ -5,24 +5,30 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
5
5
  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
- const glob_to_regexp_1 = __importDefault(require("glob-to-regexp"));
8
+ const ignore_1 = __importDefault(require("ignore"));
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"));
12
13
  const load_data_1 = __importDefault(require("./helpers/load-data"));
13
14
  const read_file_1 = __importDefault(require("./helpers/read-file"));
14
15
  const convert_pcre_1 = __importDefault(require("./helpers/convert-pcre"));
15
- const convert_glob_1 = __importDefault(require("./helpers/convert-glob"));
16
16
  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;
17
+ var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m, _o, _p, _q, _r, _s;
18
+ var _t, _u, _v;
19
19
  const useRawContent = opts.fileContent !== undefined;
20
+ input = [input !== null && input !== void 0 ? input : []].flat();
21
+ opts.fileContent = [(_a = opts.fileContent) !== null && _a !== void 0 ? _a : []].flat();
22
+ // Load data from github-linguist web repo
20
23
  const langData = await (0, load_data_1.default)('languages.yml').then(js_yaml_1.default.load);
21
24
  const vendorData = await (0, load_data_1.default)('vendor.yml').then(js_yaml_1.default.load);
25
+ const docData = await (0, load_data_1.default)('documentation.yml').then(js_yaml_1.default.load);
22
26
  const heuristicsData = await (0, load_data_1.default)('heuristics.yml').then(js_yaml_1.default.load);
23
- 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);
27
+ 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 : []; });
28
+ const vendorPaths = [...vendorData, ...docData, ...generatedData];
29
+ // Setup main variables
25
30
  const fileAssociations = {};
31
+ const definiteness = {};
26
32
  const extensions = {};
27
33
  const overrides = {};
28
34
  const results = {
@@ -30,25 +36,43 @@ 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 gitignores = (0, ignore_1.default)();
41
+ const regexIgnores = [];
42
+ gitignores.add('/.git');
43
+ if (!opts.keepVendored)
44
+ regexIgnores.push(...vendorPaths.map(path => RegExp(path, 'i')));
45
+ if (opts.ignoredFiles)
46
+ gitignores.add(opts.ignoredFiles);
47
+ // Set a common root path so that vendor paths do not incorrectly match parent folders
48
+ const resolvedInput = input.map(path => path_1.default.resolve(path).replace(/\\/g, '/'));
49
+ const commonRoot = (input.length > 1 ? (0, common_path_prefix_1.default)(resolvedInput) : resolvedInput[0]).replace(/\/?$/, '');
50
+ const relPath = (file) => path_1.default.relative(commonRoot, file).replace(/\\/g, '/');
51
+ const unRelPath = (file) => path_1.default.resolve(commonRoot, file).replace(/\\/g, '/');
52
+ // Load file paths and folders
38
53
  let files, folders;
39
- if (opts.fileContent) {
40
- opts.fileContent = Array.isArray(opts.fileContent) ? opts.fileContent : [opts.fileContent];
41
- files = [`${input}`];
54
+ if (useRawContent) {
55
+ // Uses raw file content
56
+ files = input;
42
57
  folders = [''];
43
58
  }
44
59
  else {
45
- const data = (0, walk_tree_1.default)(input !== null && input !== void 0 ? input : '.', ignoredFiles);
46
- ({ files, folders } = data);
60
+ // Uses directory on disc
61
+ const data = (0, walk_tree_1.default)(commonRoot, input, gitignores, regexIgnores);
62
+ files = data.files;
63
+ folders = data.folders;
47
64
  }
48
65
  // Apply aliases
49
- opts = { checkIgnored: !opts.quick, checkAttributes: !opts.quick, checkHeuristics: !opts.quick, checkShebang: !opts.quick, ...opts };
66
+ opts = {
67
+ checkIgnored: !opts.quick,
68
+ checkAttributes: !opts.quick,
69
+ checkHeuristics: !opts.quick,
70
+ checkShebang: !opts.quick,
71
+ checkModeline: !opts.quick,
72
+ ...opts
73
+ };
50
74
  // Ignore specific languages
51
- for (const lang of (_c = opts.ignoredLanguages) !== null && _c !== void 0 ? _c : []) {
75
+ for (const lang of (_b = opts.ignoredLanguages) !== null && _b !== void 0 ? _b : []) {
52
76
  for (const key in langData) {
53
77
  if (lang.toLowerCase() === key.toLowerCase()) {
54
78
  delete langData[key];
@@ -56,40 +80,36 @@ async function analyse(input, opts = {}) {
56
80
  }
57
81
  }
58
82
  }
59
- // Load gitattributes
60
- const customIgnored = [];
61
- const customBinary = [];
62
- const customText = [];
63
- if (!useRawContent && !opts.quick) {
83
+ // Load gitignores and gitattributes
84
+ const customBinary = (0, ignore_1.default)();
85
+ const customText = (0, ignore_1.default)();
86
+ if (!useRawContent && opts.checkAttributes) {
64
87
  for (const folder of folders) {
65
88
  // Skip if folder is marked in gitattributes
66
- if (customIgnored.some(path => (0, convert_glob_1.default)(path).test(folder)))
89
+ if (relPath(folder) && gitignores.test(relPath(folder)).ignored)
67
90
  continue;
68
91
  // Parse gitignores
69
92
  const ignoresFile = path_1.default.join(folder, '.gitignore');
70
93
  if (opts.checkIgnored && fs_1.default.existsSync(ignoresFile)) {
71
94
  const ignoresData = await (0, read_file_1.default)(ignoresFile);
72
- const ignoresList = ignoresData.split(/\r?\n/).filter(line => line.trim() && !line.startsWith('#'));
73
- const ignoredPaths = ignoresList.map(path => (0, convert_glob_1.default)(path).source);
74
- customIgnored.push(...ignoredPaths);
95
+ gitignores.add(ignoresData);
75
96
  }
76
97
  // Parse gitattributes
77
98
  const attributesFile = path_1.default.join(folder, '.gitattributes');
78
99
  if (opts.checkAttributes && fs_1.default.existsSync(attributesFile)) {
79
100
  const attributesData = await (0, read_file_1.default)(attributesFile);
80
- const relPathToRegex = (path) => (0, convert_glob_1.default)(path).source.substr(1).replace(folder, '');
81
101
  // Explicit text/binary associations
82
102
  const contentTypeMatches = attributesData.matchAll(/^(\S+).*?(-?binary|-?text)(?!=auto)/gm);
83
103
  for (const [_line, path, type] of contentTypeMatches) {
84
104
  if (['text', '-binary'].includes(type))
85
- customText.push(relPathToRegex(path));
105
+ customText.add(path);
86
106
  if (['-text', 'binary'].includes(type))
87
- customBinary.push(relPathToRegex(path));
107
+ customBinary.add(path);
88
108
  }
89
109
  // Custom vendor options
90
110
  const vendorMatches = attributesData.matchAll(/^(\S+).*[^-]linguist-(vendored|generated|documentation)(?!=false)/gm);
91
111
  for (const [_line, path] of vendorMatches) {
92
- customIgnored.push(relPathToRegex(path));
112
+ gitignores.add(path);
93
113
  }
94
114
  // Custom file associations
95
115
  const customLangMatches = attributesData.matchAll(/^(\S+).*[^-]linguist-language=(\S+)/gm);
@@ -100,17 +120,22 @@ async function analyse(input, opts = {}) {
100
120
  if (overrideLang)
101
121
  forcedLang = overrideLang[0];
102
122
  }
103
- const fullPath = folder + (0, convert_glob_1.default)(path).source.substr(1);
123
+ const fullPath = relPath(folder) + '/' + path;
104
124
  overrides[fullPath] = forcedLang;
105
125
  }
106
126
  }
107
127
  }
108
128
  }
109
129
  // Check vendored files
110
- if (!useRawContent && !opts.keepVendored) {
130
+ if (!opts.keepVendored) {
111
131
  // Filter out any files that match a vendor file path
112
- const matcher = (match) => RegExp(match.replace(/\/$/, '/.+$').replace(/^\.\//, ''));
113
- files = files.filter(file => !customIgnored.some(pattern => matcher(pattern).test(file)));
132
+ if (useRawContent) {
133
+ files = gitignores.filter(files);
134
+ files = files.filter(file => !regexIgnores.find(match => match.test(file)));
135
+ }
136
+ else {
137
+ files = gitignores.filter(files.map(relPath)).map(unRelPath);
138
+ }
114
139
  }
115
140
  // Load all files and parse languages
116
141
  const addResult = (file, result) => {
@@ -126,8 +151,8 @@ async function analyse(input, opts = {}) {
126
151
  // List all languages that could be associated with a given file
127
152
  for (const file of files) {
128
153
  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;
154
+ if (useRawContent) {
155
+ 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
156
  }
132
157
  else {
133
158
  if (!fs_1.default.existsSync(file) || fs_1.default.lstatSync(file).isDirectory())
@@ -137,22 +162,40 @@ async function analyse(input, opts = {}) {
137
162
  // Skip if file is unreadable
138
163
  if (firstLine === null)
139
164
  continue;
140
- // Check shebang line for explicit classification
141
- if (!opts.quick && opts.checkShebang && firstLine.startsWith('#!')) {
142
- // Find matching interpreters
143
- const matches = Object.entries(langData).filter(([, data]) => { var _a; return (_a = data.interpreters) === null || _a === void 0 ? void 0 : _a.some(interpreter => firstLine.match('\\b' + interpreter + '\\b')); });
165
+ // Check first line for explicit classification
166
+ const hasShebang = opts.checkShebang && /^#!/.test(firstLine);
167
+ const hasModeline = opts.checkModeline && /-\*-|(syntax|filetype|ft)\s*=/.test(firstLine);
168
+ if (!opts.quick && (hasShebang || hasModeline)) {
169
+ const matches = [];
170
+ for (const [lang, data] of Object.entries(langData)) {
171
+ const langMatcher = (lang) => `\\b${lang.toLowerCase().replace(/\W/g, '\\$&')}(?![\\w#+*]|-\*-)`;
172
+ // Check for interpreter match
173
+ const matchesInterpretor = (_f = data.interpreters) === null || _f === void 0 ? void 0 : _f.some(interpreter => firstLine.match(`\\b${interpreter}\\b`));
174
+ // Check modeline declaration
175
+ const matchesLang = firstLine.toLowerCase().match(langMatcher(lang));
176
+ const matchesAlias = (_g = data.aliases) === null || _g === void 0 ? void 0 : _g.some(lang => firstLine.toLowerCase().match(langMatcher(lang)));
177
+ // Add language
178
+ if (opts.checkShebang && matchesInterpretor)
179
+ matches.push(lang);
180
+ if (opts.checkModeline && (matchesLang || matchesAlias))
181
+ matches.push(lang);
182
+ }
144
183
  if (matches.length) {
145
184
  // Add explicitly-identified language
146
- const forcedLang = matches[0][0];
185
+ const forcedLang = matches[0];
147
186
  addResult(file, forcedLang);
187
+ definiteness[file] = true;
188
+ continue;
148
189
  }
149
190
  }
150
191
  // Check override for manual language classification
151
192
  if (!useRawContent && !opts.quick && opts.checkAttributes) {
152
- const match = overridesArray.find(item => RegExp(item[0]).test(file));
193
+ const isOverridden = (path) => (0, ignore_1.default)().add(path).test(relPath(file)).ignored;
194
+ const match = overridesArray.find(item => isOverridden(item[0]));
153
195
  if (match) {
154
196
  const forcedLang = match[1];
155
197
  addResult(file, forcedLang);
198
+ definiteness[file] = true;
156
199
  continue;
157
200
  }
158
201
  }
@@ -160,7 +203,7 @@ async function analyse(input, opts = {}) {
160
203
  let skipExts = false;
161
204
  for (const lang in langData) {
162
205
  // 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());
206
+ const matchesName = (_h = langData[lang].filenames) === null || _h === void 0 ? void 0 : _h.some(name => path_1.default.basename(file.toLowerCase()) === name.toLowerCase());
164
207
  if (matchesName) {
165
208
  addResult(file, lang);
166
209
  skipExts = true;
@@ -169,7 +212,7 @@ async function analyse(input, opts = {}) {
169
212
  if (!skipExts)
170
213
  for (const lang in langData) {
171
214
  // 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()));
215
+ const matchesExt = (_j = langData[lang].extensions) === null || _j === void 0 ? void 0 : _j.some(ext => file.toLowerCase().endsWith(ext.toLowerCase()));
173
216
  if (matchesExt)
174
217
  addResult(file, lang);
175
218
  }
@@ -179,10 +222,15 @@ async function analyse(input, opts = {}) {
179
222
  }
180
223
  // Narrow down file associations to the best fit
181
224
  for (const file in fileAssociations) {
225
+ // Skip if file has explicit association
226
+ if (definiteness[file]) {
227
+ results.files.results[file] = fileAssociations[file][0];
228
+ continue;
229
+ }
182
230
  // Skip binary files
183
231
  if (!useRawContent && !opts.keepBinary) {
184
- const isCustomText = customText.some(path => RegExp(path).test(file));
185
- const isCustomBinary = customBinary.some(path => RegExp(path).test(file));
232
+ const isCustomText = customText.test(relPath(file)).ignored;
233
+ const isCustomBinary = customBinary.test(relPath(file)).ignored;
186
234
  const isBinaryExt = binary_extensions_1.default.some(ext => file.endsWith('.' + ext));
187
235
  if (!isCustomText && (isCustomBinary || isBinaryExt || await (0, isbinaryfile_1.isBinaryFile)(file))) {
188
236
  continue;
@@ -202,14 +250,14 @@ async function analyse(input, opts = {}) {
202
250
  heuristic.language = heuristic.language[0];
203
251
  }
204
252
  // Make sure the results includes this language
205
- const languageGroup = (_j = langData[heuristic.language]) === null || _j === void 0 ? void 0 : _j.group;
253
+ const languageGroup = (_k = langData[heuristic.language]) === null || _k === void 0 ? void 0 : _k.group;
206
254
  const matchesLang = fileAssociations[file].includes(heuristic.language);
207
255
  const matchesParent = languageGroup && fileAssociations[file].includes(languageGroup);
208
256
  if (!matchesLang && !matchesParent)
209
257
  continue;
210
258
  // Normalise heuristic data
211
259
  const patterns = [];
212
- const normalise = (contents) => patterns.push(...(Array.isArray(contents) ? contents : [contents]));
260
+ const normalise = (contents) => patterns.push(...[contents].flat());
213
261
  if (heuristic.pattern)
214
262
  normalise(heuristic.pattern);
215
263
  if (heuristic.named_pattern)
@@ -225,10 +273,10 @@ async function analyse(input, opts = {}) {
225
273
  }
226
274
  }
227
275
  // If no heuristics, assign a language
228
- (_k = (_s = results.files.results)[file]) !== null && _k !== void 0 ? _k : (_s[file] = fileAssociations[file][0]);
276
+ (_l = (_t = results.files.results)[file]) !== null && _l !== void 0 ? _l : (_t[file] = fileAssociations[file][0]);
229
277
  }
230
278
  // Skip specified categories
231
- if ((_l = opts.categories) === null || _l === void 0 ? void 0 : _l.length) {
279
+ if ((_m = opts.categories) === null || _m === void 0 ? void 0 : _m.length) {
232
280
  const categories = ['data', 'markup', 'programming', 'prose'];
233
281
  const hiddenCategories = categories.filter(cat => !opts.categories.includes(cat));
234
282
  for (const [file, lang] of Object.entries(results.files.results)) {
@@ -260,21 +308,21 @@ async function analyse(input, opts = {}) {
260
308
  for (const [file, lang] of Object.entries(results.files.results)) {
261
309
  if (lang && !langData[lang])
262
310
  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;
311
+ const fileSize = (_q = (_p = (_o = opts.fileContent) === null || _o === void 0 ? void 0 : _o[files.indexOf(file)]) === null || _p === void 0 ? void 0 : _p.length) !== null && _q !== void 0 ? _q : fs_1.default.statSync(file).size;
264
312
  results.files.bytes += fileSize;
265
313
  // If no language found, add extension in other section
266
314
  if (!lang) {
267
315
  const ext = path_1.default.extname(file);
268
316
  const unknownType = ext === '' ? 'filenames' : 'extensions';
269
317
  const name = ext === '' ? path_1.default.basename(file) : ext;
270
- (_q = (_t = results.unknown[unknownType])[name]) !== null && _q !== void 0 ? _q : (_t[name] = 0);
318
+ (_r = (_u = results.unknown[unknownType])[name]) !== null && _r !== void 0 ? _r : (_u[name] = 0);
271
319
  results.unknown[unknownType][name] += fileSize;
272
320
  results.unknown.bytes += fileSize;
273
321
  continue;
274
322
  }
275
323
  // Add language and bytes data to corresponding section
276
324
  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 });
325
+ (_s = (_v = results.languages.results)[lang]) !== null && _s !== void 0 ? _s : (_v[lang] = { type, bytes: 0, color: langData[lang].color });
278
326
  if (opts.childLanguages)
279
327
  results.languages.results[lang].parent = langData[lang].group;
280
328
  results.languages.results[lang].bytes += fileSize;
package/dist/types.d.ts CHANGED
@@ -18,6 +18,7 @@ export interface Options {
18
18
  checkAttributes?: boolean;
19
19
  checkHeuristics?: boolean;
20
20
  checkShebang?: boolean;
21
+ checkModeline?: boolean;
21
22
  }
22
23
  export interface Results {
23
24
  files: {
package/license.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # ISC License
2
2
 
3
- Copyright &copy; 2021 Nixinova
3
+ Copyright &copy; Nixinova 2021&ndash;2022
4
4
 
5
5
  Permission to use, copy, modify, and/or distribute this software for any
6
6
  purpose with or without fee is hereby granted, provided that the above
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "linguist-js",
3
- "version": "2.3.1",
3
+ "version": "2.4.1",
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,8 +38,9 @@
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
- "glob-to-regexp": "~0.4.1",
43
+ "ignore": "^5.2.0",
43
44
  "isbinaryfile": "^4.0.8",
44
45
  "js-yaml": "^4.1.0",
45
46
  "node-cache": "^5.1.2"
package/readme.md CHANGED
@@ -97,7 +97,6 @@ const { files, languages, unknown } = linguist(folder, options);
97
97
  Analyse the language of all files found in a folder.
98
98
  - `entry` (optional; string or string array):
99
99
  The folder(s) to analyse (defaults to `./`).
100
- Analyse multiple folders using the syntax `"{folder1,folder2,...}"`.
101
100
  - `opts` (optional; object):
102
101
  An object containing analyser options.
103
102
  - `fileContent` (string or string array):
@@ -113,21 +112,26 @@ const { files, languages, unknown } = linguist(folder, options);
113
112
  Whether to display sub-languages instead of their parents when possible (defaults to `false`).
114
113
  - `quick` (boolean):
115
114
  Whether to skip complex language analysis such as the checking of heuristics and gitattributes statements (defaults to `false`).
116
- Alias for `checkAttributes:false, checkIgnored:false, checkHeuristics:false, checkShebang:false`.
115
+ Alias for `checkAttributes:false, checkIgnored:false, checkHeuristics:false, checkShebang:false, checkModeline:false`.
117
116
  - `keepVendored` (boolean):
118
117
  Whether to keep vendored files (dependencies, etc) (defaults to `false`).
118
+ Does nothing when `fileContent` is set.
119
119
  - `keepBinary` (boolean):
120
120
  Whether binary files should be included in the output (defaults to `false`).
121
121
  - `relativePaths` (boolean):
122
122
  Change the absolute file paths in the output to be relative to the current working directory (defaults to `false`).
123
123
  - `checkAttributes` (boolean):
124
124
  Force the checking of `.gitattributes` files (defaults to `true` unless `quick` is set).
125
+ Does nothing when `fileContent` is set.
125
126
  - `checkIgnored` (boolean):
126
127
  Force the checking of `.gitignore` files (defaults to `true` unless `quick` is set).
128
+ Does nothing when `fileContent` is set.
127
129
  - `checkHeuristics` (boolean):
128
130
  Apply heuristics to ambiguous languages (defaults to `true` unless `quick` is set).
129
131
  - `checkShebang` (boolean):
130
132
  Check shebang (`#!`) lines for explicit language classification (defaults to `true` unless `quick` is set).
133
+ - `checkModeline` (boolean):
134
+ Check modelines for explicit language classification (defaults to `true` unless `quick` is set).
131
135
 
132
136
  ### Command-line
133
137
 
@@ -155,7 +159,7 @@ linguist --help
155
159
  Requires `--json` to be specified.
156
160
  - `--quick`:
157
161
  Whether to skip the checking of `.gitattributes` and `.gitignore` files for manual language classifications.
158
- Alias for `--checkAttributes=false --checkIgnored=false --checkHeuristics=false --checkShebang=false`.
162
+ Alias for `--checkAttributes=false --checkIgnored=false --checkHeuristics=false --checkShebang=false --checkModeline=false`.
159
163
  - `--keepVendored`:
160
164
  Whether to include vendored files (auto-generated files, dependencies folder, etc).
161
165
  - `--keepBinary`:
@@ -170,6 +174,8 @@ linguist --help
170
174
  Apply heuristics to ambiguous languages (use alongside `--quick` to overwrite).
171
175
  - `--checkShebang`:
172
176
  Check shebang (`#!`) lines for explicit classification (use alongside `--quick` to overwrite).
177
+ - `--checkModeline`:
178
+ Check modelines for explicit classification (use alongside `--quick` to overwrite).
173
179
  - `--help`:
174
180
  Display a help message.
175
181
  - `--version`:
@@ -1,11 +0,0 @@
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
- const glob_to_regexp_1 = __importDefault(require("glob-to-regexp"));
7
- function parseGitGlob(path) {
8
- const globPath = `**/${path}`.replace(/\\/g, '/').replace(/\[:(space|digit):\]/g, (_, val) => `\\${val[0]}`);
9
- return (0, glob_to_regexp_1.default)(globPath, { globstar: true, extended: true });
10
- }
11
- exports.default = parseGitGlob;