linguist-js 2.5.5 → 2.5.6

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/index.js CHANGED
@@ -1,375 +1,375 @@
1
- "use strict";
2
- var __importDefault = (this && this.__importDefault) || function (mod) {
3
- return (mod && mod.__esModule) ? mod : { "default": mod };
4
- };
5
- const fs_1 = __importDefault(require("fs"));
6
- const path_1 = __importDefault(require("path"));
7
- const js_yaml_1 = __importDefault(require("js-yaml"));
8
- const ignore_1 = __importDefault(require("ignore"));
9
- const common_path_prefix_1 = __importDefault(require("common-path-prefix"));
10
- const binary_extensions_1 = __importDefault(require("binary-extensions"));
11
- const isbinaryfile_1 = require("isbinaryfile");
12
- const walk_tree_1 = __importDefault(require("./helpers/walk-tree"));
13
- const load_data_1 = __importDefault(require("./helpers/load-data"));
14
- const read_file_1 = __importDefault(require("./helpers/read-file"));
15
- const convert_pcre_1 = __importDefault(require("./helpers/convert-pcre"));
16
- async function analyse(input, opts = {}) {
17
- var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m, _o, _p, _q, _r, _s, _t;
18
- var _u, _v, _w;
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
23
- const langData = await (0, load_data_1.default)('languages.yml', opts.offline).then(js_yaml_1.default.load);
24
- const vendorData = await (0, load_data_1.default)('vendor.yml', opts.offline).then(js_yaml_1.default.load);
25
- const docData = await (0, load_data_1.default)('documentation.yml', opts.offline).then(js_yaml_1.default.load);
26
- const heuristicsData = await (0, load_data_1.default)('heuristics.yml', opts.offline).then(js_yaml_1.default.load);
27
- const generatedData = await (0, load_data_1.default)('generated.rb', opts.offline).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
30
- const fileAssociations = {};
31
- const extensions = {};
32
- const overrides = {};
33
- const results = {
34
- files: { count: 0, bytes: 0, results: {} },
35
- languages: { count: 0, bytes: 0, results: {} },
36
- unknown: { count: 0, bytes: 0, extensions: {}, filenames: {} },
37
- };
38
- // Prepare list of ignored files
39
- const gitignores = (0, ignore_1.default)();
40
- const regexIgnores = [];
41
- gitignores.add('/.git');
42
- if (!opts.keepVendored)
43
- regexIgnores.push(...vendorPaths.map(path => RegExp(path, 'i')));
44
- if (opts.ignoredFiles)
45
- gitignores.add(opts.ignoredFiles);
46
- // Set a common root path so that vendor paths do not incorrectly match parent folders
47
- const resolvedInput = input.map(path => path_1.default.resolve(path).replace(/\\/g, '/'));
48
- const commonRoot = (input.length > 1 ? (0, common_path_prefix_1.default)(resolvedInput) : resolvedInput[0]).replace(/\/?$/, '');
49
- const relPath = (file) => path_1.default.relative(commonRoot, file).replace(/\\/g, '/');
50
- const unRelPath = (file) => path_1.default.resolve(commonRoot, file).replace(/\\/g, '/');
51
- // Load file paths and folders
52
- let files, folders;
53
- if (useRawContent) {
54
- // Uses raw file content
55
- files = input;
56
- folders = [''];
57
- }
58
- else {
59
- // Uses directory on disc
60
- const data = (0, walk_tree_1.default)(true, commonRoot, input, gitignores, regexIgnores);
61
- files = data.files;
62
- folders = data.folders;
63
- }
64
- // Apply aliases
65
- opts = {
66
- checkIgnored: !opts.quick,
67
- checkAttributes: !opts.quick,
68
- checkHeuristics: !opts.quick,
69
- checkShebang: !opts.quick,
70
- checkModeline: !opts.quick,
71
- ...opts
72
- };
73
- // Ignore specific languages
74
- for (const lang of (_b = opts.ignoredLanguages) !== null && _b !== void 0 ? _b : []) {
75
- for (const key in langData) {
76
- if (lang.toLowerCase() === key.toLowerCase()) {
77
- delete langData[key];
78
- break;
79
- }
80
- }
81
- }
82
- // Load gitignores and gitattributes
83
- const customBinary = (0, ignore_1.default)();
84
- const customText = (0, ignore_1.default)();
85
- if (!useRawContent && opts.checkAttributes) {
86
- for (const folder of folders) {
87
- // Skip if folder is marked in gitattributes
88
- if (relPath(folder) && gitignores.ignores(relPath(folder))) {
89
- continue;
90
- }
91
- // Parse gitignores
92
- const ignoresFile = path_1.default.join(folder, '.gitignore');
93
- if (opts.checkIgnored && fs_1.default.existsSync(ignoresFile)) {
94
- const ignoresData = await (0, read_file_1.default)(ignoresFile);
95
- gitignores.add(ignoresData);
96
- }
97
- // Parse gitattributes
98
- const attributesFile = path_1.default.join(folder, '.gitattributes');
99
- if (opts.checkAttributes && fs_1.default.existsSync(attributesFile)) {
100
- const attributesData = await (0, read_file_1.default)(attributesFile);
101
- // Explicit text/binary associations
102
- const contentTypeMatches = attributesData.matchAll(/^(\S+).*?(-?binary|-?text)(?!=auto)/gm);
103
- for (const [_line, path, type] of contentTypeMatches) {
104
- if (['text', '-binary'].includes(type)) {
105
- customText.add(path);
106
- }
107
- if (['-text', 'binary'].includes(type)) {
108
- customBinary.add(path);
109
- }
110
- }
111
- // Custom vendor options
112
- const vendorMatches = attributesData.matchAll(/^(\S+).*[^-]linguist-(vendored|generated|documentation)(?!=false)/gm);
113
- for (const [_line, path] of vendorMatches) {
114
- gitignores.add(path);
115
- }
116
- // Custom file associations
117
- const customLangMatches = attributesData.matchAll(/^(\S+).*[^-]linguist-language=(\S+)/gm);
118
- for (let [_line, path, forcedLang] of customLangMatches) {
119
- // If specified language is an alias, associate it with its full name
120
- if (!langData[forcedLang]) {
121
- const overrideLang = Object.entries(langData).find(entry => { var _a; return (_a = entry[1].aliases) === null || _a === void 0 ? void 0 : _a.includes(forcedLang.toLowerCase()); });
122
- if (overrideLang) {
123
- forcedLang = overrideLang[0];
124
- }
125
- }
126
- const fullPath = path_1.default.join(relPath(folder), path);
127
- overrides[fullPath] = forcedLang;
128
- }
129
- }
130
- }
131
- }
132
- // Check vendored files
133
- if (!opts.keepVendored) {
134
- // Filter out any files that match a vendor file path
135
- if (useRawContent) {
136
- files = gitignores.filter(files);
137
- files = files.filter(file => !regexIgnores.find(match => match.test(file)));
138
- }
139
- else {
140
- files = gitignores.filter(files.map(relPath)).map(unRelPath);
141
- }
142
- }
143
- // Load all files and parse languages
144
- const addResult = (file, result) => {
145
- if (!fileAssociations[file]) {
146
- fileAssociations[file] = [];
147
- extensions[file] = '';
148
- }
149
- const parent = !opts.childLanguages && result && langData[result].group || false;
150
- fileAssociations[file].push(parent || result);
151
- extensions[file] = path_1.default.extname(file).toLowerCase();
152
- };
153
- const overridesArray = Object.entries(overrides);
154
- // List all languages that could be associated with a given file
155
- const definiteness = {};
156
- const fromShebang = {};
157
- for (const file of files) {
158
- let firstLine;
159
- if (useRawContent) {
160
- 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;
161
- }
162
- else if (fs_1.default.existsSync(file) && !fs_1.default.lstatSync(file).isDirectory()) {
163
- firstLine = await (0, read_file_1.default)(file, true).catch(() => null);
164
- }
165
- else
166
- continue;
167
- // Skip if file is unreadable
168
- if (firstLine === null)
169
- continue;
170
- // Check first line for explicit classification
171
- const hasShebang = opts.checkShebang && /^#!/.test(firstLine);
172
- const hasModeline = opts.checkModeline && /-\*-|(syntax|filetype|ft)\s*=/.test(firstLine);
173
- if (!opts.quick && (hasShebang || hasModeline)) {
174
- const matches = [];
175
- for (const [lang, data] of Object.entries(langData)) {
176
- const langMatcher = (lang) => `\\b${lang.toLowerCase().replace(/\W/g, '\\$&')}(?![\\w#+*]|-\*-)`;
177
- // Check for interpreter match
178
- if (opts.checkShebang && hasShebang) {
179
- const matchesInterpretor = (_f = data.interpreters) === null || _f === void 0 ? void 0 : _f.some(interpreter => firstLine.match(`\\b${interpreter}\\b`));
180
- if (matchesInterpretor)
181
- matches.push(lang);
182
- }
183
- // Check modeline declaration
184
- if (opts.checkModeline && hasModeline) {
185
- const modelineText = firstLine.toLowerCase().replace(/^.*-\*-(.+)-\*-.*$/, '$1');
186
- const matchesLang = modelineText.match(langMatcher(lang));
187
- const matchesAlias = (_g = data.aliases) === null || _g === void 0 ? void 0 : _g.some(lang => modelineText.match(langMatcher(lang)));
188
- if (matchesLang || matchesAlias)
189
- matches.push(lang);
190
- }
191
- }
192
- // Add identified language(s)
193
- if (matches.length) {
194
- for (const match of matches)
195
- addResult(file, match);
196
- if (matches.length === 1)
197
- definiteness[file] = true;
198
- fromShebang[file] = true;
199
- continue;
200
- }
201
- }
202
- // Check override for manual language classification
203
- if (!useRawContent && !opts.quick && opts.checkAttributes) {
204
- const isOverridden = (path) => (0, ignore_1.default)().add(path).ignores(relPath(file));
205
- const match = overridesArray.find(item => isOverridden(item[0]));
206
- if (match) {
207
- const forcedLang = match[1];
208
- addResult(file, forcedLang);
209
- definiteness[file] = true;
210
- continue;
211
- }
212
- }
213
- // Search each language
214
- let skipExts = false;
215
- // Check if filename is a match
216
- for (const lang in langData) {
217
- const matchesName = (_h = langData[lang].filenames) === null || _h === void 0 ? void 0 : _h.some(name => path_1.default.basename(file.toLowerCase()) === name.toLowerCase());
218
- if (matchesName) {
219
- addResult(file, lang);
220
- skipExts = true;
221
- }
222
- }
223
- // Check if extension is a match
224
- const possibleExts = [];
225
- if (!skipExts)
226
- for (const lang in langData) {
227
- const extMatches = (_j = langData[lang].extensions) === null || _j === void 0 ? void 0 : _j.filter(ext => file.toLowerCase().endsWith(ext.toLowerCase()));
228
- if (extMatches === null || extMatches === void 0 ? void 0 : extMatches.length) {
229
- for (const ext of extMatches)
230
- possibleExts.push({ ext, lang });
231
- }
232
- }
233
- // Apply more specific extension if available
234
- const isComplexExt = (ext) => /\..+\./.test(ext);
235
- const hasComplexExt = possibleExts.some(data => isComplexExt(data.ext));
236
- for (const { ext, lang } of possibleExts) {
237
- if (hasComplexExt && !isComplexExt(ext))
238
- continue;
239
- if (!hasComplexExt && isComplexExt(ext))
240
- continue;
241
- addResult(file, lang);
242
- }
243
- // Fallback to null if no language matches
244
- if (!fileAssociations[file]) {
245
- addResult(file, null);
246
- }
247
- }
248
- // Narrow down file associations to the best fit
249
- for (const file in fileAssociations) {
250
- // Skip if file has explicit association
251
- if (definiteness[file]) {
252
- results.files.results[file] = fileAssociations[file][0];
253
- continue;
254
- }
255
- // Skip binary files
256
- if (!useRawContent && !opts.keepBinary) {
257
- const isCustomText = customText.ignores(relPath(file));
258
- const isCustomBinary = customBinary.ignores(relPath(file));
259
- const isBinaryExt = binary_extensions_1.default.some(ext => file.endsWith('.' + ext));
260
- if (!isCustomText && (isCustomBinary || isBinaryExt || await (0, isbinaryfile_1.isBinaryFile)(file))) {
261
- continue;
262
- }
263
- }
264
- // Parse heuristics if applicable
265
- if (opts.checkHeuristics)
266
- for (const heuristics of heuristicsData.disambiguations) {
267
- // Make sure the extension matches the current file
268
- if (!fromShebang[file] && !heuristics.extensions.includes(extensions[file]))
269
- continue;
270
- // Load heuristic rules
271
- for (const heuristic of heuristics.rules) {
272
- // Make sure the language is not an array
273
- if (Array.isArray(heuristic.language)) {
274
- heuristic.language = heuristic.language[0];
275
- }
276
- // Make sure the results includes this language
277
- const languageGroup = (_k = langData[heuristic.language]) === null || _k === void 0 ? void 0 : _k.group;
278
- const matchesLang = fileAssociations[file].includes(heuristic.language);
279
- const matchesParent = languageGroup && fileAssociations[file].includes(languageGroup);
280
- if (!matchesLang && !matchesParent)
281
- continue;
282
- // Normalise heuristic data
283
- const patterns = [];
284
- const normalise = (contents) => patterns.push(...[contents].flat());
285
- if (heuristic.pattern)
286
- normalise(heuristic.pattern);
287
- if (heuristic.named_pattern)
288
- normalise(heuristicsData.named_patterns[heuristic.named_pattern]);
289
- if (heuristic.and) {
290
- for (const data of heuristic.and) {
291
- if (data.pattern)
292
- normalise(data.pattern);
293
- if (data.named_pattern)
294
- normalise(heuristicsData.named_patterns[data.named_pattern]);
295
- }
296
- }
297
- // Check file contents and apply heuristic patterns
298
- const fileContent = ((_l = opts.fileContent) === null || _l === void 0 ? void 0 : _l.length) ? opts.fileContent[files.indexOf(file)] : await (0, read_file_1.default)(file).catch(() => null);
299
- if (fileContent === null)
300
- continue;
301
- if (!patterns.length || patterns.some(pattern => (0, convert_pcre_1.default)(pattern).test(fileContent))) {
302
- results.files.results[file] = heuristic.language;
303
- break;
304
- }
305
- }
306
- }
307
- // If no heuristics, assign a language
308
- (_m = (_u = results.files.results)[file]) !== null && _m !== void 0 ? _m : (_u[file] = fileAssociations[file][0]);
309
- }
310
- // Skip specified categories
311
- if ((_o = opts.categories) === null || _o === void 0 ? void 0 : _o.length) {
312
- const categories = ['data', 'markup', 'programming', 'prose'];
313
- const hiddenCategories = categories.filter(cat => !opts.categories.includes(cat));
314
- for (const [file, lang] of Object.entries(results.files.results)) {
315
- if (!hiddenCategories.some(cat => { var _a; return lang && ((_a = langData[lang]) === null || _a === void 0 ? void 0 : _a.type) === cat; })) {
316
- continue;
317
- }
318
- delete results.files.results[file];
319
- if (lang) {
320
- delete results.languages.results[lang];
321
- }
322
- }
323
- for (const category of hiddenCategories) {
324
- for (const [lang, { type }] of Object.entries(results.languages.results)) {
325
- if (type === category) {
326
- delete results.languages.results[lang];
327
- }
328
- }
329
- }
330
- }
331
- // Convert paths to relative
332
- if (!useRawContent && opts.relativePaths) {
333
- const newMap = {};
334
- for (const [file, lang] of Object.entries(results.files.results)) {
335
- let relPath = path_1.default.relative(process.cwd(), file).replace(/\\/g, '/');
336
- if (!relPath.startsWith('../')) {
337
- relPath = './' + relPath;
338
- }
339
- newMap[relPath] = lang;
340
- }
341
- results.files.results = newMap;
342
- }
343
- // Load language bytes size
344
- for (const [file, lang] of Object.entries(results.files.results)) {
345
- if (lang && !langData[lang])
346
- continue;
347
- const fileSize = (_r = (_q = (_p = opts.fileContent) === null || _p === void 0 ? void 0 : _p[files.indexOf(file)]) === null || _q === void 0 ? void 0 : _q.length) !== null && _r !== void 0 ? _r : fs_1.default.statSync(file).size;
348
- results.files.bytes += fileSize;
349
- // If no language found, add extension in other section
350
- if (!lang) {
351
- const ext = path_1.default.extname(file);
352
- const unknownType = ext === '' ? 'filenames' : 'extensions';
353
- const name = ext === '' ? path_1.default.basename(file) : ext;
354
- (_s = (_v = results.unknown[unknownType])[name]) !== null && _s !== void 0 ? _s : (_v[name] = 0);
355
- results.unknown[unknownType][name] += fileSize;
356
- results.unknown.bytes += fileSize;
357
- continue;
358
- }
359
- // Add language and bytes data to corresponding section
360
- const { type } = langData[lang];
361
- (_t = (_w = results.languages.results)[lang]) !== null && _t !== void 0 ? _t : (_w[lang] = { type, bytes: 0, color: langData[lang].color });
362
- if (opts.childLanguages) {
363
- results.languages.results[lang].parent = langData[lang].group;
364
- }
365
- results.languages.results[lang].bytes += fileSize;
366
- results.languages.bytes += fileSize;
367
- }
368
- // Set counts
369
- results.files.count = Object.keys(results.files.results).length;
370
- results.languages.count = Object.keys(results.languages.results).length;
371
- results.unknown.count = Object.keys({ ...results.unknown.extensions, ...results.unknown.filenames }).length;
372
- // Return
373
- return results;
374
- }
375
- module.exports = analyse;
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ const fs_1 = __importDefault(require("fs"));
6
+ const path_1 = __importDefault(require("path"));
7
+ const js_yaml_1 = __importDefault(require("js-yaml"));
8
+ const ignore_1 = __importDefault(require("ignore"));
9
+ const common_path_prefix_1 = __importDefault(require("common-path-prefix"));
10
+ const binary_extensions_1 = __importDefault(require("binary-extensions"));
11
+ const isbinaryfile_1 = require("isbinaryfile");
12
+ const walk_tree_1 = __importDefault(require("./helpers/walk-tree"));
13
+ const load_data_1 = __importDefault(require("./helpers/load-data"));
14
+ const read_file_1 = __importDefault(require("./helpers/read-file"));
15
+ const convert_pcre_1 = __importDefault(require("./helpers/convert-pcre"));
16
+ async function analyse(input, opts = {}) {
17
+ var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m, _o, _p, _q, _r, _s, _t;
18
+ var _u, _v, _w;
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
23
+ const langData = await (0, load_data_1.default)('languages.yml', opts.offline).then(js_yaml_1.default.load);
24
+ const vendorData = await (0, load_data_1.default)('vendor.yml', opts.offline).then(js_yaml_1.default.load);
25
+ const docData = await (0, load_data_1.default)('documentation.yml', opts.offline).then(js_yaml_1.default.load);
26
+ const heuristicsData = await (0, load_data_1.default)('heuristics.yml', opts.offline).then(js_yaml_1.default.load);
27
+ const generatedData = await (0, load_data_1.default)('generated.rb', opts.offline).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
30
+ const fileAssociations = {};
31
+ const extensions = {};
32
+ const overrides = {};
33
+ const results = {
34
+ files: { count: 0, bytes: 0, results: {} },
35
+ languages: { count: 0, bytes: 0, results: {} },
36
+ unknown: { count: 0, bytes: 0, extensions: {}, filenames: {} },
37
+ };
38
+ // Prepare list of ignored files
39
+ const gitignores = (0, ignore_1.default)();
40
+ const regexIgnores = [];
41
+ gitignores.add('/.git');
42
+ if (!opts.keepVendored)
43
+ regexIgnores.push(...vendorPaths.map(path => RegExp(path, 'i')));
44
+ if (opts.ignoredFiles)
45
+ gitignores.add(opts.ignoredFiles);
46
+ // Set a common root path so that vendor paths do not incorrectly match parent folders
47
+ const resolvedInput = input.map(path => path_1.default.resolve(path).replace(/\\/g, '/'));
48
+ const commonRoot = (input.length > 1 ? (0, common_path_prefix_1.default)(resolvedInput) : resolvedInput[0]).replace(/\/?$/, '');
49
+ const relPath = (file) => path_1.default.relative(commonRoot, file).replace(/\\/g, '/');
50
+ const unRelPath = (file) => path_1.default.resolve(commonRoot, file).replace(/\\/g, '/');
51
+ // Load file paths and folders
52
+ let files, folders;
53
+ if (useRawContent) {
54
+ // Uses raw file content
55
+ files = input;
56
+ folders = [''];
57
+ }
58
+ else {
59
+ // Uses directory on disc
60
+ const data = (0, walk_tree_1.default)(true, commonRoot, input, gitignores, regexIgnores);
61
+ files = data.files;
62
+ folders = data.folders;
63
+ }
64
+ // Apply aliases
65
+ opts = {
66
+ checkIgnored: !opts.quick,
67
+ checkAttributes: !opts.quick,
68
+ checkHeuristics: !opts.quick,
69
+ checkShebang: !opts.quick,
70
+ checkModeline: !opts.quick,
71
+ ...opts
72
+ };
73
+ // Ignore specific languages
74
+ for (const lang of (_b = opts.ignoredLanguages) !== null && _b !== void 0 ? _b : []) {
75
+ for (const key in langData) {
76
+ if (lang.toLowerCase() === key.toLowerCase()) {
77
+ delete langData[key];
78
+ break;
79
+ }
80
+ }
81
+ }
82
+ // Load gitignores and gitattributes
83
+ const customBinary = (0, ignore_1.default)();
84
+ const customText = (0, ignore_1.default)();
85
+ if (!useRawContent && opts.checkAttributes) {
86
+ for (const folder of folders) {
87
+ // Skip if folder is marked in gitattributes
88
+ if (relPath(folder) && gitignores.ignores(relPath(folder))) {
89
+ continue;
90
+ }
91
+ // Parse gitignores
92
+ const ignoresFile = path_1.default.join(folder, '.gitignore');
93
+ if (opts.checkIgnored && fs_1.default.existsSync(ignoresFile)) {
94
+ const ignoresData = await (0, read_file_1.default)(ignoresFile);
95
+ gitignores.add(ignoresData);
96
+ }
97
+ // Parse gitattributes
98
+ const attributesFile = path_1.default.join(folder, '.gitattributes');
99
+ if (opts.checkAttributes && fs_1.default.existsSync(attributesFile)) {
100
+ const attributesData = await (0, read_file_1.default)(attributesFile);
101
+ // Explicit text/binary associations
102
+ const contentTypeMatches = attributesData.matchAll(/^(\S+).*?(-?binary|-?text)(?!=auto)/gm);
103
+ for (const [_line, path, type] of contentTypeMatches) {
104
+ if (['text', '-binary'].includes(type)) {
105
+ customText.add(path);
106
+ }
107
+ if (['-text', 'binary'].includes(type)) {
108
+ customBinary.add(path);
109
+ }
110
+ }
111
+ // Custom vendor options
112
+ const vendorMatches = attributesData.matchAll(/^(\S+).*[^-]linguist-(vendored|generated|documentation)(?!=false)/gm);
113
+ for (const [_line, path] of vendorMatches) {
114
+ gitignores.add(path);
115
+ }
116
+ // Custom file associations
117
+ const customLangMatches = attributesData.matchAll(/^(\S+).*[^-]linguist-language=(\S+)/gm);
118
+ for (let [_line, path, forcedLang] of customLangMatches) {
119
+ // If specified language is an alias, associate it with its full name
120
+ if (!langData[forcedLang]) {
121
+ const overrideLang = Object.entries(langData).find(entry => { var _a; return (_a = entry[1].aliases) === null || _a === void 0 ? void 0 : _a.includes(forcedLang.toLowerCase()); });
122
+ if (overrideLang) {
123
+ forcedLang = overrideLang[0];
124
+ }
125
+ }
126
+ const fullPath = path_1.default.join(relPath(folder), path);
127
+ overrides[fullPath] = forcedLang;
128
+ }
129
+ }
130
+ }
131
+ }
132
+ // Check vendored files
133
+ if (!opts.keepVendored) {
134
+ // Filter out any files that match a vendor file path
135
+ if (useRawContent) {
136
+ files = gitignores.filter(files);
137
+ files = files.filter(file => !regexIgnores.find(match => match.test(file)));
138
+ }
139
+ else {
140
+ files = gitignores.filter(files.map(relPath)).map(unRelPath);
141
+ }
142
+ }
143
+ // Load all files and parse languages
144
+ const addResult = (file, result) => {
145
+ if (!fileAssociations[file]) {
146
+ fileAssociations[file] = [];
147
+ extensions[file] = '';
148
+ }
149
+ const parent = !opts.childLanguages && result && langData[result].group || false;
150
+ fileAssociations[file].push(parent || result);
151
+ extensions[file] = path_1.default.extname(file).toLowerCase();
152
+ };
153
+ const overridesArray = Object.entries(overrides);
154
+ // List all languages that could be associated with a given file
155
+ const definiteness = {};
156
+ const fromShebang = {};
157
+ for (const file of files) {
158
+ let firstLine;
159
+ if (useRawContent) {
160
+ 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;
161
+ }
162
+ else if (fs_1.default.existsSync(file) && !fs_1.default.lstatSync(file).isDirectory()) {
163
+ firstLine = await (0, read_file_1.default)(file, true).catch(() => null);
164
+ }
165
+ else
166
+ continue;
167
+ // Skip if file is unreadable
168
+ if (firstLine === null)
169
+ continue;
170
+ // Check first line for explicit classification
171
+ const hasShebang = opts.checkShebang && /^#!/.test(firstLine);
172
+ const hasModeline = opts.checkModeline && /-\*-|(syntax|filetype|ft)\s*=/.test(firstLine);
173
+ if (!opts.quick && (hasShebang || hasModeline)) {
174
+ const matches = [];
175
+ for (const [lang, data] of Object.entries(langData)) {
176
+ const langMatcher = (lang) => `\\b${lang.toLowerCase().replace(/\W/g, '\\$&')}(?![\\w#+*]|-\*-)`;
177
+ // Check for interpreter match
178
+ if (opts.checkShebang && hasShebang) {
179
+ const matchesInterpretor = (_f = data.interpreters) === null || _f === void 0 ? void 0 : _f.some(interpreter => firstLine.match(`\\b${interpreter}\\b`));
180
+ if (matchesInterpretor)
181
+ matches.push(lang);
182
+ }
183
+ // Check modeline declaration
184
+ if (opts.checkModeline && hasModeline) {
185
+ const modelineText = firstLine.toLowerCase().replace(/^.*-\*-(.+)-\*-.*$/, '$1');
186
+ const matchesLang = modelineText.match(langMatcher(lang));
187
+ const matchesAlias = (_g = data.aliases) === null || _g === void 0 ? void 0 : _g.some(lang => modelineText.match(langMatcher(lang)));
188
+ if (matchesLang || matchesAlias)
189
+ matches.push(lang);
190
+ }
191
+ }
192
+ // Add identified language(s)
193
+ if (matches.length) {
194
+ for (const match of matches)
195
+ addResult(file, match);
196
+ if (matches.length === 1)
197
+ definiteness[file] = true;
198
+ fromShebang[file] = true;
199
+ continue;
200
+ }
201
+ }
202
+ // Check override for manual language classification
203
+ if (!useRawContent && !opts.quick && opts.checkAttributes) {
204
+ const isOverridden = (path) => (0, ignore_1.default)().add(path).ignores(relPath(file));
205
+ const match = overridesArray.find(item => isOverridden(item[0]));
206
+ if (match) {
207
+ const forcedLang = match[1];
208
+ addResult(file, forcedLang);
209
+ definiteness[file] = true;
210
+ continue;
211
+ }
212
+ }
213
+ // Search each language
214
+ let skipExts = false;
215
+ // Check if filename is a match
216
+ for (const lang in langData) {
217
+ const matchesName = (_h = langData[lang].filenames) === null || _h === void 0 ? void 0 : _h.some(name => path_1.default.basename(file.toLowerCase()) === name.toLowerCase());
218
+ if (matchesName) {
219
+ addResult(file, lang);
220
+ skipExts = true;
221
+ }
222
+ }
223
+ // Check if extension is a match
224
+ const possibleExts = [];
225
+ if (!skipExts)
226
+ for (const lang in langData) {
227
+ const extMatches = (_j = langData[lang].extensions) === null || _j === void 0 ? void 0 : _j.filter(ext => file.toLowerCase().endsWith(ext.toLowerCase()));
228
+ if (extMatches === null || extMatches === void 0 ? void 0 : extMatches.length) {
229
+ for (const ext of extMatches)
230
+ possibleExts.push({ ext, lang });
231
+ }
232
+ }
233
+ // Apply more specific extension if available
234
+ const isComplexExt = (ext) => /\..+\./.test(ext);
235
+ const hasComplexExt = possibleExts.some(data => isComplexExt(data.ext));
236
+ for (const { ext, lang } of possibleExts) {
237
+ if (hasComplexExt && !isComplexExt(ext))
238
+ continue;
239
+ if (!hasComplexExt && isComplexExt(ext))
240
+ continue;
241
+ addResult(file, lang);
242
+ }
243
+ // Fallback to null if no language matches
244
+ if (!fileAssociations[file]) {
245
+ addResult(file, null);
246
+ }
247
+ }
248
+ // Narrow down file associations to the best fit
249
+ for (const file in fileAssociations) {
250
+ // Skip if file has explicit association
251
+ if (definiteness[file]) {
252
+ results.files.results[file] = fileAssociations[file][0];
253
+ continue;
254
+ }
255
+ // Skip binary files
256
+ if (!useRawContent && !opts.keepBinary) {
257
+ const isCustomText = customText.ignores(relPath(file));
258
+ const isCustomBinary = customBinary.ignores(relPath(file));
259
+ const isBinaryExt = binary_extensions_1.default.some(ext => file.endsWith('.' + ext));
260
+ if (!isCustomText && (isCustomBinary || isBinaryExt || await (0, isbinaryfile_1.isBinaryFile)(file))) {
261
+ continue;
262
+ }
263
+ }
264
+ // Parse heuristics if applicable
265
+ if (opts.checkHeuristics)
266
+ for (const heuristics of heuristicsData.disambiguations) {
267
+ // Make sure the extension matches the current file
268
+ if (!fromShebang[file] && !heuristics.extensions.includes(extensions[file]))
269
+ continue;
270
+ // Load heuristic rules
271
+ for (const heuristic of heuristics.rules) {
272
+ // Make sure the language is not an array
273
+ if (Array.isArray(heuristic.language)) {
274
+ heuristic.language = heuristic.language[0];
275
+ }
276
+ // Make sure the results includes this language
277
+ const languageGroup = (_k = langData[heuristic.language]) === null || _k === void 0 ? void 0 : _k.group;
278
+ const matchesLang = fileAssociations[file].includes(heuristic.language);
279
+ const matchesParent = languageGroup && fileAssociations[file].includes(languageGroup);
280
+ if (!matchesLang && !matchesParent)
281
+ continue;
282
+ // Normalise heuristic data
283
+ const patterns = [];
284
+ const normalise = (contents) => patterns.push(...[contents].flat());
285
+ if (heuristic.pattern)
286
+ normalise(heuristic.pattern);
287
+ if (heuristic.named_pattern)
288
+ normalise(heuristicsData.named_patterns[heuristic.named_pattern]);
289
+ if (heuristic.and) {
290
+ for (const data of heuristic.and) {
291
+ if (data.pattern)
292
+ normalise(data.pattern);
293
+ if (data.named_pattern)
294
+ normalise(heuristicsData.named_patterns[data.named_pattern]);
295
+ }
296
+ }
297
+ // Check file contents and apply heuristic patterns
298
+ const fileContent = ((_l = opts.fileContent) === null || _l === void 0 ? void 0 : _l.length) ? opts.fileContent[files.indexOf(file)] : await (0, read_file_1.default)(file).catch(() => null);
299
+ if (fileContent === null)
300
+ continue;
301
+ if (!patterns.length || patterns.some(pattern => (0, convert_pcre_1.default)(pattern).test(fileContent))) {
302
+ results.files.results[file] = heuristic.language;
303
+ break;
304
+ }
305
+ }
306
+ }
307
+ // If no heuristics, assign a language
308
+ (_m = (_u = results.files.results)[file]) !== null && _m !== void 0 ? _m : (_u[file] = fileAssociations[file][0]);
309
+ }
310
+ // Skip specified categories
311
+ if ((_o = opts.categories) === null || _o === void 0 ? void 0 : _o.length) {
312
+ const categories = ['data', 'markup', 'programming', 'prose'];
313
+ const hiddenCategories = categories.filter(cat => !opts.categories.includes(cat));
314
+ for (const [file, lang] of Object.entries(results.files.results)) {
315
+ if (!hiddenCategories.some(cat => { var _a; return lang && ((_a = langData[lang]) === null || _a === void 0 ? void 0 : _a.type) === cat; })) {
316
+ continue;
317
+ }
318
+ delete results.files.results[file];
319
+ if (lang) {
320
+ delete results.languages.results[lang];
321
+ }
322
+ }
323
+ for (const category of hiddenCategories) {
324
+ for (const [lang, { type }] of Object.entries(results.languages.results)) {
325
+ if (type === category) {
326
+ delete results.languages.results[lang];
327
+ }
328
+ }
329
+ }
330
+ }
331
+ // Convert paths to relative
332
+ if (!useRawContent && opts.relativePaths) {
333
+ const newMap = {};
334
+ for (const [file, lang] of Object.entries(results.files.results)) {
335
+ let relPath = path_1.default.relative(process.cwd(), file).replace(/\\/g, '/');
336
+ if (!relPath.startsWith('../')) {
337
+ relPath = './' + relPath;
338
+ }
339
+ newMap[relPath] = lang;
340
+ }
341
+ results.files.results = newMap;
342
+ }
343
+ // Load language bytes size
344
+ for (const [file, lang] of Object.entries(results.files.results)) {
345
+ if (lang && !langData[lang])
346
+ continue;
347
+ const fileSize = (_r = (_q = (_p = opts.fileContent) === null || _p === void 0 ? void 0 : _p[files.indexOf(file)]) === null || _q === void 0 ? void 0 : _q.length) !== null && _r !== void 0 ? _r : fs_1.default.statSync(file).size;
348
+ results.files.bytes += fileSize;
349
+ // If no language found, add extension in other section
350
+ if (!lang) {
351
+ const ext = path_1.default.extname(file);
352
+ const unknownType = ext === '' ? 'filenames' : 'extensions';
353
+ const name = ext === '' ? path_1.default.basename(file) : ext;
354
+ (_s = (_v = results.unknown[unknownType])[name]) !== null && _s !== void 0 ? _s : (_v[name] = 0);
355
+ results.unknown[unknownType][name] += fileSize;
356
+ results.unknown.bytes += fileSize;
357
+ continue;
358
+ }
359
+ // Add language and bytes data to corresponding section
360
+ const { type } = langData[lang];
361
+ (_t = (_w = results.languages.results)[lang]) !== null && _t !== void 0 ? _t : (_w[lang] = { type, bytes: 0, color: langData[lang].color });
362
+ if (opts.childLanguages) {
363
+ results.languages.results[lang].parent = langData[lang].group;
364
+ }
365
+ results.languages.results[lang].bytes += fileSize;
366
+ results.languages.bytes += fileSize;
367
+ }
368
+ // Set counts
369
+ results.files.count = Object.keys(results.files.results).length;
370
+ results.languages.count = Object.keys(results.languages.results).length;
371
+ results.unknown.count = Object.keys({ ...results.unknown.extensions, ...results.unknown.filenames }).length;
372
+ // Return
373
+ return results;
374
+ }
375
+ module.exports = analyse;