linguist-js 2.6.0 → 2.7.0-pre
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.d.ts +1 -1
- package/dist/cli.js +141 -107
- package/dist/helpers/convert-pcre.d.ts +2 -0
- package/dist/helpers/convert-pcre.js +38 -38
- package/dist/helpers/load-data.d.ts +4 -0
- package/dist/helpers/load-data.js +38 -31
- package/dist/helpers/parse-gitattributes.d.ts +16 -0
- package/dist/helpers/parse-gitattributes.js +37 -0
- package/dist/helpers/read-file.d.ts +5 -0
- package/dist/helpers/read-file.js +23 -23
- package/dist/helpers/walk-tree.d.ts +20 -0
- package/dist/helpers/walk-tree.js +71 -64
- package/dist/index.d.ts +4 -4
- package/dist/index.js +429 -389
- package/dist/schema.d.ts +37 -37
- package/dist/schema.js +2 -2
- package/dist/types.d.ts +52 -48
- package/dist/types.js +2 -2
- package/ext/generated.rb +24 -399
- package/package.json +5 -6
- package/readme.md +40 -26
package/dist/index.js
CHANGED
|
@@ -1,389 +1,429 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
var
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
//
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
//
|
|
83
|
-
const
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
}
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
// Check
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
}
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || function (mod) {
|
|
19
|
+
if (mod && mod.__esModule) return mod;
|
|
20
|
+
var result = {};
|
|
21
|
+
if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
|
|
22
|
+
__setModuleDefault(result, mod);
|
|
23
|
+
return result;
|
|
24
|
+
};
|
|
25
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
26
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
27
|
+
};
|
|
28
|
+
const fs_1 = __importDefault(require("fs"));
|
|
29
|
+
const path_1 = __importDefault(require("path"));
|
|
30
|
+
const js_yaml_1 = __importDefault(require("js-yaml"));
|
|
31
|
+
const ignore_1 = __importDefault(require("ignore"));
|
|
32
|
+
const common_path_prefix_1 = __importDefault(require("common-path-prefix"));
|
|
33
|
+
const binary_extensions_1 = __importDefault(require("binary-extensions"));
|
|
34
|
+
const isbinaryfile_1 = require("isbinaryfile");
|
|
35
|
+
const walk_tree_1 = __importDefault(require("./helpers/walk-tree"));
|
|
36
|
+
const load_data_1 = __importStar(require("./helpers/load-data"));
|
|
37
|
+
const read_file_1 = __importDefault(require("./helpers/read-file"));
|
|
38
|
+
const parse_gitattributes_1 = __importDefault(require("./helpers/parse-gitattributes"));
|
|
39
|
+
const convert_pcre_1 = __importDefault(require("./helpers/convert-pcre"));
|
|
40
|
+
async function analyse(rawInput, opts = {}) {
|
|
41
|
+
var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m, _o, _p, _q;
|
|
42
|
+
var _r, _s;
|
|
43
|
+
const useRawContent = opts.fileContent !== undefined;
|
|
44
|
+
const input = [rawInput !== null && rawInput !== void 0 ? rawInput : []].flat();
|
|
45
|
+
const manualFileContent = [(_a = opts.fileContent) !== null && _a !== void 0 ? _a : []].flat();
|
|
46
|
+
// Normalise input option arguments
|
|
47
|
+
opts = {
|
|
48
|
+
checkIgnored: !opts.quick,
|
|
49
|
+
checkAttributes: !opts.quick,
|
|
50
|
+
checkHeuristics: !opts.quick,
|
|
51
|
+
checkShebang: !opts.quick,
|
|
52
|
+
checkModeline: !opts.quick,
|
|
53
|
+
...opts,
|
|
54
|
+
};
|
|
55
|
+
// Load data from github-linguist web repo
|
|
56
|
+
const langData = await (0, load_data_1.default)('languages.yml', opts.offline).then(js_yaml_1.default.load);
|
|
57
|
+
const vendorData = await (0, load_data_1.default)('vendor.yml', opts.offline).then(js_yaml_1.default.load);
|
|
58
|
+
const docData = await (0, load_data_1.default)('documentation.yml', opts.offline).then(js_yaml_1.default.load);
|
|
59
|
+
const heuristicsData = await (0, load_data_1.default)('heuristics.yml', opts.offline).then(js_yaml_1.default.load);
|
|
60
|
+
const generatedData = await (0, load_data_1.default)('generated.rb', opts.offline).then(load_data_1.parseGeneratedDataFile);
|
|
61
|
+
const vendorPaths = [...vendorData, ...docData, ...generatedData];
|
|
62
|
+
// Setup main variables
|
|
63
|
+
const fileAssociations = {};
|
|
64
|
+
const extensions = {};
|
|
65
|
+
const globOverrides = {};
|
|
66
|
+
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
|
+
};
|
|
71
|
+
// Set a common root path so that vendor paths do not incorrectly match parent folders
|
|
72
|
+
const normPath = (file) => file.replace(/\\/g, '/');
|
|
73
|
+
const resolvedInput = input.map(path => normPath(path_1.default.resolve(path)));
|
|
74
|
+
const commonRoot = (input.length > 1 ? (0, common_path_prefix_1.default)(resolvedInput) : resolvedInput[0]).replace(/\/?$/, '');
|
|
75
|
+
const localRoot = (folder) => folder.replace(commonRoot, '').replace(/^\//, '');
|
|
76
|
+
const relPath = (file) => useRawContent ? file : normPath(path_1.default.relative(commonRoot, file));
|
|
77
|
+
const unRelPath = (file) => useRawContent ? file : normPath(path_1.default.resolve(commonRoot, file));
|
|
78
|
+
// Other helper functions
|
|
79
|
+
const fileMatchesGlobs = (file, ...globs) => (0, ignore_1.default)().add(globs).ignores(relPath(file));
|
|
80
|
+
const filterOutIgnored = (files, ignored) => ignored.filter(files.map(relPath)).map(unRelPath);
|
|
81
|
+
//*PREPARE FILES AND DATA*//
|
|
82
|
+
// Prepare list of ignored files
|
|
83
|
+
const ignored = (0, ignore_1.default)();
|
|
84
|
+
ignored.add('.git/');
|
|
85
|
+
ignored.add((_b = opts.ignoredFiles) !== null && _b !== void 0 ? _b : []);
|
|
86
|
+
const regexIgnores = [];
|
|
87
|
+
if (!opts.keepVendored)
|
|
88
|
+
regexIgnores.push(...vendorPaths.map(path => RegExp(path, 'i')));
|
|
89
|
+
// Load file paths and folders
|
|
90
|
+
let files;
|
|
91
|
+
let folders;
|
|
92
|
+
if (useRawContent) {
|
|
93
|
+
// Uses raw file content
|
|
94
|
+
files = input;
|
|
95
|
+
folders = [''];
|
|
96
|
+
}
|
|
97
|
+
else {
|
|
98
|
+
// Uses directory on disc
|
|
99
|
+
const data = (0, walk_tree_1.default)({ init: true, commonRoot, folderRoots: resolvedInput, folders: resolvedInput, ignored });
|
|
100
|
+
files = data.files;
|
|
101
|
+
folders = data.folders;
|
|
102
|
+
}
|
|
103
|
+
// Load gitignore data and apply ignores rules
|
|
104
|
+
if (!useRawContent) {
|
|
105
|
+
// TODO switch to be like the gitattributes code
|
|
106
|
+
for (const folder of folders) {
|
|
107
|
+
// Parse gitignores
|
|
108
|
+
const ignoresFile = path_1.default.join(folder, '.gitignore');
|
|
109
|
+
if (opts.checkIgnored && fs_1.default.existsSync(ignoresFile)) {
|
|
110
|
+
const ignoresData = await (0, read_file_1.default)(ignoresFile);
|
|
111
|
+
const localIgnoresData = ignoresData.replace(/^[\/\\]/g, localRoot(folder) + '/');
|
|
112
|
+
ignored.add(localIgnoresData);
|
|
113
|
+
files = filterOutIgnored(files, ignored);
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
// Fetch and normalise gitattributes data of all subfolders and save to metadata
|
|
118
|
+
const manualAttributes = {}; // Maps file globs to gitattribute boolean flags
|
|
119
|
+
const getFlaggedGlobs = (attr, val) => {
|
|
120
|
+
return Object.entries(manualAttributes).filter(([, attrs]) => attrs[attr] === val).map(([glob,]) => glob);
|
|
121
|
+
};
|
|
122
|
+
if (!useRawContent && opts.checkAttributes) {
|
|
123
|
+
const nestedAttrFiles = files.filter(file => file.endsWith('.gitattributes'));
|
|
124
|
+
for (const attrFile of nestedAttrFiles) {
|
|
125
|
+
const relAttrFile = relPath(attrFile);
|
|
126
|
+
const relAttrFolder = path_1.default.dirname(relAttrFile);
|
|
127
|
+
const contents = await (0, read_file_1.default)(attrFile);
|
|
128
|
+
const parsed = (0, parse_gitattributes_1.default)(contents, relAttrFolder);
|
|
129
|
+
for (const { glob, attrs } of parsed) {
|
|
130
|
+
manualAttributes[glob] = attrs;
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
// Apply vendor file path matches and filter out vendored files
|
|
135
|
+
if (!opts.keepVendored) {
|
|
136
|
+
// Get data of files that have been manually marked with metadata
|
|
137
|
+
const vendorTrueGlobs = [...getFlaggedGlobs('vendored', true), ...getFlaggedGlobs('generated', true), ...getFlaggedGlobs('documentation', true)];
|
|
138
|
+
const vendorFalseGlobs = [...getFlaggedGlobs('vendored', false), ...getFlaggedGlobs('generated', false), ...getFlaggedGlobs('documentation', false)];
|
|
139
|
+
// Set up glob ignore object to use for expanding globs to match files
|
|
140
|
+
const vendorOverrides = (0, ignore_1.default)().add(vendorFalseGlobs);
|
|
141
|
+
// Remove all files marked as vendored by default
|
|
142
|
+
const excludedFiles = files.filter(file => vendorPaths.some(pathPtn => RegExp(pathPtn, 'i').test(relPath(file))));
|
|
143
|
+
files = files.filter(file => !excludedFiles.includes(file));
|
|
144
|
+
// Re-add removed files that are overridden manually in gitattributes
|
|
145
|
+
const overriddenExcludedFiles = excludedFiles.filter(file => vendorOverrides.ignores(relPath(file)));
|
|
146
|
+
files.push(...overriddenExcludedFiles);
|
|
147
|
+
// Remove files explicitly marked as vendored in gitattributes
|
|
148
|
+
// TODO change globs.includes(file) to parse the glob using ignore()
|
|
149
|
+
files = files.filter(file => !vendorTrueGlobs.includes(relPath(file)));
|
|
150
|
+
}
|
|
151
|
+
// Filter out binary files
|
|
152
|
+
if (!opts.keepBinary) {
|
|
153
|
+
// Filter out files that are binary by default
|
|
154
|
+
files = files.filter(file => !binary_extensions_1.default.some(ext => file.endsWith('.' + ext)));
|
|
155
|
+
// Filter out manually specified binary files
|
|
156
|
+
const binaryIgnored = (0, ignore_1.default)().add(getFlaggedGlobs('binary', true));
|
|
157
|
+
files = filterOutIgnored(files, binaryIgnored);
|
|
158
|
+
// Re-add files manually marked not as binary
|
|
159
|
+
const binaryUnignored = (0, ignore_1.default)().add(getFlaggedGlobs('binary', false));
|
|
160
|
+
// TODO parse the globs using ignore()
|
|
161
|
+
const unignoredList = filterOutIgnored(files, binaryUnignored);
|
|
162
|
+
files.push(...unignoredList);
|
|
163
|
+
}
|
|
164
|
+
// Ignore specific languages
|
|
165
|
+
for (const lang of (_c = opts.ignoredLanguages) !== null && _c !== void 0 ? _c : []) {
|
|
166
|
+
for (const key in langData) {
|
|
167
|
+
if (lang.toLowerCase() === key.toLowerCase()) {
|
|
168
|
+
delete langData[key];
|
|
169
|
+
break;
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
// Establish language overrides taken from gitattributes
|
|
174
|
+
const forcedLangs = Object.entries(manualAttributes).filter(([, attrs]) => attrs.language);
|
|
175
|
+
for (const [globPath, attrs] of forcedLangs) {
|
|
176
|
+
let forcedLang = attrs.language;
|
|
177
|
+
if (!forcedLang)
|
|
178
|
+
continue;
|
|
179
|
+
// If specified language is an alias, associate it with its full name
|
|
180
|
+
if (!langData[forcedLang]) {
|
|
181
|
+
const overrideLang = Object.entries(langData).find(entry => { var _a; return (_a = entry[1].aliases) === null || _a === void 0 ? void 0 : _a.includes(forcedLang.toLowerCase()); });
|
|
182
|
+
if (overrideLang) {
|
|
183
|
+
forcedLang = overrideLang[0];
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
globOverrides[globPath] = forcedLang;
|
|
187
|
+
}
|
|
188
|
+
//*PARSE LANGUAGES*//
|
|
189
|
+
const addResult = (file, result) => {
|
|
190
|
+
if (!fileAssociations[file]) {
|
|
191
|
+
fileAssociations[file] = [];
|
|
192
|
+
extensions[file] = '';
|
|
193
|
+
}
|
|
194
|
+
// Set parent to result group if it is present
|
|
195
|
+
// Is nullish if either `opts.childLanguages` is set or if there is no group
|
|
196
|
+
const finalResult = !opts.childLanguages && result && langData[result] && langData[result].group || result;
|
|
197
|
+
if (!fileAssociations[file].includes(finalResult)) {
|
|
198
|
+
fileAssociations[file].push(finalResult);
|
|
199
|
+
}
|
|
200
|
+
extensions[file] = path_1.default.extname(file).toLowerCase();
|
|
201
|
+
};
|
|
202
|
+
const definiteness = {};
|
|
203
|
+
const fromShebang = {};
|
|
204
|
+
fileLoop: for (const file of files) {
|
|
205
|
+
// Check manual override
|
|
206
|
+
for (const globMatch in globOverrides) {
|
|
207
|
+
if (!fileMatchesGlobs(file, globMatch))
|
|
208
|
+
continue;
|
|
209
|
+
// If the given file matches the glob, apply the override to the file
|
|
210
|
+
const forcedLang = globOverrides[globMatch];
|
|
211
|
+
addResult(file, forcedLang);
|
|
212
|
+
definiteness[file] = true;
|
|
213
|
+
continue fileLoop; // no need to check other heuristics, the classified language has been found
|
|
214
|
+
}
|
|
215
|
+
// Check first line for readability
|
|
216
|
+
let firstLine;
|
|
217
|
+
if (useRawContent) {
|
|
218
|
+
firstLine = (_e = (_d = manualFileContent[files.indexOf(file)]) === null || _d === void 0 ? void 0 : _d.split('\n')[0]) !== null && _e !== void 0 ? _e : null;
|
|
219
|
+
}
|
|
220
|
+
else if (fs_1.default.existsSync(file) && !fs_1.default.lstatSync(file).isDirectory()) {
|
|
221
|
+
firstLine = await (0, read_file_1.default)(file, true).catch(() => null);
|
|
222
|
+
}
|
|
223
|
+
else
|
|
224
|
+
continue;
|
|
225
|
+
// Skip if file is unreadable or blank
|
|
226
|
+
if (firstLine === null)
|
|
227
|
+
continue;
|
|
228
|
+
// Check first line for explicit classification
|
|
229
|
+
const hasShebang = opts.checkShebang && /^#!/.test(firstLine);
|
|
230
|
+
const hasModeline = opts.checkModeline && /-\*-|(syntax|filetype|ft)\s*=/.test(firstLine);
|
|
231
|
+
if (!opts.quick && (hasShebang || hasModeline)) {
|
|
232
|
+
const matches = [];
|
|
233
|
+
for (const [lang, data] of Object.entries(langData)) {
|
|
234
|
+
const langMatcher = (lang) => `\\b${lang.toLowerCase().replace(/\W/g, '\\$&')}(?![\\w#+*]|-\*-)`;
|
|
235
|
+
// Check for interpreter match
|
|
236
|
+
if (opts.checkShebang && hasShebang) {
|
|
237
|
+
const matchesInterpretor = (_f = data.interpreters) === null || _f === void 0 ? void 0 : _f.some(interpreter => firstLine.match(`\\b${interpreter}\\b`));
|
|
238
|
+
if (matchesInterpretor)
|
|
239
|
+
matches.push(lang);
|
|
240
|
+
}
|
|
241
|
+
// Check modeline declaration
|
|
242
|
+
if (opts.checkModeline && hasModeline) {
|
|
243
|
+
const modelineText = firstLine.toLowerCase().replace(/^.*-\*-(.+)-\*-.*$/, '$1');
|
|
244
|
+
const matchesLang = modelineText.match(langMatcher(lang));
|
|
245
|
+
const matchesAlias = (_g = data.aliases) === null || _g === void 0 ? void 0 : _g.some(lang => modelineText.match(langMatcher(lang)));
|
|
246
|
+
if (matchesLang || matchesAlias)
|
|
247
|
+
matches.push(lang);
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
// Add identified language(s)
|
|
251
|
+
if (matches.length) {
|
|
252
|
+
for (const match of matches)
|
|
253
|
+
addResult(file, match);
|
|
254
|
+
if (matches.length === 1)
|
|
255
|
+
definiteness[file] = true;
|
|
256
|
+
fromShebang[file] = true;
|
|
257
|
+
continue;
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
// Search each language
|
|
261
|
+
let skipExts = false;
|
|
262
|
+
// Check if filename is a match
|
|
263
|
+
for (const lang in langData) {
|
|
264
|
+
const matchesName = (_h = langData[lang].filenames) === null || _h === void 0 ? void 0 : _h.some(name => path_1.default.basename(file.toLowerCase()) === name.toLowerCase());
|
|
265
|
+
if (matchesName) {
|
|
266
|
+
addResult(file, lang);
|
|
267
|
+
skipExts = true;
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
// Check if extension is a match
|
|
271
|
+
const possibleExts = [];
|
|
272
|
+
if (!skipExts)
|
|
273
|
+
for (const lang in langData) {
|
|
274
|
+
const extMatches = (_j = langData[lang].extensions) === null || _j === void 0 ? void 0 : _j.filter(ext => file.toLowerCase().endsWith(ext.toLowerCase()));
|
|
275
|
+
if (extMatches === null || extMatches === void 0 ? void 0 : extMatches.length) {
|
|
276
|
+
for (const ext of extMatches)
|
|
277
|
+
possibleExts.push({ ext, lang });
|
|
278
|
+
}
|
|
279
|
+
}
|
|
280
|
+
// Apply more specific extension if available
|
|
281
|
+
const isComplexExt = (ext) => /\..+\./.test(ext);
|
|
282
|
+
const hasComplexExt = possibleExts.some(data => isComplexExt(data.ext));
|
|
283
|
+
for (const { ext, lang } of possibleExts) {
|
|
284
|
+
if (hasComplexExt && !isComplexExt(ext))
|
|
285
|
+
continue;
|
|
286
|
+
if (!hasComplexExt && isComplexExt(ext))
|
|
287
|
+
continue;
|
|
288
|
+
addResult(file, lang);
|
|
289
|
+
}
|
|
290
|
+
// Fallback to null if no language matches
|
|
291
|
+
if (!fileAssociations[file]) {
|
|
292
|
+
addResult(file, null);
|
|
293
|
+
}
|
|
294
|
+
}
|
|
295
|
+
// Narrow down file associations to the best fit
|
|
296
|
+
for (const file in fileAssociations) {
|
|
297
|
+
// Skip if file has explicit association
|
|
298
|
+
if (definiteness[file]) {
|
|
299
|
+
results.files.results[file] = fileAssociations[file][0];
|
|
300
|
+
continue;
|
|
301
|
+
}
|
|
302
|
+
// Skip binary files
|
|
303
|
+
if (!useRawContent && !opts.keepBinary) {
|
|
304
|
+
if (await (0, isbinaryfile_1.isBinaryFile)(file))
|
|
305
|
+
continue;
|
|
306
|
+
}
|
|
307
|
+
// Parse heuristics if applicable
|
|
308
|
+
if (opts.checkHeuristics)
|
|
309
|
+
for (const heuristics of heuristicsData.disambiguations) {
|
|
310
|
+
// Make sure the extension matches the current file
|
|
311
|
+
if (!fromShebang[file] && !heuristics.extensions.includes(extensions[file]))
|
|
312
|
+
continue;
|
|
313
|
+
// Load heuristic rules
|
|
314
|
+
for (const heuristic of heuristics.rules) {
|
|
315
|
+
// Make sure the language is not an array
|
|
316
|
+
if (Array.isArray(heuristic.language)) {
|
|
317
|
+
heuristic.language = heuristic.language[0];
|
|
318
|
+
}
|
|
319
|
+
// Make sure the results includes this language
|
|
320
|
+
const languageGroup = (_k = langData[heuristic.language]) === null || _k === void 0 ? void 0 : _k.group;
|
|
321
|
+
const matchesLang = fileAssociations[file].includes(heuristic.language);
|
|
322
|
+
const matchesParent = languageGroup && fileAssociations[file].includes(languageGroup);
|
|
323
|
+
if (!matchesLang && !matchesParent)
|
|
324
|
+
continue;
|
|
325
|
+
// Normalise heuristic data
|
|
326
|
+
const patterns = [];
|
|
327
|
+
const normalise = (contents) => patterns.push(...[contents].flat());
|
|
328
|
+
if (heuristic.pattern)
|
|
329
|
+
normalise(heuristic.pattern);
|
|
330
|
+
if (heuristic.named_pattern)
|
|
331
|
+
normalise(heuristicsData.named_patterns[heuristic.named_pattern]);
|
|
332
|
+
if (heuristic.and) {
|
|
333
|
+
for (const data of heuristic.and) {
|
|
334
|
+
if (data.pattern)
|
|
335
|
+
normalise(data.pattern);
|
|
336
|
+
if (data.named_pattern)
|
|
337
|
+
normalise(heuristicsData.named_patterns[data.named_pattern]);
|
|
338
|
+
}
|
|
339
|
+
}
|
|
340
|
+
// Check file contents and apply heuristic patterns
|
|
341
|
+
const fileContent = opts.fileContent ? manualFileContent[files.indexOf(file)] : await (0, read_file_1.default)(file).catch(() => null);
|
|
342
|
+
// Skip if file read errors
|
|
343
|
+
if (fileContent === null)
|
|
344
|
+
continue;
|
|
345
|
+
// Apply heuristics
|
|
346
|
+
if (!patterns.length || patterns.some(pattern => (0, convert_pcre_1.default)(pattern).test(fileContent))) {
|
|
347
|
+
results.files.results[file] = heuristic.language;
|
|
348
|
+
break;
|
|
349
|
+
}
|
|
350
|
+
}
|
|
351
|
+
}
|
|
352
|
+
// If no heuristics, assign a language
|
|
353
|
+
if (!results.files.results[file]) {
|
|
354
|
+
const possibleLangs = fileAssociations[file];
|
|
355
|
+
// Assign first language as a default option
|
|
356
|
+
const defaultLang = possibleLangs[0];
|
|
357
|
+
const alternativeLangs = possibleLangs.slice(1);
|
|
358
|
+
results.files.results[file] = defaultLang;
|
|
359
|
+
// List alternative languages if there are any
|
|
360
|
+
if (alternativeLangs.length > 0)
|
|
361
|
+
results.files.alternatives[file] = alternativeLangs;
|
|
362
|
+
}
|
|
363
|
+
}
|
|
364
|
+
// Skip specified categories
|
|
365
|
+
if ((_l = opts.categories) === null || _l === void 0 ? void 0 : _l.length) {
|
|
366
|
+
const categories = ['data', 'markup', 'programming', 'prose'];
|
|
367
|
+
const hiddenCategories = categories.filter(cat => !opts.categories.includes(cat));
|
|
368
|
+
for (const [file, lang] of Object.entries(results.files.results)) {
|
|
369
|
+
if (!hiddenCategories.some(cat => { var _a; return lang && ((_a = langData[lang]) === null || _a === void 0 ? void 0 : _a.type) === cat; })) {
|
|
370
|
+
continue;
|
|
371
|
+
}
|
|
372
|
+
delete results.files.results[file];
|
|
373
|
+
if (lang) {
|
|
374
|
+
delete results.languages.results[lang];
|
|
375
|
+
}
|
|
376
|
+
}
|
|
377
|
+
for (const category of hiddenCategories) {
|
|
378
|
+
for (const [lang, { type }] of Object.entries(results.languages.results)) {
|
|
379
|
+
if (type === category) {
|
|
380
|
+
delete results.languages.results[lang];
|
|
381
|
+
}
|
|
382
|
+
}
|
|
383
|
+
}
|
|
384
|
+
}
|
|
385
|
+
// Convert paths to relative
|
|
386
|
+
if (!useRawContent && opts.relativePaths) {
|
|
387
|
+
const newMap = {};
|
|
388
|
+
for (const [file, lang] of Object.entries(results.files.results)) {
|
|
389
|
+
let relPath = path_1.default.relative(process.cwd(), file).replace(/\\/g, '/');
|
|
390
|
+
if (!relPath.startsWith('../')) {
|
|
391
|
+
relPath = './' + relPath;
|
|
392
|
+
}
|
|
393
|
+
newMap[relPath] = lang;
|
|
394
|
+
}
|
|
395
|
+
results.files.results = newMap;
|
|
396
|
+
}
|
|
397
|
+
// Load language bytes size
|
|
398
|
+
for (const [file, lang] of Object.entries(results.files.results)) {
|
|
399
|
+
if (lang && !langData[lang])
|
|
400
|
+
continue;
|
|
401
|
+
const fileSize = (_o = (_m = manualFileContent[files.indexOf(file)]) === null || _m === void 0 ? void 0 : _m.length) !== null && _o !== void 0 ? _o : fs_1.default.statSync(file).size;
|
|
402
|
+
results.files.bytes += fileSize;
|
|
403
|
+
// If no language found, add extension in other section
|
|
404
|
+
if (!lang) {
|
|
405
|
+
const ext = path_1.default.extname(file);
|
|
406
|
+
const unknownType = ext === '' ? 'filenames' : 'extensions';
|
|
407
|
+
const name = ext === '' ? path_1.default.basename(file) : ext;
|
|
408
|
+
(_p = (_r = results.unknown[unknownType])[name]) !== null && _p !== void 0 ? _p : (_r[name] = 0);
|
|
409
|
+
results.unknown[unknownType][name] += fileSize;
|
|
410
|
+
results.unknown.bytes += fileSize;
|
|
411
|
+
continue;
|
|
412
|
+
}
|
|
413
|
+
// Add language and bytes data to corresponding section
|
|
414
|
+
const { type } = langData[lang];
|
|
415
|
+
(_q = (_s = results.languages.results)[lang]) !== null && _q !== void 0 ? _q : (_s[lang] = { type, bytes: 0, color: langData[lang].color });
|
|
416
|
+
if (opts.childLanguages) {
|
|
417
|
+
results.languages.results[lang].parent = langData[lang].group;
|
|
418
|
+
}
|
|
419
|
+
results.languages.results[lang].bytes += fileSize;
|
|
420
|
+
results.languages.bytes += fileSize;
|
|
421
|
+
}
|
|
422
|
+
// Set counts
|
|
423
|
+
results.files.count = Object.keys(results.files.results).length;
|
|
424
|
+
results.languages.count = Object.keys(results.languages.results).length;
|
|
425
|
+
results.unknown.count = Object.keys({ ...results.unknown.extensions, ...results.unknown.filenames }).length;
|
|
426
|
+
// Return
|
|
427
|
+
return results;
|
|
428
|
+
}
|
|
429
|
+
module.exports = analyse;
|