linguist-js 2.9.2 → 3.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (124) hide show
  1. package/bin/index.js +1 -1
  2. package/dist/analyser/classifiers/byAttributes.d.ts +3 -0
  3. package/dist/analyser/classifiers/byAttributes.js +13 -0
  4. package/dist/analyser/classifiers/byExtension.d.ts +3 -0
  5. package/dist/analyser/classifiers/byExtension.js +31 -0
  6. package/dist/analyser/classifiers/byFilename.d.ts +3 -0
  7. package/dist/analyser/classifiers/byFilename.js +10 -0
  8. package/dist/analyser/classifiers/byHeuristics.d.ts +3 -0
  9. package/dist/analyser/classifiers/byHeuristics.js +67 -0
  10. package/dist/analyser/classifiers/byModeline.d.ts +3 -0
  11. package/dist/analyser/classifiers/byModeline.js +22 -0
  12. package/dist/analyser/classifiers/byShebang.d.ts +3 -0
  13. package/dist/analyser/classifiers/byShebang.js +27 -0
  14. package/dist/analyser/index.d.ts +3 -0
  15. package/dist/analyser/index.js +12 -0
  16. package/dist/analyser/pipeline/aggregate.d.ts +4 -0
  17. package/dist/analyser/pipeline/aggregate.js +98 -0
  18. package/dist/analyser/pipeline/classify.d.ts +3 -0
  19. package/dist/analyser/pipeline/classify.js +32 -0
  20. package/dist/analyser/pipeline/filter.d.ts +2 -0
  21. package/dist/analyser/pipeline/filter.js +14 -0
  22. package/dist/analyser/pipeline/heuristics.d.ts +3 -0
  23. package/dist/analyser/pipeline/heuristics.js +12 -0
  24. package/dist/analyser/pipeline/normalise.d.ts +2 -0
  25. package/dist/analyser/pipeline/normalise.js +9 -0
  26. package/dist/cli/output/default.d.ts +3 -0
  27. package/dist/cli/output/default.js +106 -0
  28. package/dist/cli/output/tree.d.ts +3 -0
  29. package/dist/cli/output/tree.js +11 -0
  30. package/dist/cli/runCliAnalysis.d.ts +2 -0
  31. package/dist/cli/runCliAnalysis.js +26 -0
  32. package/dist/cli/utils.d.ts +2 -0
  33. package/dist/cli/utils.js +9 -0
  34. package/dist/cli.js +14 -143
  35. package/dist/entry/analyseFs.d.ts +2 -0
  36. package/dist/entry/analyseFs.js +11 -0
  37. package/dist/entry/analyseRaw.d.ts +4 -0
  38. package/dist/entry/analyseRaw.js +11 -0
  39. package/dist/index.d.ts +19 -4
  40. package/dist/index.js +18 -483
  41. package/dist/input/fromFilesystem.d.ts +4 -0
  42. package/dist/input/fromFilesystem.js +115 -0
  43. package/dist/input/fromRawContent.d.ts +2 -0
  44. package/dist/input/fromRawContent.js +19 -0
  45. package/dist/input/normaliseOpts.d.ts +2 -0
  46. package/dist/input/normaliseOpts.js +13 -0
  47. package/dist/program/classes/attributes.d.ts +11 -0
  48. package/dist/program/classes/attributes.js +37 -0
  49. package/dist/program/data/loadDataFiles.d.ts +4 -0
  50. package/dist/program/data/loadDataFiles.js +32 -0
  51. package/dist/program/data/retrieveData.d.ts +12 -0
  52. package/dist/program/data/retrieveData.js +27 -0
  53. package/dist/program/fs/normalisedPath.d.ts +3 -0
  54. package/dist/program/fs/normalisedPath.js +17 -0
  55. package/dist/program/fs/readFile.js +17 -0
  56. package/dist/program/fs/walkTree.d.ts +20 -0
  57. package/dist/program/fs/walkTree.js +82 -0
  58. package/dist/program/parsing/parseGitattributes.d.ts +17 -0
  59. package/dist/program/parsing/parseGitattributes.js +33 -0
  60. package/dist/program/parsing/parseGitignore.js +9 -0
  61. package/dist/program/processFiles.d.ts +9 -0
  62. package/dist/program/processFiles.js +111 -0
  63. package/dist/program/utils/pcre.js +35 -0
  64. package/dist/src/cli/output/default.d.ts +3 -0
  65. package/dist/src/cli/output/default.js +106 -0
  66. package/dist/src/cli/output/tree.d.ts +3 -0
  67. package/dist/src/cli/output/tree.js +11 -0
  68. package/dist/src/cli/runCliAnalysis.d.ts +2 -0
  69. package/dist/src/cli/runCliAnalysis.js +25 -0
  70. package/dist/src/cli/utils.d.ts +2 -0
  71. package/dist/src/cli/utils.js +9 -0
  72. package/dist/src/cli.d.ts +1 -0
  73. package/dist/src/cli.js +56 -0
  74. package/dist/src/helpers/convert-pcre.d.ts +2 -0
  75. package/dist/src/helpers/parse-gitignore.d.ts +1 -0
  76. package/dist/src/helpers/read-file.d.ts +5 -0
  77. package/dist/{helpers → src/helpers}/read-file.js +1 -1
  78. package/dist/src/index.d.ts +5 -0
  79. package/dist/src/index.js +470 -0
  80. package/dist/src/program/data/loadData.d.ts +4 -0
  81. package/dist/src/program/data/loadData.js +32 -0
  82. package/dist/src/program/fs/normalisedPath.d.ts +2 -0
  83. package/dist/src/program/fs/normalisedPath.js +7 -0
  84. package/dist/src/program/fs/readFile.d.ts +5 -0
  85. package/dist/src/program/fs/readFile.js +17 -0
  86. package/dist/src/program/fs/walkTree.d.ts +20 -0
  87. package/dist/src/program/fs/walkTree.js +82 -0
  88. package/dist/src/program/parsing/parseGitattributes.d.ts +17 -0
  89. package/dist/src/program/parsing/parseGitattributes.js +33 -0
  90. package/dist/src/program/parsing/parseGitignore.d.ts +1 -0
  91. package/dist/src/program/parsing/parseGitignore.js +9 -0
  92. package/dist/src/program/utils/pcre.d.ts +2 -0
  93. package/dist/src/program/utils/pcre.js +35 -0
  94. package/dist/src/types/schema.d.ts +37 -0
  95. package/dist/src/types/schema.js +1 -0
  96. package/dist/src/types/types.d.ts +65 -0
  97. package/dist/src/types/types.js +1 -0
  98. package/dist/types/schema.d.ts +37 -0
  99. package/dist/types/schema.js +1 -0
  100. package/dist/types/types.d.ts +84 -0
  101. package/dist/types/types.js +1 -0
  102. package/ext/generated.rb +6 -0
  103. package/ext/heuristics.yml +102 -11
  104. package/ext/languages.yml +526 -88
  105. package/ext/vendor.yml +1 -0
  106. package/package.json +14 -13
  107. package/readme.md +99 -111
  108. /package/dist/{helpers/read-file.d.ts → program/fs/readFile.d.ts} +0 -0
  109. /package/dist/{helpers/parse-gitignore.d.ts → program/parsing/parseGitignore.d.ts} +0 -0
  110. /package/dist/{helpers/convert-pcre.d.ts → program/utils/pcre.d.ts} +0 -0
  111. /package/dist/{helpers → src/helpers}/convert-pcre.js +0 -0
  112. /package/dist/{helpers → src/helpers}/load-data.d.ts +0 -0
  113. /package/dist/{helpers → src/helpers}/load-data.js +0 -0
  114. /package/dist/{helpers → src/helpers}/norm-path.d.ts +0 -0
  115. /package/dist/{helpers → src/helpers}/norm-path.js +0 -0
  116. /package/dist/{helpers → src/helpers}/parse-gitattributes.d.ts +0 -0
  117. /package/dist/{helpers → src/helpers}/parse-gitattributes.js +0 -0
  118. /package/dist/{helpers → src/helpers}/parse-gitignore.js +0 -0
  119. /package/dist/{helpers → src/helpers}/walk-tree.d.ts +0 -0
  120. /package/dist/{helpers → src/helpers}/walk-tree.js +0 -0
  121. /package/dist/{schema.d.ts → src/schema.d.ts} +0 -0
  122. /package/dist/{schema.js → src/schema.js} +0 -0
  123. /package/dist/{types.d.ts → src/types.d.ts} +0 -0
  124. /package/dist/{types.js → src/types.js} +0 -0
package/dist/index.js CHANGED
@@ -1,484 +1,19 @@
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;
1
+ import analyseFs from './entry/analyseFs.js';
2
+ import analyseRaw from './entry/analyseRaw.js';
3
+ // Entry point of the program
4
+ export default {
5
+ /**
6
+ * Analyse a list of folders and return language statistics
7
+ * @param inputPaths - An array of file paths to analyse
8
+ * @param options - Configuration options for the analysis
9
+ * @returns A promise that resolves to the analysis results
10
+ */
11
+ analyseFolders: analyseFs,
12
+ /**
13
+ * Analyse raw content and return language statistics
14
+ * @param inputContent - An object where keys are filenames and values are file contents
15
+ * @param options - Configuration options for the analysis
16
+ * @returns A promise that resolves to the analysis results
17
+ */
18
+ analyseRawContent: analyseRaw,
24
19
  };
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
- const norm_path_1 = require("./helpers/norm-path");
41
- async function analyse(rawPaths, opts = {}) {
42
- var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m, _o, _p, _q, _r, _s, _t;
43
- var _u, _v;
44
- const useRawContent = opts.fileContent !== undefined;
45
- const input = [rawPaths !== null && rawPaths !== void 0 ? rawPaths : []].flat();
46
- const manualFileContent = [(_a = opts.fileContent) !== null && _a !== void 0 ? _a : []].flat();
47
- // Normalise input option arguments
48
- opts = {
49
- calculateLines: (_b = opts.calculateLines) !== null && _b !== void 0 ? _b : true, // default to true if unset
50
- checkIgnored: !opts.quick,
51
- checkDetected: !opts.quick,
52
- checkAttributes: !opts.quick,
53
- checkHeuristics: !opts.quick,
54
- checkShebang: !opts.quick,
55
- checkModeline: !opts.quick,
56
- ...opts,
57
- };
58
- // Load data from github-linguist web repo
59
- const langData = await (0, load_data_1.default)('languages.yml', opts.offline).then(js_yaml_1.default.load);
60
- const vendorData = await (0, load_data_1.default)('vendor.yml', opts.offline).then(js_yaml_1.default.load);
61
- const docData = await (0, load_data_1.default)('documentation.yml', opts.offline).then(js_yaml_1.default.load);
62
- const heuristicsData = await (0, load_data_1.default)('heuristics.yml', opts.offline).then(js_yaml_1.default.load);
63
- const generatedData = await (0, load_data_1.default)('generated.rb', opts.offline).then(load_data_1.parseGeneratedDataFile);
64
- const vendorPaths = [...vendorData, ...docData, ...generatedData];
65
- // Setup main variables
66
- const fileAssociations = {};
67
- const extensions = {};
68
- const globOverrides = {};
69
- const results = {
70
- files: { count: 0, bytes: 0, lines: { total: 0, content: 0, code: 0 }, results: {}, alternatives: {} },
71
- languages: { count: 0, bytes: 0, lines: { total: 0, content: 0, code: 0 }, results: {} },
72
- unknown: { count: 0, bytes: 0, lines: { total: 0, content: 0, code: 0 }, extensions: {}, filenames: {} },
73
- };
74
- // Set a common root path so that vendor paths do not incorrectly match parent folders
75
- const resolvedInput = input.map(path => (0, norm_path_1.normPath)(path_1.default.resolve(path)));
76
- const commonRoot = (input.length > 1 ? (0, common_path_prefix_1.default)(resolvedInput) : resolvedInput[0]).replace(/\/?$/, '');
77
- const relPath = (file) => useRawContent ? file : (0, norm_path_1.normPath)(path_1.default.relative(commonRoot, file));
78
- const unRelPath = (file) => useRawContent ? file : (0, norm_path_1.normPath)(path_1.default.resolve(commonRoot, file));
79
- // Other helper functions
80
- const fileMatchesGlobs = (file, ...globs) => (0, ignore_1.default)().add(globs).ignores(relPath(file));
81
- const filterOutIgnored = (files, ignored) => ignored.filter(files.map(relPath)).map(unRelPath);
82
- //*PREPARE FILES AND DATA*//
83
- // Prepare list of ignored files
84
- const ignored = (0, ignore_1.default)();
85
- ignored.add('.git/');
86
- ignored.add((_c = opts.ignoredFiles) !== null && _c !== void 0 ? _c : []);
87
- const regexIgnores = opts.keepVendored ? [] : vendorPaths.map(path => RegExp(path, 'i'));
88
- // Load file paths and folders
89
- let files;
90
- if (useRawContent) {
91
- // Uses raw file content
92
- files = input;
93
- }
94
- else {
95
- // Uses directory on disc
96
- const data = (0, walk_tree_1.default)({ init: true, commonRoot, folderRoots: resolvedInput, folders: resolvedInput, ignored });
97
- files = data.files;
98
- }
99
- // Fetch and normalise gitattributes data of all subfolders and save to metadata
100
- const manualAttributes = {}; // Maps file globs to gitattribute boolean flags
101
- const getFlaggedGlobs = (attr, val) => {
102
- return Object.entries(manualAttributes).filter(([, attrs]) => attrs[attr] === val).map(([glob,]) => glob);
103
- };
104
- const findAttrsForPath = (filePath) => {
105
- const resultAttrs = {};
106
- for (const glob in manualAttributes) {
107
- if ((0, ignore_1.default)().add(glob).ignores(relPath(filePath))) {
108
- const matchingAttrs = manualAttributes[glob];
109
- for (const [attr, val] of Object.entries(matchingAttrs)) {
110
- if (val !== null)
111
- resultAttrs[attr] = val;
112
- }
113
- }
114
- }
115
- if (!JSON.stringify(resultAttrs)) {
116
- return null;
117
- }
118
- return resultAttrs;
119
- };
120
- if (!useRawContent && opts.checkAttributes) {
121
- const nestedAttrFiles = files.filter(file => file.endsWith('.gitattributes'));
122
- for (const attrFile of nestedAttrFiles) {
123
- const relAttrFile = relPath(attrFile);
124
- const relAttrFolder = path_1.default.dirname(relAttrFile);
125
- const contents = await (0, read_file_1.default)(attrFile);
126
- const parsed = (0, parse_gitattributes_1.default)(contents, relAttrFolder);
127
- for (const { glob, attrs } of parsed) {
128
- manualAttributes[glob] = attrs;
129
- }
130
- }
131
- }
132
- // Remove files that are linguist-ignored via regex by default unless explicitly unignored in gitattributes
133
- const filesToIgnore = [];
134
- for (const file of files) {
135
- const relFile = relPath(file);
136
- const isRegexIgnored = regexIgnores.some(pattern => pattern.test(relFile));
137
- if (!isRegexIgnored) {
138
- // Checking overrides is moot if file is not even marked as ignored by default
139
- continue;
140
- }
141
- const fileAttrs = findAttrsForPath(file);
142
- if ((fileAttrs === null || fileAttrs === void 0 ? void 0 : fileAttrs.generated) === false || (fileAttrs === null || fileAttrs === void 0 ? void 0 : fileAttrs.vendored) === false) {
143
- // File is explicitly marked as *not* to be ignored
144
- // do nothing
145
- }
146
- else {
147
- filesToIgnore.push(file);
148
- }
149
- }
150
- files = files.filter(file => !filesToIgnore.includes(file));
151
- // Apply vendor file path matches and filter out vendored files
152
- if (!opts.keepVendored) {
153
- // Get data of files that have been manually marked with metadata
154
- const vendorTrueGlobs = [...getFlaggedGlobs('vendored', true), ...getFlaggedGlobs('generated', true), ...getFlaggedGlobs('documentation', true)];
155
- const vendorFalseGlobs = [...getFlaggedGlobs('vendored', false), ...getFlaggedGlobs('generated', false), ...getFlaggedGlobs('documentation', false)];
156
- // Set up glob ignore object to use for expanding globs to match files
157
- const vendorTrueIgnore = (0, ignore_1.default)().add(vendorTrueGlobs);
158
- const vendorFalseIgnore = (0, ignore_1.default)().add(vendorFalseGlobs);
159
- // Remove all files marked as vendored by default
160
- const excludedFiles = files.filter(file => vendorPaths.some(pathPtn => RegExp(pathPtn, 'i').test(relPath(file))));
161
- files = files.filter(file => !excludedFiles.includes(file));
162
- // Re-add removed files that are overridden manually in gitattributes
163
- const overriddenExcludedFiles = excludedFiles.filter(file => vendorFalseIgnore.ignores(relPath(file)));
164
- files.push(...overriddenExcludedFiles);
165
- // Remove files explicitly marked as vendored in gitattributes
166
- files = files.filter(file => !vendorTrueIgnore.ignores(relPath(file)));
167
- }
168
- // Filter out binary files
169
- if (!opts.keepBinary) {
170
- // Filter out files that are binary by default
171
- files = files.filter(file => !binary_extensions_1.default.some(ext => file.endsWith('.' + ext)));
172
- // Filter out manually specified binary files
173
- const binaryIgnored = (0, ignore_1.default)().add(getFlaggedGlobs('binary', true));
174
- files = filterOutIgnored(files, binaryIgnored);
175
- // Re-add files manually marked not as binary
176
- const binaryUnignored = (0, ignore_1.default)().add(getFlaggedGlobs('binary', false));
177
- const unignoredList = filterOutIgnored(files, binaryUnignored);
178
- files.push(...unignoredList);
179
- }
180
- // Ignore specific languages
181
- for (const lang of (_d = opts.ignoredLanguages) !== null && _d !== void 0 ? _d : []) {
182
- for (const key in langData) {
183
- if (lang.toLowerCase() === key.toLowerCase()) {
184
- delete langData[key];
185
- break;
186
- }
187
- }
188
- }
189
- // Establish language overrides taken from gitattributes
190
- const forcedLangs = Object.entries(manualAttributes).filter(([, attrs]) => attrs.language);
191
- for (const [globPath, attrs] of forcedLangs) {
192
- let forcedLang = attrs.language;
193
- if (!forcedLang)
194
- continue;
195
- // If specified language is an alias, associate it with its full name
196
- if (!langData[forcedLang]) {
197
- const overrideLang = Object.entries(langData).find(entry => { var _a; return (_a = entry[1].aliases) === null || _a === void 0 ? void 0 : _a.includes(forcedLang.toLowerCase()); });
198
- if (overrideLang) {
199
- forcedLang = overrideLang[0];
200
- }
201
- }
202
- globOverrides[globPath] = forcedLang;
203
- }
204
- //*PARSE LANGUAGES*//
205
- const addResult = (file, result) => {
206
- if (!fileAssociations[file]) {
207
- fileAssociations[file] = [];
208
- extensions[file] = '';
209
- }
210
- // Set parent to result group if it is present
211
- // Is nullish if either `opts.childLanguages` is set or if there is no group
212
- const finalResult = !opts.childLanguages && result && langData[result] && langData[result].group || result;
213
- if (!fileAssociations[file].includes(finalResult)) {
214
- fileAssociations[file].push(finalResult);
215
- }
216
- extensions[file] = path_1.default.extname(file).toLowerCase();
217
- };
218
- const definiteness = {};
219
- const fromShebang = {};
220
- fileLoop: for (const file of files) {
221
- // Check manual override
222
- for (const globMatch in globOverrides) {
223
- if (!fileMatchesGlobs(file, globMatch))
224
- continue;
225
- // If the given file matches the glob, apply the override to the file
226
- const forcedLang = globOverrides[globMatch];
227
- addResult(file, forcedLang);
228
- definiteness[file] = true;
229
- continue fileLoop; // no need to check other heuristics, the classified language has been found
230
- }
231
- // Check first line for readability
232
- let firstLine;
233
- if (useRawContent) {
234
- firstLine = (_f = (_e = manualFileContent[files.indexOf(file)]) === null || _e === void 0 ? void 0 : _e.split('\n')[0]) !== null && _f !== void 0 ? _f : null;
235
- }
236
- else if (fs_1.default.existsSync(file) && !fs_1.default.lstatSync(file).isDirectory()) {
237
- firstLine = await (0, read_file_1.default)(file, true).catch(() => null);
238
- }
239
- else
240
- continue;
241
- // Skip if file is unreadable or blank
242
- if (firstLine === null)
243
- continue;
244
- // Check first line for explicit classification
245
- const hasShebang = opts.checkShebang && /^#!/.test(firstLine);
246
- const hasModeline = opts.checkModeline && /-\*-|(syntax|filetype|ft)\s*=/.test(firstLine);
247
- if (!opts.quick && (hasShebang || hasModeline)) {
248
- const matches = [];
249
- for (const [lang, data] of Object.entries(langData)) {
250
- const langMatcher = (lang) => `\\b${lang.toLowerCase().replace(/\W/g, '\\$&')}(?![\\w#+*]|-\*-)`;
251
- // Check for interpreter match
252
- if (opts.checkShebang && hasShebang) {
253
- const matchesInterpretor = (_g = data.interpreters) === null || _g === void 0 ? void 0 : _g.some(interpreter => firstLine.match(`\\b${interpreter}\\b`));
254
- if (matchesInterpretor)
255
- matches.push(lang);
256
- }
257
- // Check modeline declaration
258
- if (opts.checkModeline && hasModeline) {
259
- const modelineText = firstLine.toLowerCase().replace(/^.*-\*-(.+)-\*-.*$/, '$1');
260
- const matchesLang = modelineText.match(langMatcher(lang));
261
- const matchesAlias = (_h = data.aliases) === null || _h === void 0 ? void 0 : _h.some(lang => modelineText.match(langMatcher(lang)));
262
- if (matchesLang || matchesAlias)
263
- matches.push(lang);
264
- }
265
- }
266
- // Add identified language(s)
267
- if (matches.length) {
268
- for (const match of matches)
269
- addResult(file, match);
270
- if (matches.length === 1)
271
- definiteness[file] = true;
272
- fromShebang[file] = true;
273
- continue;
274
- }
275
- }
276
- // Search each language
277
- let skipExts = false;
278
- // Check if filename is a match
279
- for (const lang in langData) {
280
- const matchesName = (_j = langData[lang].filenames) === null || _j === void 0 ? void 0 : _j.some(name => path_1.default.basename(file.toLowerCase()) === name.toLowerCase());
281
- if (matchesName) {
282
- addResult(file, lang);
283
- skipExts = true;
284
- }
285
- }
286
- // Check if extension is a match
287
- const possibleExts = [];
288
- if (!skipExts)
289
- for (const lang in langData) {
290
- const extMatches = (_k = langData[lang].extensions) === null || _k === void 0 ? void 0 : _k.filter(ext => file.toLowerCase().endsWith(ext.toLowerCase()));
291
- if (extMatches === null || extMatches === void 0 ? void 0 : extMatches.length) {
292
- for (const ext of extMatches)
293
- possibleExts.push({ ext, lang });
294
- }
295
- }
296
- // Apply more specific extension if available
297
- const isComplexExt = (ext) => /\..+\./.test(ext);
298
- const hasComplexExt = possibleExts.some(data => isComplexExt(data.ext));
299
- for (const { ext, lang } of possibleExts) {
300
- if (hasComplexExt && !isComplexExt(ext))
301
- continue;
302
- if (!hasComplexExt && isComplexExt(ext))
303
- continue;
304
- addResult(file, lang);
305
- }
306
- // Fallback to null if no language matches
307
- if (!fileAssociations[file]) {
308
- addResult(file, null);
309
- }
310
- }
311
- // Narrow down file associations to the best fit
312
- for (const file in fileAssociations) {
313
- // Skip if file has explicit association
314
- if (definiteness[file]) {
315
- results.files.results[file] = fileAssociations[file][0];
316
- continue;
317
- }
318
- // Skip binary files
319
- if (!useRawContent && !opts.keepBinary) {
320
- if (await (0, isbinaryfile_1.isBinaryFile)(file))
321
- continue;
322
- }
323
- // Parse heuristics if applicable
324
- if (opts.checkHeuristics)
325
- for (const heuristics of heuristicsData.disambiguations) {
326
- // Make sure the extension matches the current file
327
- if (!fromShebang[file] && !heuristics.extensions.includes(extensions[file]))
328
- continue;
329
- // Load heuristic rules
330
- for (const heuristic of heuristics.rules) {
331
- // Make sure the language is not an array
332
- if (Array.isArray(heuristic.language)) {
333
- heuristic.language = heuristic.language[0];
334
- }
335
- // Make sure the results includes this language
336
- const languageGroup = (_l = langData[heuristic.language]) === null || _l === void 0 ? void 0 : _l.group;
337
- const matchesLang = fileAssociations[file].includes(heuristic.language);
338
- const matchesParent = languageGroup && fileAssociations[file].includes(languageGroup);
339
- if (!matchesLang && !matchesParent)
340
- continue;
341
- // Normalise heuristic data
342
- const patterns = [];
343
- const normalise = (contents) => patterns.push(...[contents].flat());
344
- if (heuristic.pattern)
345
- normalise(heuristic.pattern);
346
- if (heuristic.named_pattern)
347
- normalise(heuristicsData.named_patterns[heuristic.named_pattern]);
348
- if (heuristic.and) {
349
- for (const data of heuristic.and) {
350
- if (data.pattern)
351
- normalise(data.pattern);
352
- if (data.named_pattern)
353
- normalise(heuristicsData.named_patterns[data.named_pattern]);
354
- }
355
- }
356
- // Check file contents and apply heuristic patterns
357
- const fileContent = opts.fileContent ? manualFileContent[files.indexOf(file)] : await (0, read_file_1.default)(file).catch(() => null);
358
- // Skip if file read errors
359
- if (fileContent === null)
360
- continue;
361
- // Apply heuristics
362
- if (!patterns.length || patterns.some(pattern => (0, convert_pcre_1.default)(pattern).test(fileContent))) {
363
- results.files.results[file] = heuristic.language;
364
- break;
365
- }
366
- }
367
- }
368
- // If no heuristics, assign a language
369
- if (!results.files.results[file]) {
370
- const possibleLangs = fileAssociations[file];
371
- // Assign first language as a default option
372
- const defaultLang = possibleLangs[0];
373
- const alternativeLangs = possibleLangs.slice(1);
374
- results.files.results[file] = defaultLang;
375
- // List alternative languages if there are any
376
- if (alternativeLangs.length > 0)
377
- results.files.alternatives[file] = alternativeLangs;
378
- }
379
- }
380
- // Skip specified categories
381
- if ((_m = opts.categories) === null || _m === void 0 ? void 0 : _m.length) {
382
- const categories = ['data', 'markup', 'programming', 'prose'];
383
- const hiddenCategories = categories.filter(cat => !opts.categories.includes(cat));
384
- for (const [file, lang] of Object.entries(results.files.results)) {
385
- // Skip if language is not hidden
386
- if (!hiddenCategories.some(cat => { var _a; return lang && ((_a = langData[lang]) === null || _a === void 0 ? void 0 : _a.type) === cat; }))
387
- continue;
388
- // Skip if language is forced as detectable
389
- if (opts.checkDetected) {
390
- const detectable = (0, ignore_1.default)().add(getFlaggedGlobs('detectable', true));
391
- if (detectable.ignores(relPath(file)))
392
- continue;
393
- }
394
- // Delete result otherwise
395
- delete results.files.results[file];
396
- if (lang)
397
- delete results.languages.results[lang];
398
- }
399
- for (const category of hiddenCategories) {
400
- for (const [lang, { type }] of Object.entries(results.languages.results)) {
401
- if (type === category) {
402
- delete results.languages.results[lang];
403
- }
404
- }
405
- }
406
- }
407
- // Convert paths to relative
408
- if (!useRawContent && opts.relativePaths) {
409
- const newMap = {};
410
- for (const [file, lang] of Object.entries(results.files.results)) {
411
- let relPath = (0, norm_path_1.normPath)(path_1.default.relative(process.cwd(), file));
412
- if (!relPath.startsWith('../')) {
413
- relPath = './' + relPath;
414
- }
415
- newMap[relPath] = lang;
416
- }
417
- results.files.results = newMap;
418
- }
419
- // Load language bytes size
420
- for (const [file, lang] of Object.entries(results.files.results)) {
421
- if (lang && !langData[lang])
422
- continue;
423
- // Calculate file size
424
- const fileSize = (_p = (_o = manualFileContent[files.indexOf(file)]) === null || _o === void 0 ? void 0 : _o.length) !== null && _p !== void 0 ? _p : fs_1.default.statSync(file).size;
425
- // Calculate lines of code
426
- const loc = { total: 0, content: 0, code: 0 };
427
- if (opts.calculateLines) {
428
- const fileContent = (_r = ((_q = manualFileContent[files.indexOf(file)]) !== null && _q !== void 0 ? _q : fs_1.default.readFileSync(file).toString())) !== null && _r !== void 0 ? _r : '';
429
- const allLines = fileContent.split(/\r?\n/gm);
430
- loc.total = allLines.length;
431
- loc.content = allLines.filter(line => line.trim().length > 0).length;
432
- const codeLines = fileContent
433
- .replace(/^\s*(\/\/|# |;|--).+/gm, '')
434
- .replace(/\/\*.+\*\/|<!--.+-->/sg, '');
435
- loc.code = codeLines.split(/\r?\n/gm).filter(line => line.trim().length > 0).length;
436
- }
437
- // Apply to files totals
438
- results.files.bytes += fileSize;
439
- results.files.lines.total += loc.total;
440
- results.files.lines.content += loc.content;
441
- results.files.lines.code += loc.code;
442
- // Add results to 'languages' section if language match found, or 'unknown' section otherwise
443
- if (lang) {
444
- const { type } = langData[lang];
445
- // set default if unset
446
- (_s = (_u = results.languages.results)[lang]) !== null && _s !== void 0 ? _s : (_u[lang] = { type, bytes: 0, lines: { total: 0, content: 0, code: 0 }, color: langData[lang].color });
447
- // apply results to 'languages' section
448
- if (opts.childLanguages) {
449
- results.languages.results[lang].parent = langData[lang].group;
450
- }
451
- results.languages.results[lang].bytes += fileSize;
452
- results.languages.bytes += fileSize;
453
- results.languages.results[lang].lines.total += loc.total;
454
- results.languages.results[lang].lines.content += loc.content;
455
- results.languages.results[lang].lines.code += loc.code;
456
- results.languages.lines.total += loc.total;
457
- results.languages.lines.content += loc.content;
458
- results.languages.lines.code += loc.code;
459
- }
460
- else {
461
- const ext = path_1.default.extname(file);
462
- const unknownType = ext ? 'extensions' : 'filenames';
463
- const name = ext || path_1.default.basename(file);
464
- // apply results to 'unknown' section
465
- (_t = (_v = results.unknown[unknownType])[name]) !== null && _t !== void 0 ? _t : (_v[name] = 0);
466
- results.unknown[unknownType][name] += fileSize;
467
- results.unknown.bytes += fileSize;
468
- results.unknown.lines.total += loc.total;
469
- results.unknown.lines.content += loc.content;
470
- results.unknown.lines.code += loc.code;
471
- }
472
- }
473
- // Set lines output to NaN when line calculation is disabled
474
- if (opts.calculateLines === false) {
475
- results.files.lines = { total: NaN, content: NaN, code: NaN };
476
- }
477
- // Set counts
478
- results.files.count = Object.keys(results.files.results).length;
479
- results.languages.count = Object.keys(results.languages.results).length;
480
- results.unknown.count = Object.keys({ ...results.unknown.extensions, ...results.unknown.filenames }).length;
481
- // Return
482
- return results;
483
- }
484
- module.exports = analyse;
@@ -0,0 +1,4 @@
1
+ import * as T from '../types/types.js';
2
+ type FileInput = string;
3
+ export default function fromFilesystem(input: FileInput[], opts: T.Options, vendorPaths: string[]): Promise<T.VirtualFile[]>;
4
+ export {};
@@ -0,0 +1,115 @@
1
+ import commonPrefix from 'common-path-prefix';
2
+ import ignore from 'ignore';
3
+ import FS from 'node:fs';
4
+ import Path from 'node:path';
5
+ import Attributes from '../program/classes/attributes.js';
6
+ import { getFileExtension, normPath } from '../program/fs/normalisedPath.js';
7
+ import walkTree from '../program/fs/walkTree.js';
8
+ import parseGitattributes from '../program/parsing/parseGitattributes.js';
9
+ const binaryData = JSON.parse(FS.readFileSync(new URL('../../node_modules/binary-extensions/binary-extensions.json', import.meta.url), 'utf-8'));
10
+ export default async function fromFilesystem(input, opts, vendorPaths) {
11
+ const resolvedInput = input.map((path) => normPath(Path.resolve(path)));
12
+ const commonRoot = (resolvedInput.length > 1 ? commonPrefix(resolvedInput) : resolvedInput[0]).replace(/\/?$/, '');
13
+ const relPath = (file) => normPath(Path.relative(commonRoot, file));
14
+ const ignored = ignore();
15
+ ignored.add('.git/');
16
+ ignored.add(opts.ignoredFiles ?? []);
17
+ const regexIgnores = opts.keepVendored ? [] : vendorPaths.map((path) => RegExp(path, 'i'));
18
+ const directoryInputs = resolvedInput.filter((path) => FS.existsSync(path) && FS.lstatSync(path).isDirectory());
19
+ const fileInputs = resolvedInput.filter((path) => FS.existsSync(path) && !FS.lstatSync(path).isDirectory());
20
+ let files = [];
21
+ if (directoryInputs.length) {
22
+ const data = walkTree({ init: true, commonRoot, folderRoots: directoryInputs, folders: directoryInputs, ignored });
23
+ files = [...data.files];
24
+ }
25
+ files.push(...fileInputs);
26
+ files = [...new Set(files)];
27
+ // Establish language overrides taken from gitattributes
28
+ const manualAttributes = new Attributes();
29
+ if (opts.checkAttributes) {
30
+ const nestedAttrFiles = files.filter((file) => file.endsWith('.gitattributes'));
31
+ for (const attrFile of nestedAttrFiles) {
32
+ const relAttrFile = relPath(attrFile);
33
+ const relAttrFolder = Path.dirname(relAttrFile);
34
+ const contents = FS.readFileSync(attrFile, 'utf-8');
35
+ const parsed = parseGitattributes(contents, relAttrFolder);
36
+ for (const { glob, attrs } of parsed) {
37
+ manualAttributes.add(glob, attrs);
38
+ }
39
+ }
40
+ }
41
+ const filesToIgnore = [];
42
+ for (const file of files) {
43
+ const relative = relPath(file);
44
+ const isRegexIgnored = regexIgnores.some((pattern) => pattern.test(relative));
45
+ if (!isRegexIgnored)
46
+ continue;
47
+ const fileAttrs = manualAttributes.findAttrsForPath(relative);
48
+ if (fileAttrs?.generated === false || fileAttrs?.vendored === false)
49
+ continue;
50
+ filesToIgnore.push(file);
51
+ }
52
+ files = files.filter((file) => !filesToIgnore.includes(file));
53
+ if (!opts.keepVendored) {
54
+ const vendorTrueGlobs = [
55
+ ...manualAttributes.getFlaggedGlobs('vendored', true),
56
+ ...manualAttributes.getFlaggedGlobs('generated', true),
57
+ ...manualAttributes.getFlaggedGlobs('documentation', true),
58
+ ];
59
+ const vendorFalseGlobs = [
60
+ ...manualAttributes.getFlaggedGlobs('vendored', false),
61
+ ...manualAttributes.getFlaggedGlobs('generated', false),
62
+ ...manualAttributes.getFlaggedGlobs('documentation', false),
63
+ ];
64
+ const vendorTrueIgnore = ignore().add(vendorTrueGlobs);
65
+ const vendorFalseIgnore = ignore().add(vendorFalseGlobs);
66
+ const excludedFiles = files.filter((file) => vendorPaths.some((pathPtn) => RegExp(pathPtn, 'i').test(relPath(file))));
67
+ files = files.filter((file) => !excludedFiles.includes(file));
68
+ const overriddenExcludedFiles = excludedFiles.filter((file) => vendorFalseIgnore.ignores(relPath(file)));
69
+ files.push(...overriddenExcludedFiles);
70
+ files = files.filter((file) => !vendorTrueIgnore.ignores(relPath(file)));
71
+ }
72
+ if (!opts.keepBinary) {
73
+ files = files.filter((file) => !binaryData.some((ext) => file.toLowerCase().endsWith(`.${ext}`)));
74
+ const binaryIgnored = ignore().add(manualAttributes.getFlaggedGlobs('binary', true));
75
+ files = files.filter((file) => !binaryIgnored.ignores(relPath(file)));
76
+ const binaryUnignored = ignore().add(manualAttributes.getFlaggedGlobs('binary', false));
77
+ const binaryUnignoredList = files.filter((file) => binaryUnignored.ignores(relPath(file)));
78
+ files.push(...binaryUnignoredList);
79
+ }
80
+ const fileSet = new Set(files);
81
+ const virtualFiles = [];
82
+ for (const file of fileSet) {
83
+ if (!FS.existsSync(file) || FS.lstatSync(file).isDirectory())
84
+ continue;
85
+ const content = FS.readFileSync(file, 'utf-8');
86
+ const firstLine = content.split(/\r?\n/)[0] ?? '';
87
+ const relative = relPath(file);
88
+ const fileAttrs = manualAttributes.findAttrsForPath(relative);
89
+ const metadata = {};
90
+ if (fileAttrs?.vendored === true)
91
+ metadata.vendored = true;
92
+ if (fileAttrs?.generated === true)
93
+ metadata.generated = true;
94
+ if (fileAttrs?.documentation === true)
95
+ metadata.documentation = true;
96
+ const attributes = fileAttrs
97
+ ? {
98
+ language: fileAttrs.language ?? undefined,
99
+ binary: fileAttrs.binary === true ? true : undefined,
100
+ detectable: fileAttrs.detectable === true ? true : undefined,
101
+ }
102
+ : undefined;
103
+ virtualFiles.push({
104
+ path: file,
105
+ content,
106
+ firstLine,
107
+ size: Buffer.byteLength(content, 'utf-8'),
108
+ extension: getFileExtension(file),
109
+ isBinary: fileAttrs?.binary === true || binaryData.some((ext) => file.toLowerCase().endsWith(`.${ext}`)),
110
+ metadata: Object.keys(metadata).length ? metadata : undefined,
111
+ attributes,
112
+ });
113
+ }
114
+ return virtualFiles;
115
+ }
@@ -0,0 +1,2 @@
1
+ import * as T from '../types/types.js';
2
+ export default function fromRawContent(contents: Record<string, string>, vendorPaths?: string[]): T.VirtualFile[];
@@ -0,0 +1,19 @@
1
+ import { getFileExtension } from '../program/fs/normalisedPath.js';
2
+ export default function fromRawContent(contents, vendorPaths = []) {
3
+ return Object.entries(contents).map(([path, content]) => {
4
+ const extension = getFileExtension(path);
5
+ const metadata = {};
6
+ if (vendorPaths.some((pathPtn) => RegExp(pathPtn, 'i').test(path))) {
7
+ metadata.vendored = true;
8
+ }
9
+ return {
10
+ path,
11
+ content,
12
+ firstLine: content.split(/\r?\n/)[0] ?? '',
13
+ size: Buffer.byteLength(content, 'utf-8'),
14
+ extension,
15
+ isBinary: false,
16
+ metadata: Object.keys(metadata).length ? metadata : undefined,
17
+ };
18
+ });
19
+ }
@@ -0,0 +1,2 @@
1
+ import * as T from '../types/types.js';
2
+ export default function normaliseOpts(opts: T.Options): T.Options;
@@ -0,0 +1,13 @@
1
+ export default function normaliseOpts(opts) {
2
+ // Normalise input option arguments
3
+ return {
4
+ calculateLines: opts.calculateLines ?? true, // default to true if unset
5
+ checkIgnored: !opts.quick,
6
+ checkDetected: !opts.quick,
7
+ checkAttributes: !opts.quick,
8
+ checkHeuristics: !opts.quick,
9
+ checkShebang: !opts.quick,
10
+ checkModeline: !opts.quick,
11
+ ...opts,
12
+ };
13
+ }