linguist-js 2.4.0 → 2.4.1

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.
@@ -8,9 +8,7 @@ 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(root, folders, ignored = []) {
12
- // Switch out paths that expect being in root
13
- ignored = ignored.map(match => match.replace(/^\^/, '^\\./'));
11
+ function walk(root, folders, gitignores, regexIgnores) {
14
12
  // Walk tree of a folder
15
13
  if (folders.length === 1) {
16
14
  const folder = folders[0];
@@ -27,7 +25,10 @@ function walk(root, folders, ignored = []) {
27
25
  // Create absolute path for disc operations
28
26
  const path = path_1.default.resolve(root, file).replace(/\\/g, '/');
29
27
  // Skip if nonexistant or ignored
30
- if (!fs_1.default.existsSync(path) || ignored.some(pattern => RegExp(pattern).test(file)))
28
+ const nonExistant = !fs_1.default.existsSync(path);
29
+ const isGitIgnored = gitignores.test(file.replace('./', '')).ignored;
30
+ const isRegexIgnored = regexIgnores.find(match => file.replace('./', '').match(match));
31
+ if (nonExistant || isGitIgnored || isRegexIgnored)
31
32
  continue;
32
33
  // Add absolute folder path to list
33
34
  allFolders.add(path_1.default.resolve(folder).replace(/\\/g, '/'));
@@ -35,7 +36,7 @@ function walk(root, folders, ignored = []) {
35
36
  if (file.endsWith('/')) {
36
37
  // Recurse into subfolders
37
38
  allFolders.add(path);
38
- walk(root, [path], ignored);
39
+ walk(root, [path], gitignores, regexIgnores);
39
40
  }
40
41
  else {
41
42
  // Add relative file path to list
@@ -46,7 +47,7 @@ function walk(root, folders, ignored = []) {
46
47
  // Recurse into all folders
47
48
  else {
48
49
  for (const path of folders) {
49
- walk(root, [path], ignored);
50
+ walk(root, [path], gitignores, regexIgnores);
50
51
  }
51
52
  }
52
53
  // Return absolute files and folders lists
package/dist/index.js CHANGED
@@ -5,7 +5,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
5
5
  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
- const glob_to_regexp_1 = __importDefault(require("glob-to-regexp"));
8
+ const ignore_1 = __importDefault(require("ignore"));
9
9
  const common_path_prefix_1 = __importDefault(require("common-path-prefix"));
10
10
  const binary_extensions_1 = __importDefault(require("binary-extensions"));
11
11
  const isbinaryfile_1 = require("isbinaryfile");
@@ -13,7 +13,6 @@ const walk_tree_1 = __importDefault(require("./helpers/walk-tree"));
13
13
  const load_data_1 = __importDefault(require("./helpers/load-data"));
14
14
  const read_file_1 = __importDefault(require("./helpers/read-file"));
15
15
  const convert_pcre_1 = __importDefault(require("./helpers/convert-pcre"));
16
- const convert_glob_1 = __importDefault(require("./helpers/convert-glob"));
17
16
  async function analyse(input, opts = {}) {
18
17
  var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m, _o, _p, _q, _r, _s;
19
18
  var _t, _u, _v;
@@ -25,7 +24,7 @@ async function analyse(input, opts = {}) {
25
24
  const vendorData = await (0, load_data_1.default)('vendor.yml').then(js_yaml_1.default.load);
26
25
  const docData = await (0, load_data_1.default)('documentation.yml').then(js_yaml_1.default.load);
27
26
  const heuristicsData = await (0, load_data_1.default)('heuristics.yml').then(js_yaml_1.default.load);
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 : []; });
27
+ 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 : []; });
29
28
  const vendorPaths = [...vendorData, ...docData, ...generatedData];
30
29
  // Setup main variables
31
30
  const fileAssociations = {};
@@ -38,13 +37,18 @@ async function analyse(input, opts = {}) {
38
37
  unknown: { count: 0, bytes: 0, extensions: {}, filenames: {} },
39
38
  };
40
39
  // 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
- }
40
+ const gitignores = (0, ignore_1.default)();
41
+ const regexIgnores = [];
42
+ gitignores.add('/.git');
43
+ if (!opts.keepVendored)
44
+ regexIgnores.push(...vendorPaths.map(path => RegExp(path, 'i')));
45
+ if (opts.ignoredFiles)
46
+ gitignores.add(opts.ignoredFiles);
47
+ // Set a common root path so that vendor paths do not incorrectly match parent folders
48
+ const resolvedInput = input.map(path => path_1.default.resolve(path).replace(/\\/g, '/'));
49
+ const commonRoot = (input.length > 1 ? (0, common_path_prefix_1.default)(resolvedInput) : resolvedInput[0]).replace(/\/?$/, '');
50
+ const relPath = (file) => path_1.default.relative(commonRoot, file).replace(/\\/g, '/');
51
+ const unRelPath = (file) => path_1.default.resolve(commonRoot, file).replace(/\\/g, '/');
48
52
  // Load file paths and folders
49
53
  let files, folders;
50
54
  if (useRawContent) {
@@ -54,10 +58,7 @@ async function analyse(input, opts = {}) {
54
58
  }
55
59
  else {
56
60
  // 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
+ const data = (0, walk_tree_1.default)(commonRoot, input, gitignores, regexIgnores);
61
62
  files = data.files;
62
63
  folders = data.folders;
63
64
  }
@@ -79,40 +80,36 @@ async function analyse(input, opts = {}) {
79
80
  }
80
81
  }
81
82
  }
82
- // Load gitattributes
83
- const customIgnored = [];
84
- const customBinary = [];
85
- const customText = [];
83
+ // Load gitignores and gitattributes
84
+ const customBinary = (0, ignore_1.default)();
85
+ const customText = (0, ignore_1.default)();
86
86
  if (!useRawContent && opts.checkAttributes) {
87
87
  for (const folder of folders) {
88
88
  // Skip if folder is marked in gitattributes
89
- if (customIgnored.some(path => (0, convert_glob_1.default)(path).test(folder)))
89
+ if (relPath(folder) && gitignores.test(relPath(folder)).ignored)
90
90
  continue;
91
91
  // Parse gitignores
92
92
  const ignoresFile = path_1.default.join(folder, '.gitignore');
93
93
  if (opts.checkIgnored && fs_1.default.existsSync(ignoresFile)) {
94
94
  const ignoresData = await (0, read_file_1.default)(ignoresFile);
95
- const ignoresList = ignoresData.split(/\r?\n/).filter(line => line.trim() && !line.startsWith('#'));
96
- const ignoredPaths = ignoresList.map(path => (0, convert_glob_1.default)(path).source);
97
- customIgnored.push(...ignoredPaths);
95
+ gitignores.add(ignoresData);
98
96
  }
99
97
  // Parse gitattributes
100
98
  const attributesFile = path_1.default.join(folder, '.gitattributes');
101
99
  if (opts.checkAttributes && fs_1.default.existsSync(attributesFile)) {
102
100
  const attributesData = await (0, read_file_1.default)(attributesFile);
103
- const relPathToRegex = (path) => (0, convert_glob_1.default)(path).source.substr(1).replace(folder, '');
104
101
  // Explicit text/binary associations
105
102
  const contentTypeMatches = attributesData.matchAll(/^(\S+).*?(-?binary|-?text)(?!=auto)/gm);
106
103
  for (const [_line, path, type] of contentTypeMatches) {
107
104
  if (['text', '-binary'].includes(type))
108
- customText.push(relPathToRegex(path));
105
+ customText.add(path);
109
106
  if (['-text', 'binary'].includes(type))
110
- customBinary.push(relPathToRegex(path));
107
+ customBinary.add(path);
111
108
  }
112
109
  // Custom vendor options
113
110
  const vendorMatches = attributesData.matchAll(/^(\S+).*[^-]linguist-(vendored|generated|documentation)(?!=false)/gm);
114
111
  for (const [_line, path] of vendorMatches) {
115
- customIgnored.push(relPathToRegex(path));
112
+ gitignores.add(path);
116
113
  }
117
114
  // Custom file associations
118
115
  const customLangMatches = attributesData.matchAll(/^(\S+).*[^-]linguist-language=(\S+)/gm);
@@ -123,17 +120,22 @@ async function analyse(input, opts = {}) {
123
120
  if (overrideLang)
124
121
  forcedLang = overrideLang[0];
125
122
  }
126
- const fullPath = folder + (0, convert_glob_1.default)(path).source.substr(1);
123
+ const fullPath = relPath(folder) + '/' + path;
127
124
  overrides[fullPath] = forcedLang;
128
125
  }
129
126
  }
130
127
  }
131
128
  }
132
129
  // Check vendored files
133
- if (!useRawContent && !opts.keepVendored) {
130
+ if (!opts.keepVendored) {
134
131
  // Filter out any files that match a vendor file path
135
- const matcher = (match) => RegExp(match.replace(/\/$/, '/.+$').replace(/^\.\//, ''));
136
- files = files.filter(file => !customIgnored.some(pattern => matcher(pattern).test(file)));
132
+ if (useRawContent) {
133
+ files = gitignores.filter(files);
134
+ files = files.filter(file => !regexIgnores.find(match => match.test(file)));
135
+ }
136
+ else {
137
+ files = gitignores.filter(files.map(relPath)).map(unRelPath);
138
+ }
137
139
  }
138
140
  // Load all files and parse languages
139
141
  const addResult = (file, result) => {
@@ -166,7 +168,7 @@ async function analyse(input, opts = {}) {
166
168
  if (!opts.quick && (hasShebang || hasModeline)) {
167
169
  const matches = [];
168
170
  for (const [lang, data] of Object.entries(langData)) {
169
- const langMatcher = (lang) => `\\b${lang.toLowerCase().replace(/\W/g, '\\$&')}\\b`;
171
+ const langMatcher = (lang) => `\\b${lang.toLowerCase().replace(/\W/g, '\\$&')}(?![\\w#+*]|-\*-)`;
170
172
  // Check for interpreter match
171
173
  const matchesInterpretor = (_f = data.interpreters) === null || _f === void 0 ? void 0 : _f.some(interpreter => firstLine.match(`\\b${interpreter}\\b`));
172
174
  // Check modeline declaration
@@ -188,7 +190,8 @@ async function analyse(input, opts = {}) {
188
190
  }
189
191
  // Check override for manual language classification
190
192
  if (!useRawContent && !opts.quick && opts.checkAttributes) {
191
- const match = overridesArray.find(item => RegExp(item[0]).test(file));
193
+ const isOverridden = (path) => (0, ignore_1.default)().add(path).test(relPath(file)).ignored;
194
+ const match = overridesArray.find(item => isOverridden(item[0]));
192
195
  if (match) {
193
196
  const forcedLang = match[1];
194
197
  addResult(file, forcedLang);
@@ -226,8 +229,8 @@ async function analyse(input, opts = {}) {
226
229
  }
227
230
  // Skip binary files
228
231
  if (!useRawContent && !opts.keepBinary) {
229
- const isCustomText = customText.some(path => RegExp(path).test(file));
230
- const isCustomBinary = customBinary.some(path => RegExp(path).test(file));
232
+ const isCustomText = customText.test(relPath(file)).ignored;
233
+ const isCustomBinary = customBinary.test(relPath(file)).ignored;
231
234
  const isBinaryExt = binary_extensions_1.default.some(ext => file.endsWith('.' + ext));
232
235
  if (!isCustomText && (isCustomBinary || isBinaryExt || await (0, isbinaryfile_1.isBinaryFile)(file))) {
233
236
  continue;
package/license.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # ISC License
2
2
 
3
- Copyright &copy; 2021 Nixinova
3
+ Copyright &copy; Nixinova 2021&ndash;2022
4
4
 
5
5
  Permission to use, copy, modify, and/or distribute this software for any
6
6
  purpose with or without fee is hereby granted, provided that the above
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "linguist-js",
3
- "version": "2.4.0",
3
+ "version": "2.4.1",
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": {
@@ -40,7 +40,7 @@
40
40
  "commander": "^9.0.0",
41
41
  "common-path-prefix": "^3.0.0",
42
42
  "cross-fetch": "^3.1.5",
43
- "glob-to-regexp": "~0.4.1",
43
+ "ignore": "^5.2.0",
44
44
  "isbinaryfile": "^4.0.8",
45
45
  "js-yaml": "^4.1.0",
46
46
  "node-cache": "^5.1.2"
package/readme.md CHANGED
@@ -97,7 +97,6 @@ const { files, languages, unknown } = linguist(folder, options);
97
97
  Analyse the language of all files found in a folder.
98
98
  - `entry` (optional; string or string array):
99
99
  The folder(s) to analyse (defaults to `./`).
100
- Analyse multiple folders using the syntax `"{folder1,folder2,...}"`.
101
100
  - `opts` (optional; object):
102
101
  An object containing analyser options.
103
102
  - `fileContent` (string or string array):
@@ -160,7 +159,7 @@ linguist --help
160
159
  Requires `--json` to be specified.
161
160
  - `--quick`:
162
161
  Whether to skip the checking of `.gitattributes` and `.gitignore` files for manual language classifications.
163
- Alias for `--checkAttributes=false --checkIgnored=false --checkHeuristics=false --checkExplicit=false --checkModeline=false`.
162
+ Alias for `--checkAttributes=false --checkIgnored=false --checkHeuristics=false --checkShebang=false --checkModeline=false`.
164
163
  - `--keepVendored`:
165
164
  Whether to include vendored files (auto-generated files, dependencies folder, etc).
166
165
  - `--keepBinary`:
@@ -1,11 +0,0 @@
1
- "use strict";
2
- var __importDefault = (this && this.__importDefault) || function (mod) {
3
- return (mod && mod.__esModule) ? mod : { "default": mod };
4
- };
5
- Object.defineProperty(exports, "__esModule", { value: true });
6
- const glob_to_regexp_1 = __importDefault(require("glob-to-regexp"));
7
- function parseGitGlob(path) {
8
- const globPath = `**/${path}`.replace(/\\/g, '/').replace(/\[:(space|digit):\]/g, (_, val) => `\\${val[0]}`);
9
- return (0, glob_to_regexp_1.default)(globPath, { globstar: true, extended: true });
10
- }
11
- exports.default = parseGitGlob;