linguist-js 2.2.1 → 2.4.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.
package/dist/cli.js CHANGED
@@ -6,7 +6,6 @@ Object.defineProperty(exports, "__esModule", { value: true });
6
6
  const VERSION = require('../package.json').version;
7
7
  const commander_1 = require("commander");
8
8
  const index_1 = __importDefault(require("./index"));
9
- const walk_tree_1 = __importDefault(require("./helpers/walk-tree"));
10
9
  const colouredMsg = ([r, g, b], msg) => `\u001B[${38};2;${r};${g};${b}m${msg}${'\u001b[0m'}`;
11
10
  const hexToRgb = (hex) => [parseInt(hex.slice(1, 3), 16), parseInt(hex.slice(3, 5), 16), parseInt(hex.slice(5, 7), 16)];
12
11
  commander_1.program
@@ -27,6 +26,7 @@ commander_1.program
27
26
  .option('-I|--checkIgnored [bool]', 'Force the checking of gitignore files', true)
28
27
  .option('-H|--checkHeuristics [bool]', 'Apply heuristics to ambiguous languages', true)
29
28
  .option('-S|--checkShebang [bool]', 'Check shebang lines for explicit classification', true)
29
+ .option('-M|--checkModeline [bool]', 'Check modelines for explicit classification', true)
30
30
  .helpOption(`-h|--help`, 'Display this help message')
31
31
  .version(VERSION, '-v|--version', 'Display the installed version of linguist-js');
32
32
  commander_1.program.parse(process.argv);
@@ -53,13 +53,11 @@ if (args.analyze)
53
53
  const root = args.analyze === true ? '.' : args.analyze;
54
54
  const data = await (0, index_1.default)(root, args);
55
55
  const { files, languages, unknown } = data;
56
- // Get file count
57
- let totalFiles = (0, walk_tree_1.default)(root).files.length;
58
56
  // Print output
59
57
  if (!args.json) {
60
58
  const sortedEntries = Object.entries(languages.results).sort((a, b) => a[1].bytes < b[1].bytes ? +1 : -1);
61
59
  const totalBytes = languages.bytes;
62
- console.log(`\n Analysed ${files.bytes.toLocaleString()} B from ${totalFiles} files (${totalFiles - files.count} ignored) with linguist-js`);
60
+ console.log(`\n Analysed ${files.bytes.toLocaleString()} B from ${files.count} files with linguist-js`);
63
61
  console.log(`\n Language analysis results:`);
64
62
  let count = 0;
65
63
  if (sortedEntries.length === 0)
@@ -3,23 +3,26 @@ Object.defineProperty(exports, "__esModule", { value: true });
3
3
  /** Convert a PCRE regex into JS. */
4
4
  function pcre(regex) {
5
5
  let finalRegex = regex;
6
+ const replace = (search, replace) => finalRegex = finalRegex.replace(search, replace);
6
7
  const finalFlags = new Set();
7
8
  // Convert inline flag declarations
8
9
  const inlineMatches = regex.matchAll(/\?([a-z]):/g);
9
10
  const startMatches = regex.matchAll(/\(\?([a-z]+)\)/g);
10
11
  for (const [match, flags] of [...inlineMatches, ...startMatches]) {
11
- finalRegex = finalRegex.replace(match, '');
12
+ replace(match, '');
12
13
  [...flags].forEach(flag => finalFlags.add(flag));
13
14
  }
14
- // Remove invalid modifiers
15
- finalRegex = finalRegex.replace(/([*+]){2}/g, '$1');
15
+ // Remove invalid syntax
16
+ replace(/([*+]){2}/g, '$1');
17
+ replace(/\(\?>/g, '(?:');
16
18
  // Remove start/end-of-file markers
17
19
  if (/\\[AZ]/.test(finalRegex)) {
18
- finalRegex = finalRegex.replace(/\\A/g, '^').replace(/\\Z/g, '$');
20
+ replace(/\\A/g, '^').replace(/\\Z/g, '$');
19
21
  finalFlags.delete('m');
20
22
  }
21
23
  else
22
24
  finalFlags.add('m');
25
+ // Return final regex
23
26
  return RegExp(finalRegex, [...finalFlags].join(''));
24
27
  }
25
28
  exports.default = pcre;
@@ -8,28 +8,50 @@ const path_1 = __importDefault(require("path"));
8
8
  const allFiles = new Set();
9
9
  const allFolders = new Set();
10
10
  /** Generate list of files in a directory. */
11
- function walk(folder, ignored = []) {
12
- if (Array.isArray(folder)) {
13
- for (const path of folder)
14
- walk(path, ignored);
15
- }
16
- else {
17
- const files = fs_1.default.readdirSync(folder);
11
+ function walk(root, folders, ignored = []) {
12
+ // Switch out paths that expect being in root
13
+ ignored = ignored.map(match => match.replace(/^\^/, '^\\./'));
14
+ // Walk tree of a folder
15
+ if (folders.length === 1) {
16
+ const folder = folders[0];
17
+ // Get list of files and folders inside this folder
18
+ const files = fs_1.default.readdirSync(folder).map(file => {
19
+ // Create path relative to root
20
+ const base = path_1.default.resolve(folder, file).replace(/\\/g, '/').replace(root, '.');
21
+ // Add trailing slash to mark directories
22
+ const isDir = fs_1.default.lstatSync(path_1.default.resolve(root, base)).isDirectory();
23
+ return isDir ? `${base}/` : base;
24
+ });
25
+ // Loop through files and folders
18
26
  for (const file of files) {
19
- const path = path_1.default.resolve(folder, file).replace(/\\/g, '/');
20
- if (!fs_1.default.existsSync(path) || ignored.some(pattern => pattern.test(path)))
27
+ // Create absolute path for disc operations
28
+ const path = path_1.default.resolve(root, file).replace(/\\/g, '/');
29
+ // Skip if nonexistant or ignored
30
+ if (!fs_1.default.existsSync(path) || ignored.some(pattern => RegExp(pattern).test(file)))
21
31
  continue;
22
- allFolders.add(folder.replace(/\\/g, '/'));
23
- if (fs_1.default.lstatSync(path).isDirectory()) {
32
+ // Add absolute folder path to list
33
+ allFolders.add(path_1.default.resolve(folder).replace(/\\/g, '/'));
34
+ // Check if this is a folder or file
35
+ if (file.endsWith('/')) {
36
+ // Recurse into subfolders
24
37
  allFolders.add(path);
25
- walk(path, ignored);
26
- continue;
38
+ walk(root, [path], ignored);
39
+ }
40
+ else {
41
+ // Add relative file path to list
42
+ allFiles.add(path);
27
43
  }
28
- allFiles.add(path);
29
44
  }
30
45
  }
46
+ // Recurse into all folders
47
+ else {
48
+ for (const path of folders) {
49
+ walk(root, [path], ignored);
50
+ }
51
+ }
52
+ // Return absolute files and folders lists
31
53
  return {
32
- files: [...allFiles],
54
+ files: [...allFiles].map(file => file.replace(/^\./, root)),
33
55
  folders: [...allFolders],
34
56
  };
35
57
  }
package/dist/index.js CHANGED
@@ -6,6 +6,7 @@ const fs_1 = __importDefault(require("fs"));
6
6
  const path_1 = __importDefault(require("path"));
7
7
  const js_yaml_1 = __importDefault(require("js-yaml"));
8
8
  const glob_to_regexp_1 = __importDefault(require("glob-to-regexp"));
9
+ const common_path_prefix_1 = __importDefault(require("common-path-prefix"));
9
10
  const binary_extensions_1 = __importDefault(require("binary-extensions"));
10
11
  const isbinaryfile_1 = require("isbinaryfile");
11
12
  const walk_tree_1 = __importDefault(require("./helpers/walk-tree"));
@@ -14,14 +15,21 @@ const read_file_1 = __importDefault(require("./helpers/read-file"));
14
15
  const convert_pcre_1 = __importDefault(require("./helpers/convert-pcre"));
15
16
  const convert_glob_1 = __importDefault(require("./helpers/convert-glob"));
16
17
  async function analyse(input, opts = {}) {
17
- var _a, _b, _c, _d, _e, _f, _g, _h, _j;
18
- var _k, _l, _m;
18
+ var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m, _o, _p, _q, _r, _s;
19
+ var _t, _u, _v;
20
+ const useRawContent = opts.fileContent !== undefined;
21
+ input = [input !== null && input !== void 0 ? input : []].flat();
22
+ opts.fileContent = [(_a = opts.fileContent) !== null && _a !== void 0 ? _a : []].flat();
23
+ // Load data from github-linguist web repo
19
24
  const langData = await (0, load_data_1.default)('languages.yml').then(js_yaml_1.default.load);
20
25
  const vendorData = await (0, load_data_1.default)('vendor.yml').then(js_yaml_1.default.load);
26
+ const docData = await (0, load_data_1.default)('documentation.yml').then(js_yaml_1.default.load);
21
27
  const heuristicsData = await (0, load_data_1.default)('heuristics.yml').then(js_yaml_1.default.load);
22
28
  const generatedData = await (0, load_data_1.default)('generated.rb').then(text => { var _a; return (_a = text.match(/(?<=name\.match\(\/).+?(?=(?<!\\)\/\))/gm)) !== null && _a !== void 0 ? _a : []; });
23
- vendorData.push(...generatedData);
29
+ const vendorPaths = [...vendorData, ...docData, ...generatedData];
30
+ // Setup main variables
24
31
  const fileAssociations = {};
32
+ const definiteness = {};
25
33
  const extensions = {};
26
34
  const overrides = {};
27
35
  const results = {
@@ -29,16 +37,41 @@ async function analyse(input, opts = {}) {
29
37
  languages: { count: 0, bytes: 0, results: {} },
30
38
  unknown: { count: 0, bytes: 0, extensions: {}, filenames: {} },
31
39
  };
32
- const ignoredFiles = [
33
- /\/\.git\//,
34
- opts.keepVendored ? [] : vendorData.map(path => RegExp(path)),
35
- (_b = (_a = opts.ignoredFiles) === null || _a === void 0 ? void 0 : _a.map(path => (0, glob_to_regexp_1.default)('*' + path + '*', { extended: true }))) !== null && _b !== void 0 ? _b : [],
36
- ].flat();
37
- let { files, folders } = (0, walk_tree_1.default)(input !== null && input !== void 0 ? input : '.', ignoredFiles);
40
+ // Prepare list of ignored files
41
+ const ignoredFiles = ['(^|\\/)\\.git\\/'];
42
+ if (!opts.keepVendored) {
43
+ ignoredFiles.push(...vendorPaths);
44
+ }
45
+ if (opts.ignoredFiles) {
46
+ ignoredFiles.push(...opts.ignoredFiles.map(path => (0, glob_to_regexp_1.default)('*' + path + '*', { extended: true }).source));
47
+ }
48
+ // Load file paths and folders
49
+ let files, folders;
50
+ if (useRawContent) {
51
+ // Uses raw file content
52
+ files = input;
53
+ folders = [''];
54
+ }
55
+ else {
56
+ // Uses directory on disc
57
+ // Set a common root path so that vendor paths do not incorrectly match parent folders
58
+ const resolvedInput = input.map(path => path_1.default.resolve(path).replace(/\\/g, '/'));
59
+ const commonRoot = (input.length > 1 ? (0, common_path_prefix_1.default)(resolvedInput) : resolvedInput[0]).replace(/\/?$/, '');
60
+ const data = (0, walk_tree_1.default)(commonRoot, input, ignoredFiles);
61
+ files = data.files;
62
+ folders = data.folders;
63
+ }
38
64
  // Apply aliases
39
- opts = { checkIgnored: !opts.quick, checkAttributes: !opts.quick, checkHeuristics: !opts.quick, checkShebang: !opts.quick, ...opts };
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
+ };
40
73
  // Ignore specific languages
41
- for (const lang of (_c = opts.ignoredLanguages) !== null && _c !== void 0 ? _c : []) {
74
+ for (const lang of (_b = opts.ignoredLanguages) !== null && _b !== void 0 ? _b : []) {
42
75
  for (const key in langData) {
43
76
  if (lang.toLowerCase() === key.toLowerCase()) {
44
77
  delete langData[key];
@@ -50,7 +83,7 @@ async function analyse(input, opts = {}) {
50
83
  const customIgnored = [];
51
84
  const customBinary = [];
52
85
  const customText = [];
53
- if (!opts.quick) {
86
+ if (!useRawContent && opts.checkAttributes) {
54
87
  for (const folder of folders) {
55
88
  // Skip if folder is marked in gitattributes
56
89
  if (customIgnored.some(path => (0, convert_glob_1.default)(path).test(folder)))
@@ -97,7 +130,7 @@ async function analyse(input, opts = {}) {
97
130
  }
98
131
  }
99
132
  // Check vendored files
100
- if (!opts.keepVendored) {
133
+ if (!useRawContent && !opts.keepVendored) {
101
134
  // Filter out any files that match a vendor file path
102
135
  const matcher = (match) => RegExp(match.replace(/\/$/, '/.+$').replace(/^\.\//, ''));
103
136
  files = files.filter(file => !customIgnored.some(pattern => matcher(pattern).test(file)));
@@ -110,33 +143,56 @@ async function analyse(input, opts = {}) {
110
143
  }
111
144
  const parent = !opts.childLanguages && result && langData[result].group || false;
112
145
  fileAssociations[file].push(parent || result);
113
- extensions[file] = path_1.default.extname(file);
146
+ extensions[file] = path_1.default.extname(file).toLowerCase();
114
147
  };
115
148
  const overridesArray = Object.entries(overrides);
116
149
  // List all languages that could be associated with a given file
117
150
  for (const file of files) {
118
- if (!fs_1.default.existsSync(file) || fs_1.default.lstatSync(file).isDirectory())
119
- continue;
120
- const firstLine = await (0, read_file_1.default)(file, true).catch(() => null);
151
+ let firstLine;
152
+ if (useRawContent) {
153
+ 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;
154
+ }
155
+ else {
156
+ if (!fs_1.default.existsSync(file) || fs_1.default.lstatSync(file).isDirectory())
157
+ continue;
158
+ firstLine = await (0, read_file_1.default)(file, true).catch(() => null);
159
+ }
121
160
  // Skip if file is unreadable
122
161
  if (firstLine === null)
123
162
  continue;
124
- // Check shebang line for explicit classification
125
- if (!opts.quick && opts.checkShebang && firstLine.startsWith('#!')) {
126
- // Find matching interpreters
127
- const matches = Object.entries(langData).filter(([, data]) => { var _a; return (_a = data.interpreters) === null || _a === void 0 ? void 0 : _a.some(interpreter => firstLine.match('\\b' + interpreter + '\\b')); });
163
+ // Check first line for explicit classification
164
+ const hasShebang = opts.checkShebang && /^#!/.test(firstLine);
165
+ const hasModeline = opts.checkModeline && /-\*-|(syntax|filetype|ft)\s*=/.test(firstLine);
166
+ if (!opts.quick && (hasShebang || hasModeline)) {
167
+ const matches = [];
168
+ for (const [lang, data] of Object.entries(langData)) {
169
+ const langMatcher = (lang) => `\\b${lang.toLowerCase().replace(/\W/g, '\\$&')}\\b`;
170
+ // Check for interpreter match
171
+ const matchesInterpretor = (_f = data.interpreters) === null || _f === void 0 ? void 0 : _f.some(interpreter => firstLine.match(`\\b${interpreter}\\b`));
172
+ // Check modeline declaration
173
+ const matchesLang = firstLine.toLowerCase().match(langMatcher(lang));
174
+ const matchesAlias = (_g = data.aliases) === null || _g === void 0 ? void 0 : _g.some(lang => firstLine.toLowerCase().match(langMatcher(lang)));
175
+ // Add language
176
+ if (opts.checkShebang && matchesInterpretor)
177
+ matches.push(lang);
178
+ if (opts.checkModeline && (matchesLang || matchesAlias))
179
+ matches.push(lang);
180
+ }
128
181
  if (matches.length) {
129
182
  // Add explicitly-identified language
130
- const forcedLang = matches[0][0];
183
+ const forcedLang = matches[0];
131
184
  addResult(file, forcedLang);
185
+ definiteness[file] = true;
186
+ continue;
132
187
  }
133
188
  }
134
189
  // Check override for manual language classification
135
- if (!opts.quick && opts.checkAttributes) {
190
+ if (!useRawContent && !opts.quick && opts.checkAttributes) {
136
191
  const match = overridesArray.find(item => RegExp(item[0]).test(file));
137
192
  if (match) {
138
193
  const forcedLang = match[1];
139
194
  addResult(file, forcedLang);
195
+ definiteness[file] = true;
140
196
  continue;
141
197
  }
142
198
  }
@@ -144,7 +200,7 @@ async function analyse(input, opts = {}) {
144
200
  let skipExts = false;
145
201
  for (const lang in langData) {
146
202
  // Check if filename is a match
147
- const matchesName = (_d = langData[lang].filenames) === null || _d === void 0 ? void 0 : _d.some(name => path_1.default.basename(file.toLowerCase()) === name.toLowerCase());
203
+ const matchesName = (_h = langData[lang].filenames) === null || _h === void 0 ? void 0 : _h.some(name => path_1.default.basename(file.toLowerCase()) === name.toLowerCase());
148
204
  if (matchesName) {
149
205
  addResult(file, lang);
150
206
  skipExts = true;
@@ -153,7 +209,7 @@ async function analyse(input, opts = {}) {
153
209
  if (!skipExts)
154
210
  for (const lang in langData) {
155
211
  // Check if extension is a match
156
- const matchesExt = (_e = langData[lang].extensions) === null || _e === void 0 ? void 0 : _e.some(ext => file.toLowerCase().endsWith(ext.toLowerCase()));
212
+ const matchesExt = (_j = langData[lang].extensions) === null || _j === void 0 ? void 0 : _j.some(ext => file.toLowerCase().endsWith(ext.toLowerCase()));
157
213
  if (matchesExt)
158
214
  addResult(file, lang);
159
215
  }
@@ -163,8 +219,13 @@ async function analyse(input, opts = {}) {
163
219
  }
164
220
  // Narrow down file associations to the best fit
165
221
  for (const file in fileAssociations) {
222
+ // Skip if file has explicit association
223
+ if (definiteness[file]) {
224
+ results.files.results[file] = fileAssociations[file][0];
225
+ continue;
226
+ }
166
227
  // Skip binary files
167
- if (!opts.keepBinary) {
228
+ if (!useRawContent && !opts.keepBinary) {
168
229
  const isCustomText = customText.some(path => RegExp(path).test(file));
169
230
  const isCustomBinary = customBinary.some(path => RegExp(path).test(file));
170
231
  const isBinaryExt = binary_extensions_1.default.some(ext => file.endsWith('.' + ext));
@@ -186,13 +247,14 @@ async function analyse(input, opts = {}) {
186
247
  heuristic.language = heuristic.language[0];
187
248
  }
188
249
  // Make sure the results includes this language
250
+ const languageGroup = (_k = langData[heuristic.language]) === null || _k === void 0 ? void 0 : _k.group;
189
251
  const matchesLang = fileAssociations[file].includes(heuristic.language);
190
- const matchesParent = langData[heuristic.language].group && fileAssociations[file].includes(langData[heuristic.language].group);
252
+ const matchesParent = languageGroup && fileAssociations[file].includes(languageGroup);
191
253
  if (!matchesLang && !matchesParent)
192
254
  continue;
193
255
  // Normalise heuristic data
194
256
  const patterns = [];
195
- const normalise = (contents) => patterns.push(...(Array.isArray(contents) ? contents : [contents]));
257
+ const normalise = (contents) => patterns.push(...[contents].flat());
196
258
  if (heuristic.pattern)
197
259
  normalise(heuristic.pattern);
198
260
  if (heuristic.named_pattern)
@@ -208,10 +270,10 @@ async function analyse(input, opts = {}) {
208
270
  }
209
271
  }
210
272
  // If no heuristics, assign a language
211
- (_f = (_k = results.files.results)[file]) !== null && _f !== void 0 ? _f : (_k[file] = fileAssociations[file][0]);
273
+ (_l = (_t = results.files.results)[file]) !== null && _l !== void 0 ? _l : (_t[file] = fileAssociations[file][0]);
212
274
  }
213
275
  // Skip specified categories
214
- if ((_g = opts.categories) === null || _g === void 0 ? void 0 : _g.length) {
276
+ if ((_m = opts.categories) === null || _m === void 0 ? void 0 : _m.length) {
215
277
  const categories = ['data', 'markup', 'programming', 'prose'];
216
278
  const hiddenCategories = categories.filter(cat => !opts.categories.includes(cat));
217
279
  for (const [file, lang] of Object.entries(results.files.results)) {
@@ -229,7 +291,7 @@ async function analyse(input, opts = {}) {
229
291
  }
230
292
  }
231
293
  // Convert paths to relative
232
- if (opts.relativePaths) {
294
+ if (!useRawContent && opts.relativePaths) {
233
295
  const newMap = {};
234
296
  for (const [file, lang] of Object.entries(results.files.results)) {
235
297
  let relPath = path_1.default.relative(process.cwd(), file).replace(/\\/g, '/');
@@ -243,21 +305,21 @@ async function analyse(input, opts = {}) {
243
305
  for (const [file, lang] of Object.entries(results.files.results)) {
244
306
  if (lang && !langData[lang])
245
307
  continue;
246
- const fileSize = fs_1.default.statSync(file).size;
308
+ const fileSize = (_q = (_p = (_o = opts.fileContent) === null || _o === void 0 ? void 0 : _o[files.indexOf(file)]) === null || _p === void 0 ? void 0 : _p.length) !== null && _q !== void 0 ? _q : fs_1.default.statSync(file).size;
247
309
  results.files.bytes += fileSize;
248
310
  // If no language found, add extension in other section
249
311
  if (!lang) {
250
312
  const ext = path_1.default.extname(file);
251
313
  const unknownType = ext === '' ? 'filenames' : 'extensions';
252
314
  const name = ext === '' ? path_1.default.basename(file) : ext;
253
- (_h = (_l = results.unknown[unknownType])[name]) !== null && _h !== void 0 ? _h : (_l[name] = 0);
315
+ (_r = (_u = results.unknown[unknownType])[name]) !== null && _r !== void 0 ? _r : (_u[name] = 0);
254
316
  results.unknown[unknownType][name] += fileSize;
255
317
  results.unknown.bytes += fileSize;
256
318
  continue;
257
319
  }
258
320
  // Add language and bytes data to corresponding section
259
321
  const { type } = langData[lang];
260
- (_j = (_m = results.languages.results)[lang]) !== null && _j !== void 0 ? _j : (_m[lang] = { type, bytes: 0, color: langData[lang].color });
322
+ (_s = (_v = results.languages.results)[lang]) !== null && _s !== void 0 ? _s : (_v[lang] = { type, bytes: 0, color: langData[lang].color });
261
323
  if (opts.childLanguages)
262
324
  results.languages.results[lang].parent = langData[lang].group;
263
325
  results.languages.results[lang].bytes += fileSize;
package/dist/types.d.ts CHANGED
@@ -5,6 +5,7 @@ export declare type FilePath = string;
5
5
  export declare type Bytes = Integer;
6
6
  export declare type Integer = number;
7
7
  export interface Options {
8
+ fileContent?: string | string[];
8
9
  ignoredFiles?: string[];
9
10
  ignoredLanguages?: Language[];
10
11
  categories?: Category[];
@@ -17,6 +18,7 @@ export interface Options {
17
18
  checkAttributes?: boolean;
18
19
  checkHeuristics?: boolean;
19
20
  checkShebang?: boolean;
21
+ checkModeline?: boolean;
20
22
  }
21
23
  export interface Results {
22
24
  files: {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "linguist-js",
3
- "version": "2.2.1",
3
+ "version": "2.4.0",
4
4
  "description": "Analyse languages used in a folder. Powered by GitHub Linguist, although it doesn't need to be installed.",
5
5
  "main": "dist/index.js",
6
6
  "bin": {
@@ -38,6 +38,7 @@
38
38
  "dependencies": {
39
39
  "binary-extensions": "^2.2.0",
40
40
  "commander": "^9.0.0",
41
+ "common-path-prefix": "^3.0.0",
41
42
  "cross-fetch": "^3.1.5",
42
43
  "glob-to-regexp": "~0.4.1",
43
44
  "isbinaryfile": "^4.0.8",
@@ -45,10 +46,10 @@
45
46
  "node-cache": "^5.1.2"
46
47
  },
47
48
  "devDependencies": {
48
- "@types/glob-to-regexp": "ts4.4",
49
- "@types/js-yaml": "ts4.4",
50
- "@types/node": "ts4.4",
49
+ "@types/glob-to-regexp": "ts4.6",
50
+ "@types/js-yaml": "ts4.6",
51
+ "@types/node": "ts4.6",
51
52
  "deep-object-diff": "^1.1.7",
52
- "typescript": "~4.5.5"
53
+ "typescript": "~4.6.1-rc"
53
54
  }
54
55
  }
package/readme.md CHANGED
@@ -77,6 +77,7 @@ Running Linguist on this folder will return the following JSON:
77
77
  ### Notes
78
78
 
79
79
  - File paths in the output use only forward slashes as delimiters, even on Windows.
80
+ - This tool does not work when offline.
80
81
  - Do not rely on any language classification output from Linguist being unchanged between runs.
81
82
  Language data is fetched each run from the latest classifications of [`github-linguist`](https://github.com/github/linguist).
82
83
  This data is subject to change at any time and may change the results of a run even when using the same version of Linguist.
@@ -99,6 +100,8 @@ const { files, languages, unknown } = linguist(folder, options);
99
100
  Analyse multiple folders using the syntax `"{folder1,folder2,...}"`.
100
101
  - `opts` (optional; object):
101
102
  An object containing analyser options.
103
+ - `fileContent` (string or string array):
104
+ Provides the file content associated with the file name(s) given as `entry` to analyse instead of reading from a folder on disk.
102
105
  - `ignoredFiles` (string array):
103
106
  A list of file path globs to explicitly ignore.
104
107
  - `ignoredLanguages` (string array):
@@ -110,21 +113,26 @@ const { files, languages, unknown } = linguist(folder, options);
110
113
  Whether to display sub-languages instead of their parents when possible (defaults to `false`).
111
114
  - `quick` (boolean):
112
115
  Whether to skip complex language analysis such as the checking of heuristics and gitattributes statements (defaults to `false`).
113
- Alias for `checkAttributes:false, checkIgnored:false, checkHeuristics:false, checkShebang:false`.
116
+ Alias for `checkAttributes:false, checkIgnored:false, checkHeuristics:false, checkShebang:false, checkModeline:false`.
114
117
  - `keepVendored` (boolean):
115
118
  Whether to keep vendored files (dependencies, etc) (defaults to `false`).
119
+ Does nothing when `fileContent` is set.
116
120
  - `keepBinary` (boolean):
117
121
  Whether binary files should be included in the output (defaults to `false`).
118
122
  - `relativePaths` (boolean):
119
123
  Change the absolute file paths in the output to be relative to the current working directory (defaults to `false`).
120
124
  - `checkAttributes` (boolean):
121
125
  Force the checking of `.gitattributes` files (defaults to `true` unless `quick` is set).
126
+ Does nothing when `fileContent` is set.
122
127
  - `checkIgnored` (boolean):
123
128
  Force the checking of `.gitignore` files (defaults to `true` unless `quick` is set).
129
+ Does nothing when `fileContent` is set.
124
130
  - `checkHeuristics` (boolean):
125
131
  Apply heuristics to ambiguous languages (defaults to `true` unless `quick` is set).
126
132
  - `checkShebang` (boolean):
127
133
  Check shebang (`#!`) lines for explicit language classification (defaults to `true` unless `quick` is set).
134
+ - `checkModeline` (boolean):
135
+ Check modelines for explicit language classification (defaults to `true` unless `quick` is set).
128
136
 
129
137
  ### Command-line
130
138
 
@@ -152,7 +160,7 @@ linguist --help
152
160
  Requires `--json` to be specified.
153
161
  - `--quick`:
154
162
  Whether to skip the checking of `.gitattributes` and `.gitignore` files for manual language classifications.
155
- Alias for `--checkAttributes=false --checkIgnored=false --checkHeuristics=false --checkShebang=false`.
163
+ Alias for `--checkAttributes=false --checkIgnored=false --checkHeuristics=false --checkExplicit=false --checkModeline=false`.
156
164
  - `--keepVendored`:
157
165
  Whether to include vendored files (auto-generated files, dependencies folder, etc).
158
166
  - `--keepBinary`:
@@ -167,6 +175,8 @@ linguist --help
167
175
  Apply heuristics to ambiguous languages (use alongside `--quick` to overwrite).
168
176
  - `--checkShebang`:
169
177
  Check shebang (`#!`) lines for explicit classification (use alongside `--quick` to overwrite).
178
+ - `--checkModeline`:
179
+ Check modelines for explicit classification (use alongside `--quick` to overwrite).
170
180
  - `--help`:
171
181
  Display a help message.
172
182
  - `--version`: