linguist-js 2.3.2 → 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
@@ -26,6 +26,7 @@ commander_1.program
26
26
  .option('-I|--checkIgnored [bool]', 'Force the checking of gitignore files', true)
27
27
  .option('-H|--checkHeuristics [bool]', 'Apply heuristics to ambiguous languages', true)
28
28
  .option('-S|--checkShebang [bool]', 'Check shebang lines for explicit classification', true)
29
+ .option('-M|--checkModeline [bool]', 'Check modelines for explicit classification', true)
29
30
  .helpOption(`-h|--help`, 'Display this help message')
30
31
  .version(VERSION, '-v|--version', 'Display the installed version of linguist-js');
31
32
  commander_1.program.parse(process.argv);
package/dist/index.js CHANGED
@@ -15,8 +15,8 @@ const read_file_1 = __importDefault(require("./helpers/read-file"));
15
15
  const convert_pcre_1 = __importDefault(require("./helpers/convert-pcre"));
16
16
  const convert_glob_1 = __importDefault(require("./helpers/convert-glob"));
17
17
  async function analyse(input, opts = {}) {
18
- var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m, _o, _p, _q;
19
- var _r, _s, _t;
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
20
  const useRawContent = opts.fileContent !== undefined;
21
21
  input = [input !== null && input !== void 0 ? input : []].flat();
22
22
  opts.fileContent = [(_a = opts.fileContent) !== null && _a !== void 0 ? _a : []].flat();
@@ -29,6 +29,7 @@ async function analyse(input, opts = {}) {
29
29
  const vendorPaths = [...vendorData, ...docData, ...generatedData];
30
30
  // Setup main variables
31
31
  const fileAssociations = {};
32
+ const definiteness = {};
32
33
  const extensions = {};
33
34
  const overrides = {};
34
35
  const results = {
@@ -61,7 +62,14 @@ async function analyse(input, opts = {}) {
61
62
  folders = data.folders;
62
63
  }
63
64
  // Apply aliases
64
- 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
+ };
65
73
  // Ignore specific languages
66
74
  for (const lang of (_b = opts.ignoredLanguages) !== null && _b !== void 0 ? _b : []) {
67
75
  for (const key in langData) {
@@ -152,14 +160,30 @@ async function analyse(input, opts = {}) {
152
160
  // Skip if file is unreadable
153
161
  if (firstLine === null)
154
162
  continue;
155
- // Check shebang line for explicit classification
156
- if (!opts.quick && opts.checkShebang && firstLine.startsWith('#!')) {
157
- // Find matching interpreters
158
- 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
+ }
159
181
  if (matches.length) {
160
182
  // Add explicitly-identified language
161
- const forcedLang = matches[0][0];
183
+ const forcedLang = matches[0];
162
184
  addResult(file, forcedLang);
185
+ definiteness[file] = true;
186
+ continue;
163
187
  }
164
188
  }
165
189
  // Check override for manual language classification
@@ -168,6 +192,7 @@ async function analyse(input, opts = {}) {
168
192
  if (match) {
169
193
  const forcedLang = match[1];
170
194
  addResult(file, forcedLang);
195
+ definiteness[file] = true;
171
196
  continue;
172
197
  }
173
198
  }
@@ -175,7 +200,7 @@ async function analyse(input, opts = {}) {
175
200
  let skipExts = false;
176
201
  for (const lang in langData) {
177
202
  // Check if filename is a match
178
- const matchesName = (_f = langData[lang].filenames) === null || _f === void 0 ? void 0 : _f.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());
179
204
  if (matchesName) {
180
205
  addResult(file, lang);
181
206
  skipExts = true;
@@ -184,7 +209,7 @@ async function analyse(input, opts = {}) {
184
209
  if (!skipExts)
185
210
  for (const lang in langData) {
186
211
  // Check if extension is a match
187
- const matchesExt = (_g = langData[lang].extensions) === null || _g === void 0 ? void 0 : _g.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()));
188
213
  if (matchesExt)
189
214
  addResult(file, lang);
190
215
  }
@@ -194,6 +219,11 @@ async function analyse(input, opts = {}) {
194
219
  }
195
220
  // Narrow down file associations to the best fit
196
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
+ }
197
227
  // Skip binary files
198
228
  if (!useRawContent && !opts.keepBinary) {
199
229
  const isCustomText = customText.some(path => RegExp(path).test(file));
@@ -217,7 +247,7 @@ async function analyse(input, opts = {}) {
217
247
  heuristic.language = heuristic.language[0];
218
248
  }
219
249
  // Make sure the results includes this language
220
- const languageGroup = (_h = langData[heuristic.language]) === null || _h === void 0 ? void 0 : _h.group;
250
+ const languageGroup = (_k = langData[heuristic.language]) === null || _k === void 0 ? void 0 : _k.group;
221
251
  const matchesLang = fileAssociations[file].includes(heuristic.language);
222
252
  const matchesParent = languageGroup && fileAssociations[file].includes(languageGroup);
223
253
  if (!matchesLang && !matchesParent)
@@ -240,10 +270,10 @@ async function analyse(input, opts = {}) {
240
270
  }
241
271
  }
242
272
  // If no heuristics, assign a language
243
- (_j = (_r = results.files.results)[file]) !== null && _j !== void 0 ? _j : (_r[file] = fileAssociations[file][0]);
273
+ (_l = (_t = results.files.results)[file]) !== null && _l !== void 0 ? _l : (_t[file] = fileAssociations[file][0]);
244
274
  }
245
275
  // Skip specified categories
246
- if ((_k = opts.categories) === null || _k === void 0 ? void 0 : _k.length) {
276
+ if ((_m = opts.categories) === null || _m === void 0 ? void 0 : _m.length) {
247
277
  const categories = ['data', 'markup', 'programming', 'prose'];
248
278
  const hiddenCategories = categories.filter(cat => !opts.categories.includes(cat));
249
279
  for (const [file, lang] of Object.entries(results.files.results)) {
@@ -275,21 +305,21 @@ async function analyse(input, opts = {}) {
275
305
  for (const [file, lang] of Object.entries(results.files.results)) {
276
306
  if (lang && !langData[lang])
277
307
  continue;
278
- const fileSize = (_o = (_m = (_l = opts.fileContent) === null || _l === void 0 ? void 0 : _l[files.indexOf(file)]) === null || _m === void 0 ? void 0 : _m.length) !== null && _o !== void 0 ? _o : 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;
279
309
  results.files.bytes += fileSize;
280
310
  // If no language found, add extension in other section
281
311
  if (!lang) {
282
312
  const ext = path_1.default.extname(file);
283
313
  const unknownType = ext === '' ? 'filenames' : 'extensions';
284
314
  const name = ext === '' ? path_1.default.basename(file) : ext;
285
- (_p = (_s = results.unknown[unknownType])[name]) !== null && _p !== void 0 ? _p : (_s[name] = 0);
315
+ (_r = (_u = results.unknown[unknownType])[name]) !== null && _r !== void 0 ? _r : (_u[name] = 0);
286
316
  results.unknown[unknownType][name] += fileSize;
287
317
  results.unknown.bytes += fileSize;
288
318
  continue;
289
319
  }
290
320
  // Add language and bytes data to corresponding section
291
321
  const { type } = langData[lang];
292
- (_q = (_t = results.languages.results)[lang]) !== null && _q !== void 0 ? _q : (_t[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 });
293
323
  if (opts.childLanguages)
294
324
  results.languages.results[lang].parent = langData[lang].group;
295
325
  results.languages.results[lang].bytes += fileSize;
package/dist/types.d.ts CHANGED
@@ -18,6 +18,7 @@ export interface Options {
18
18
  checkAttributes?: boolean;
19
19
  checkHeuristics?: boolean;
20
20
  checkShebang?: boolean;
21
+ checkModeline?: boolean;
21
22
  }
22
23
  export interface Results {
23
24
  files: {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "linguist-js",
3
- "version": "2.3.2",
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": {
package/readme.md CHANGED
@@ -113,21 +113,26 @@ const { files, languages, unknown } = linguist(folder, options);
113
113
  Whether to display sub-languages instead of their parents when possible (defaults to `false`).
114
114
  - `quick` (boolean):
115
115
  Whether to skip complex language analysis such as the checking of heuristics and gitattributes statements (defaults to `false`).
116
- Alias for `checkAttributes:false, checkIgnored:false, checkHeuristics:false, checkShebang:false`.
116
+ Alias for `checkAttributes:false, checkIgnored:false, checkHeuristics:false, checkShebang:false, checkModeline:false`.
117
117
  - `keepVendored` (boolean):
118
118
  Whether to keep vendored files (dependencies, etc) (defaults to `false`).
119
+ Does nothing when `fileContent` is set.
119
120
  - `keepBinary` (boolean):
120
121
  Whether binary files should be included in the output (defaults to `false`).
121
122
  - `relativePaths` (boolean):
122
123
  Change the absolute file paths in the output to be relative to the current working directory (defaults to `false`).
123
124
  - `checkAttributes` (boolean):
124
125
  Force the checking of `.gitattributes` files (defaults to `true` unless `quick` is set).
126
+ Does nothing when `fileContent` is set.
125
127
  - `checkIgnored` (boolean):
126
128
  Force the checking of `.gitignore` files (defaults to `true` unless `quick` is set).
129
+ Does nothing when `fileContent` is set.
127
130
  - `checkHeuristics` (boolean):
128
131
  Apply heuristics to ambiguous languages (defaults to `true` unless `quick` is set).
129
132
  - `checkShebang` (boolean):
130
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).
131
136
 
132
137
  ### Command-line
133
138
 
@@ -155,7 +160,7 @@ linguist --help
155
160
  Requires `--json` to be specified.
156
161
  - `--quick`:
157
162
  Whether to skip the checking of `.gitattributes` and `.gitignore` files for manual language classifications.
158
- Alias for `--checkAttributes=false --checkIgnored=false --checkHeuristics=false --checkShebang=false`.
163
+ Alias for `--checkAttributes=false --checkIgnored=false --checkHeuristics=false --checkExplicit=false --checkModeline=false`.
159
164
  - `--keepVendored`:
160
165
  Whether to include vendored files (auto-generated files, dependencies folder, etc).
161
166
  - `--keepBinary`:
@@ -170,6 +175,8 @@ linguist --help
170
175
  Apply heuristics to ambiguous languages (use alongside `--quick` to overwrite).
171
176
  - `--checkShebang`:
172
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).
173
180
  - `--help`:
174
181
  Display a help message.
175
182
  - `--version`: