@darksheep/eslint 4.1.3

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 (40) hide show
  1. package/CHANGELOG.md +737 -0
  2. package/LICENSE +24 -0
  3. package/README.md +34 -0
  4. package/package.json +62 -0
  5. package/src/bin/eslint.cjs +11 -0
  6. package/src/configs/eslint-base.js +124 -0
  7. package/src/configs/eslint-complexity.js +28 -0
  8. package/src/configs/eslint-ignores.js +51 -0
  9. package/src/configs/eslint-recommended.js +9 -0
  10. package/src/configs/eslint-style.js +23 -0
  11. package/src/custom/index.js +11 -0
  12. package/src/custom/instance-of-array.js +72 -0
  13. package/src/custom/loose-types.js +204 -0
  14. package/src/custom/no-useless-expression.js +28 -0
  15. package/src/custom/sequence-expression.js +21 -0
  16. package/src/index.js +86 -0
  17. package/src/plugins/eslint-comments.js +27 -0
  18. package/src/plugins/import.js +139 -0
  19. package/src/plugins/jest.js +124 -0
  20. package/src/plugins/jsdoc.js +70 -0
  21. package/src/plugins/json.js +37 -0
  22. package/src/plugins/jsx-a11y.js +100 -0
  23. package/src/plugins/node.js +129 -0
  24. package/src/plugins/promise.js +16 -0
  25. package/src/plugins/react.js +128 -0
  26. package/src/plugins/regexp.js +13 -0
  27. package/src/plugins/sca.js +41 -0
  28. package/src/plugins/security.js +15 -0
  29. package/src/plugins/sonarjs.js +28 -0
  30. package/src/plugins/style.js +249 -0
  31. package/src/plugins/typescript.js +87 -0
  32. package/src/plugins/unicorn.js +58 -0
  33. package/src/plugins/unused-imports.js +52 -0
  34. package/src/types.d.ts +118 -0
  35. package/src/utilities/editorconfig.js +210 -0
  36. package/src/utilities/eslint-files.js +65 -0
  37. package/src/utilities/expand-glob.js +49 -0
  38. package/src/utilities/filesystem.js +73 -0
  39. package/src/utilities/make-compat.js +17 -0
  40. package/src/utilities/package.js +49 -0
@@ -0,0 +1,87 @@
1
+ import { getTypescriptFiles } from '../utilities/eslint-files.js';
2
+ import { getPackageJson } from '../utilities/package.js';
3
+
4
+ /**
5
+ * @template {Object} T
6
+ * @param {T | { default: T }} target The module to optionally check for default exports
7
+ * @returns {T}
8
+ */
9
+ function idef(target) {
10
+ return /** @type {{ default: T }} */ (target)?.default ?? target;
11
+ }
12
+
13
+ /**
14
+ * Get ESLint config for the typescript plugin
15
+ * @param {import('node:url').URL} root root url
16
+ * @returns {Promise<import('eslint').Linter.FlatConfig[]>}
17
+ */
18
+ export async function createEslintTypescriptConfig(root) {
19
+ const packageJson = await getPackageJson(root, 'typescript');
20
+
21
+ // Typescript not installed
22
+ if (packageJson == null) {
23
+ return [];
24
+ }
25
+
26
+ const typescriptParser = idef(await import('@typescript-eslint/parser'));
27
+ const typescriptPlugin = idef(await import('@typescript-eslint/eslint-plugin'));
28
+
29
+ return [ {
30
+ files: await getTypescriptFiles(),
31
+ languageOptions: {
32
+ parser: /** @type {*} */ (typescriptParser),
33
+ sourceType: 'module',
34
+ parserOptions: {
35
+ warnOnUnsupportedTypeScriptVersion: false,
36
+ },
37
+ },
38
+ plugins: {
39
+ '@typescript-eslint': /** @type {*} */ (typescriptPlugin),
40
+ },
41
+ rules: {
42
+ // Rules that fight with typescript
43
+ 'constructor-super': 'off',
44
+ 'getter-return': 'off',
45
+ 'no-const-assign': 'off',
46
+ 'no-dupe-args': 'off',
47
+ 'no-dupe-class-members': 'off',
48
+ 'no-dupe-keys': 'off',
49
+ 'no-func-assign': 'off',
50
+ 'no-import-assign': 'off',
51
+ 'no-new-symbol': 'off',
52
+ 'no-obj-calls': 'off',
53
+ 'no-redeclare': 'off',
54
+ 'no-setter-return': 'off',
55
+ 'no-this-before-super': 'off',
56
+ 'no-undef': 'off',
57
+ 'no-unreachable': 'off',
58
+ 'no-unsafe-negation': 'off',
59
+ 'no-array-constructor': 'off',
60
+ 'no-loss-of-precision': 'off',
61
+
62
+ // Rules that prevert some commmon ts issues
63
+ 'no-var': 'error',
64
+ 'prefer-const': 'error',
65
+ 'prefer-rest-params': 'error',
66
+ 'prefer-spread': 'error',
67
+
68
+ // ts eslint recommended
69
+ '@typescript-eslint/ban-ts-comment': 'error',
70
+ '@typescript-eslint/ban-types': 'error',
71
+ '@typescript-eslint/no-array-constructor': 'error',
72
+ '@typescript-eslint/no-duplicate-enum-values': 'error',
73
+ '@typescript-eslint/no-explicit-any': 'error',
74
+ '@typescript-eslint/no-extra-non-null-assertion': 'error',
75
+ '@typescript-eslint/no-loss-of-precision': 'error',
76
+ '@typescript-eslint/no-misused-new': 'error',
77
+ '@typescript-eslint/no-namespace': [ 'error', { allowDeclarations: true } ],
78
+ '@typescript-eslint/no-non-null-asserted-optional-chain': 'error',
79
+ '@typescript-eslint/no-this-alias': 'error',
80
+ '@typescript-eslint/no-unnecessary-type-constraint': 'error',
81
+ '@typescript-eslint/no-unsafe-declaration-merging': 'error',
82
+ '@typescript-eslint/no-var-requires': 'error',
83
+ '@typescript-eslint/prefer-as-const': 'error',
84
+ '@typescript-eslint/triple-slash-reference': 'error',
85
+ },
86
+ } ];
87
+ }
@@ -0,0 +1,58 @@
1
+ import { makeCompat } from '../utilities/make-compat.js';
2
+
3
+ /**
4
+ * Get ESLint config for the unicorn plugin
5
+ * @param {import('node:url').URL} root root url
6
+ * @returns {Promise<import('eslint').Linter.FlatConfig[]>}
7
+ */
8
+ export async function createEslintUnicornConfig(root) {
9
+ return makeCompat(root).config({
10
+ plugins: [ 'unicorn' ],
11
+ rules: {
12
+ /*
13
+ * Sanity checks
14
+ */
15
+ 'unicorn/better-regex': 'off',
16
+ 'unicorn/no-empty-file': 'warn',
17
+ 'unicorn/no-unsafe-regex': 'off',
18
+ 'unicorn/no-zero-fractions': 'error',
19
+ 'unicorn/no-unreadable-array-destructuring': 'error',
20
+ 'unicorn/no-useless-undefined': 'error',
21
+ 'unicorn/no-abusive-eslint-disable': 'warn',
22
+
23
+ /*
24
+ * Error management
25
+ */
26
+ 'unicorn/catch-error-name': 'error',
27
+ 'unicorn/custom-error-definition': 'off',
28
+ 'unicorn/throw-new-error': 'error',
29
+ 'unicorn/error-message': 'error',
30
+ 'unicorn/prefer-type-error': 'error',
31
+
32
+ // This breaks babel
33
+ 'unicorn/prefer-optional-catch-binding': 'off',
34
+
35
+ /*
36
+ * Best practice
37
+ */
38
+ 'unicorn/consistent-destructuring': 'off',
39
+ 'unicorn/escape-case': 'error',
40
+ 'unicorn/filename-case': 'off',
41
+ 'unicorn/no-console-spaces': 'error',
42
+ 'unicorn/no-keyword-prefix': 'warn',
43
+ 'unicorn/no-nested-ternary': 'warn',
44
+ 'unicorn/no-new-array': 'error',
45
+ 'unicorn/no-useless-promise-resolve-reject': 'error',
46
+ 'unicorn/no-useless-switch-case': 'error',
47
+ 'unicorn/prefer-add-event-listener': 'error',
48
+ 'unicorn/prefer-default-parameters': 'error',
49
+ 'unicorn/prefer-export-from': 'warn',
50
+ 'unicorn/prefer-native-coercion-functions': 'error',
51
+ 'unicorn/prefer-node-protocol': 'warn',
52
+ 'unicorn/prefer-reflect-apply': 'warn',
53
+ 'unicorn/prefer-regexp-test': 'warn',
54
+ 'unicorn/require-array-join-separator': 'error',
55
+ 'unicorn/text-encoding-identifier-case': 'error',
56
+ },
57
+ });
58
+ }
@@ -0,0 +1,52 @@
1
+ import unused from 'eslint-plugin-unused-imports';
2
+
3
+ import {
4
+ getModuleFiles,
5
+ getTypescriptFiles,
6
+ } from '../utilities/eslint-files.js';
7
+
8
+ /**
9
+ * Get ESLint config for the unused import plugin
10
+ * @param {import('node:url').URL} root root url
11
+ * @returns {Promise<import('eslint').Linter.FlatConfig[]>}
12
+ */
13
+ export async function createEslintUnusedImportsConfig(root) {
14
+ return [
15
+ {
16
+ files: await getModuleFiles(root),
17
+ plugins: { 'unused-imports': unused },
18
+ rules: {
19
+ 'no-unused-vars': 'off',
20
+ '@typescript-eslint/no-unused-vars': 'off',
21
+
22
+ 'unused-imports/no-unused-imports': 'error',
23
+ 'unused-imports/no-unused-vars': [
24
+ 'error', {
25
+ argsIgnorePattern: '^_',
26
+ varsIgnorePattern: '^_',
27
+ args: 'after-used',
28
+ vars: 'local',
29
+ },
30
+ ],
31
+ },
32
+ },
33
+ {
34
+ files: await getTypescriptFiles(),
35
+ plugins: { 'unused-imports': unused },
36
+ rules: {
37
+ 'no-unused-vars': 'off',
38
+ '@typescript-eslint/no-unused-vars': 'off',
39
+
40
+ 'unused-imports/no-unused-imports-ts': 'error',
41
+ 'unused-imports/no-unused-vars-ts': [
42
+ 'error', {
43
+ argsIgnorePattern: '^_',
44
+ varsIgnorePattern: '^_',
45
+ args: 'after-used',
46
+ vars: 'local',
47
+ },
48
+ ],
49
+ },
50
+ },
51
+ ];
52
+ }
package/src/types.d.ts ADDED
@@ -0,0 +1,118 @@
1
+ /* eslint no-duplicate-imports: 0 */
2
+
3
+ type WithRequired<T, K extends keyof T> = T & { [P in K]-?: T[P] };
4
+
5
+ declare module '@eslint-community/eslint-plugin-eslint-comments' {
6
+ import type { ESLint } from 'eslint';
7
+ export default {} as ESLint.Plugin;
8
+ }
9
+
10
+ declare module 'eslint-plugin-import' {
11
+ import type { ESLint, Linter } from 'eslint';
12
+ export default {} as {
13
+ rules: Required<ESLint.Plugin['rules']>;
14
+ configs: {
15
+ 'recommended': {
16
+ plugins: Required<ESLint.ConfigData['plugins']>;
17
+ rules: Linter.RulesRecord;
18
+ parserOptions: Required<ESLint.ConfigData['parserOptions']>;
19
+ };
20
+ 'errors': {
21
+ plugins: Required<ESLint.ConfigData['plugins']>;
22
+ rules: Linter.RulesRecord;
23
+ };
24
+ 'warnings': {
25
+ plugins: Required<ESLint.ConfigData['plugins']>;
26
+ rules: Linter.RulesRecord;
27
+ };
28
+ 'stage-0': {
29
+ plugins: Required<ESLint.ConfigData['plugins']>;
30
+ rules: Linter.RulesRecord;
31
+ };
32
+ 'react': {
33
+ settings: Required<ESLint.ConfigData['settings']>;
34
+ parserOptions: Required<ESLint.ConfigData['parserOptions']>;
35
+ };
36
+ 'react-native': {
37
+ settings: Required<ESLint.ConfigData['settings']>;
38
+ };
39
+ 'electron': {
40
+ settings: Required<ESLint.ConfigData['settings']>;
41
+ };
42
+ 'typescript': {
43
+ settings: Required<ESLint.ConfigData['settings']>;
44
+ rules: Linter.RulesRecord;
45
+ };
46
+ };
47
+ };
48
+ }
49
+
50
+ declare module 'eslint-plugin-jest' {
51
+ import type { ESLint } from 'eslint';
52
+ export default {} as WithRequired<ESLint.Plugin, 'environments'>;
53
+ }
54
+
55
+ declare module 'eslint-plugin-jsdoc' {
56
+ import type { ESLint } from 'eslint';
57
+ export default {} as WithRequired<ESLint.Plugin, 'environments'>;
58
+ }
59
+
60
+ declare module '@stylistic/eslint-plugin' {
61
+ import type { ESLint } from 'eslint';
62
+ export default {} as WithRequired<ESLint.Plugin, 'environments'>;
63
+ }
64
+
65
+ declare module 'eslint-plugin-jsx-a11y' {
66
+ import type { ESLint } from 'eslint';
67
+ export default {} as ESLint.Plugin;
68
+ }
69
+
70
+ declare module 'eslint-plugin-promise' {
71
+ import type { ESLint } from 'eslint';
72
+ export default {} as {
73
+ rules: Required<ESLint.Plugin['rules']>;
74
+ configs: {
75
+ recommended: WithRequired<ESLint.ConfigData, 'plugins' | 'rules'>;
76
+ };
77
+ };
78
+ }
79
+
80
+ declare module 'eslint-plugin-react' {
81
+ import type { ESLint } from 'eslint';
82
+ export default {} as ESLint.Plugin;
83
+ }
84
+
85
+ declare module 'eslint-plugin-security' {
86
+ import type { ESLint } from 'eslint';
87
+ export default {} as {
88
+ rules: Required<ESLint.Plugin['rules']>;
89
+ configs: {
90
+ recommended: WithRequired<ESLint.ConfigData, 'plugins' | 'rules'>;
91
+ };
92
+ };
93
+ }
94
+
95
+ declare module 'eslint-plugin-unused-imports' {
96
+ import type { ESLint } from 'eslint';
97
+ export default {} as ESLint.Plugin;
98
+ }
99
+
100
+ declare module 'eslint-plugin-n' {
101
+ import type { ESLint, Linter } from 'eslint';
102
+ export default {} as {
103
+ meta: {
104
+ name: string;
105
+ version: string;
106
+ };
107
+ rules: Required<ESLint.Plugin['rules']>;
108
+ configs: {
109
+ 'recommended-module': ESLint.ConfigData;
110
+ 'recommended-script': ESLint.ConfigData;
111
+ 'recommended': ESLint.ConfigData;
112
+ 'flat/recommended-module': Linter.FlatConfig;
113
+ 'flat/recommended-script': Linter.FlatConfig;
114
+ 'flat/recommended': Linter.FlatConfig;
115
+ 'flat/mixed-esm-and-cjs': Linter.FlatConfig;
116
+ };
117
+ };
118
+ }
@@ -0,0 +1,210 @@
1
+ import { readFile } from 'node:fs/promises';
2
+ import { resolve } from 'node:path';
3
+ import { fileURLToPath } from 'node:url';
4
+
5
+ import { parseBuffer } from 'editorconfig';
6
+ import { findUp } from './filesystem.js';
7
+ import { expandGlob } from './expand-glob.js';
8
+
9
+ /**
10
+ * @typedef {Object} RuleSet
11
+ * @property {string} EOL end of line
12
+ * @property {string} LF line feed
13
+ * @property {string} TRAIL trailing spaces
14
+ * @property {string} INDENT indent size
15
+ * @property {string} [INDENT_BINARY] indent binary ops size
16
+ */
17
+
18
+ /**
19
+ * @param {import('editorconfig').SectionBody} input Raw editor config converted into an object
20
+ * @param {RuleSet} RULES The rules used to create eslint rules options
21
+ * @returns {import('eslint').Linter.RulesRecord}
22
+ */
23
+ function convert(input, RULES) {
24
+ /** @type {import('eslint').Linter.RulesRecord} */
25
+ const output = {};
26
+
27
+ if (input?.end_of_line === 'lf') {
28
+ output[RULES.LF] = [ 'error', 'unix' ];
29
+ }
30
+
31
+ if (input?.end_of_line === 'crlf') {
32
+ output[RULES.LF] = [ 'error', 'windows' ];
33
+ }
34
+
35
+ if (input?.trim_trailing_whitespace === 'true') {
36
+ output[RULES.TRAIL] = 'error';
37
+ }
38
+
39
+ if (input?.trim_trailing_whitespace === 'false') {
40
+ output[RULES.TRAIL] = 'off';
41
+ }
42
+
43
+ if (input?.insert_final_newline === 'true') {
44
+ output[RULES.EOL] = [ 'error', 'always' ];
45
+ }
46
+
47
+ if (input?.insert_final_newline === 'false') {
48
+ output[RULES.EOL] = [ 'error', 'never' ];
49
+ }
50
+
51
+ if (input?.indent_style === 'space') {
52
+ const size = Number.parseInt(input?.indent_size ?? 4, 10);
53
+ output[RULES.INDENT] = [ 'error', size, { SwitchCase: 1 } ];
54
+ if (RULES.INDENT_BINARY != null) {
55
+ output[RULES.INDENT_BINARY] = [ 'error', size ];
56
+ }
57
+ }
58
+
59
+ if (input?.indent_style === 'tab') {
60
+ output[RULES.INDENT] = [ 'error', 'tab', { SwitchCase: 1 } ];
61
+ if (RULES.INDENT_BINARY != null) {
62
+ output[RULES.INDENT_BINARY] = [ 'error', 'tab' ];
63
+ }
64
+ }
65
+
66
+ return output;
67
+ }
68
+
69
+ /** @type {[string[], RuleSet][]} */
70
+ const extToRules = [
71
+ [
72
+ [ '.js', '.cjs', '.mjs', '.ts', '.cts', '.mts', '.jsx', '.tsx' ],
73
+ {
74
+ EOL: 'style/eol-last',
75
+ LF: 'style/linebreak-style',
76
+ TRAIL: 'style/no-trailing-spaces',
77
+ INDENT: 'style/indent',
78
+ INDENT_BINARY: 'style/indent-binary-ops',
79
+ },
80
+ ],
81
+ [
82
+ [ '.json', '.jsonc', '.json5' ], {
83
+ EOL: 'style/eol-last',
84
+ LF: 'style/linebreak-style',
85
+ TRAIL: 'style/no-trailing-spaces',
86
+ INDENT: 'jsonc/indent',
87
+ },
88
+ ],
89
+ ];
90
+
91
+ /**
92
+ * Do any of the provided files match a js file?
93
+ * @param {string[]} files The list of files to check
94
+ * @param {string[]} extensions The allowed file extensions
95
+ * @returns {string[]}
96
+ */
97
+ function filterMatchingFiles(files, extensions) {
98
+ return files.filter((file) => {
99
+ if (file.endsWith('*')) {
100
+ return true;
101
+ }
102
+
103
+ for (const extension of extensions) {
104
+ if (file.endsWith(extension)) {
105
+ return true;
106
+ }
107
+ }
108
+ return false;
109
+ });
110
+ }
111
+
112
+ /**
113
+ * Do any of the provided files match a js file?
114
+ * @param {string[] | null | undefined} one The list of files to check
115
+ * @param {string[] | null | undefined} two The allowed file extensions
116
+ * @returns {boolean}
117
+ */
118
+ function equal(one, two) {
119
+ if (one == null && two == null) {
120
+ return true;
121
+ }
122
+ if (one == null || two == null) {
123
+ return false;
124
+ }
125
+
126
+ if (
127
+ Array.isArray(one) === false ||
128
+ Array.isArray(two) === false ||
129
+ one.length !== two.length
130
+ ) {
131
+ return false;
132
+ }
133
+
134
+ for (const element of one) {
135
+ if (two.includes(element) === false) {
136
+ return false;
137
+ }
138
+ }
139
+
140
+ return true;
141
+ }
142
+
143
+ /**
144
+ * @param {import('eslint').Linter.FlatConfig[]} configs [description]
145
+ * @param {import('eslint').Linter.FlatConfig} config [description]
146
+ */
147
+ function addOrMergeConfig(configs, config) {
148
+ for (const previous of configs) {
149
+ if (equal(
150
+ /** @type {string[]} */ (previous.files),
151
+ /** @type {string[]} */ (config.files),
152
+ )) {
153
+ previous.rules = {
154
+ ...previous.rules,
155
+ ...config.rules,
156
+ };
157
+
158
+ return;
159
+ }
160
+ }
161
+ configs.push(config);
162
+ }
163
+
164
+ /**
165
+ * @param {import('node:url').URL} root root url
166
+ * @returns {Promise<import('eslint').Linter.FlatConfig[]>}
167
+ */
168
+ export async function createEditorOverrides(root) {
169
+ const eslintConfigPath = resolve(fileURLToPath(root), 'eslint.config.js');
170
+ const path = await findUp(eslintConfigPath, '.editorconfig');
171
+ if (path == null) {
172
+ return [];
173
+ }
174
+
175
+ const editorconfig = parseBuffer(await readFile(path));
176
+
177
+ /** @type {import('eslint').Linter.FlatConfig[]} */
178
+ const configs = [];
179
+ for (const [ name, body ] of editorconfig) {
180
+ if (name === null) {
181
+ continue;
182
+ }
183
+
184
+ const allFiles = expandGlob(name);
185
+ if (allFiles == null) {
186
+ continue;
187
+ }
188
+
189
+ for (const [ extensions, rules ] of extToRules) {
190
+ const files = filterMatchingFiles(allFiles, extensions);
191
+ if (files.length === 0) {
192
+ continue;
193
+ }
194
+
195
+ if (files.includes('*')) {
196
+ addOrMergeConfig(configs, {
197
+ files: extensions.map((extension) => `**/*${extension}`),
198
+ rules: convert(body, rules),
199
+ });
200
+ } else {
201
+ addOrMergeConfig(configs, {
202
+ files: files.map((file) => `**/${file}`),
203
+ rules: convert(body, rules),
204
+ });
205
+ }
206
+ }
207
+ }
208
+
209
+ return configs;
210
+ }
@@ -0,0 +1,65 @@
1
+ import { getPackageJson } from './package.js';
2
+ import { expandGlob } from './expand-glob.js';
3
+
4
+ /**
5
+ * @param {string[]} files [description]
6
+ * @param {string} type [description]
7
+ * @param {string} target [description]
8
+ * @returns {string[]}
9
+ */
10
+ function getFiles(files, type, target) {
11
+ if (type === target) {
12
+ return [ '**/*.js', '**/*.jsx', ...files ];
13
+ }
14
+
15
+ return files;
16
+ }
17
+
18
+ /**
19
+ * Get a list of globs for common js files
20
+ * @returns {Promise<string[]>}
21
+ */
22
+ export async function getJsonFiles() {
23
+ return [ '**/*.json', '**/*.jsonc', '**/*.json5' ];
24
+ }
25
+
26
+ /**
27
+ * Get a list of globs for common js files
28
+ * @param {import('node:url').URL} root root url
29
+ * @returns {Promise<string[]>}
30
+ */
31
+ export async function getCommonFiles(root) {
32
+ const { type = 'commonjs' } = await getPackageJson(root) ?? {};
33
+
34
+ return getFiles([ '**/*.cjs' ], type, 'commonjs');
35
+ }
36
+
37
+ /**
38
+ * Get a list of globs for module files
39
+ * @param {import('node:url').URL} root root url
40
+ * @returns {Promise<string[]>}
41
+ */
42
+ export async function getModuleFiles(root) {
43
+ const { type = 'commonjs' } = await getPackageJson(root) ?? {};
44
+ return getFiles([ '**/*.mjs' ], type, 'module');
45
+ }
46
+
47
+ /**
48
+ * Get a list of globs for typescript files
49
+ * @returns {Promise<string[]>}
50
+ */
51
+ export async function getTypescriptFiles() {
52
+ return [ '**/*.ts', '**/*.tsx' ];
53
+ }
54
+
55
+ /**
56
+ * Get a list of possible example files
57
+ * @returns {Promise<string[]>}
58
+ */
59
+ export async function getExampleFiles() {
60
+ return [
61
+ ...expandGlob('**/example.{ts,js,tsx,jsx,mts,cts,mjs,cjs}'),
62
+ ...expandGlob('**/{example,examples}/*'),
63
+ ...expandGlob('**/{example,examples}/**/*'),
64
+ ];
65
+ }
@@ -0,0 +1,49 @@
1
+ const globNumRegex = /\{\d+\.\.\d+\}/;
2
+ const globEachRegex = /\{[\w.-]+(?:,[\w.-]+)+\}/;
3
+
4
+ /**
5
+ * @param {string} input The glob to process
6
+ * @returns {string[]}
7
+ */
8
+ export function expandGlob(input) {
9
+ const globNumMatch = input.match(globNumRegex);
10
+ if (typeof globNumMatch?.index === 'number') {
11
+ const first = input.slice(0, globNumMatch.index);
12
+ const each = globNumMatch[0];
13
+ const last = input.slice(globNumMatch.index + each.length);
14
+
15
+ const [ a, b ] = each
16
+ .slice(1, -1)
17
+ .split('..')
18
+ .map((number) => Number.parseInt(number, 10));
19
+
20
+ const start = a > b ? b : a;
21
+ const end = a < b ? b : a;
22
+
23
+ const output = [];
24
+ for (let i = start; i <= end; i++) {
25
+ output.push(`${first}${i}${last}`);
26
+ }
27
+
28
+ return output.map(expandGlob).flat();
29
+ }
30
+
31
+ const globEachMatch = input.match(globEachRegex);
32
+ if (typeof globEachMatch?.index === 'number') {
33
+ const first = input.slice(0, globEachMatch.index);
34
+ const each = globEachMatch[0];
35
+ const last = input.slice(globEachMatch.index + each.length);
36
+
37
+ const output = each
38
+ .slice(1, -1).split(',')
39
+ .map((part) => `${first}${part}${last}`);
40
+
41
+ if (output.length > 1) {
42
+ return output.map(expandGlob).flat();
43
+ }
44
+
45
+ return output;
46
+ }
47
+
48
+ return [ input ];
49
+ }