eslint-config-decent 1.1.0 → 1.2.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/README.md +29 -0
- package/dist/index.cjs +124 -9
- package/dist/index.d.cts +6 -2
- package/dist/index.d.mts +6 -2
- package/dist/index.d.ts +6 -2
- package/dist/index.mjs +124 -9
- package/package.json +1 -1
- package/src/extension.ts +30 -0
- package/src/index.ts +11 -2
- package/src/rules/requireExtensionRule.ts +54 -0
- package/src/rules/requireIndexRule.ts +51 -0
package/README.md
CHANGED
|
@@ -15,6 +15,35 @@ import tsEslint from 'typescript-eslint';
|
|
|
15
15
|
export default tsEslint.config(...defaultConfig());
|
|
16
16
|
```
|
|
17
17
|
|
|
18
|
+
## Override parserOptions
|
|
19
|
+
|
|
20
|
+
````mjs
|
|
21
|
+
// eslint.config.mjs
|
|
22
|
+
|
|
23
|
+
import { defaultConfig } from 'eslint-config-decent';
|
|
24
|
+
import tsEslint from 'typescript-eslint';
|
|
25
|
+
|
|
26
|
+
export default tsEslint.config(...defaultConfig({
|
|
27
|
+
projectService: {
|
|
28
|
+
allowedDefaultProject: ['./*.js', './*.cjs', './*.mjs', './tests/**/*.ts', './tests/**/*.js', './tests/**/*.cjs', './tests/**/*.mjs'],
|
|
29
|
+
defaultProject: 'tsconfig.json',
|
|
30
|
+
},
|
|
31
|
+
tsconfigRootDir: import.meta.dirname,
|
|
32
|
+
}));
|
|
33
|
+
|
|
34
|
+
## Disable require-extensions rules
|
|
35
|
+
|
|
36
|
+
```mjs
|
|
37
|
+
// eslint.config.mjs
|
|
38
|
+
|
|
39
|
+
import { defaultConfig } from 'eslint-config-decent';
|
|
40
|
+
import tsEslint from 'typescript-eslint';
|
|
41
|
+
|
|
42
|
+
export default tsEslint.config(...defaultConfig({
|
|
43
|
+
enableRequireExtensions: false,
|
|
44
|
+
}));
|
|
45
|
+
````
|
|
46
|
+
|
|
18
47
|
## License
|
|
19
48
|
|
|
20
49
|
MIT
|
package/dist/index.cjs
CHANGED
|
@@ -4,6 +4,8 @@ const eslint = require('@eslint/js');
|
|
|
4
4
|
const globals = require('globals');
|
|
5
5
|
const tsEslint = require('typescript-eslint');
|
|
6
6
|
const prettier = require('eslint-plugin-prettier/recommended');
|
|
7
|
+
const fs = require('fs');
|
|
8
|
+
const path = require('path');
|
|
7
9
|
const jsdoc = require('eslint-plugin-jsdoc');
|
|
8
10
|
const mocha = require('eslint-plugin-mocha');
|
|
9
11
|
const promise = require('eslint-plugin-promise');
|
|
@@ -30,7 +32,7 @@ const testingLibrary__default = /*#__PURE__*/_interopDefaultCompat(testingLibrar
|
|
|
30
32
|
const security__default = /*#__PURE__*/_interopDefaultCompat(security);
|
|
31
33
|
const unicorn__default = /*#__PURE__*/_interopDefaultCompat(unicorn);
|
|
32
34
|
|
|
33
|
-
const base$
|
|
35
|
+
const base$8 = {
|
|
34
36
|
rules: {
|
|
35
37
|
"array-callback-return": ["error", { allowImplicit: true }],
|
|
36
38
|
"block-scoped-var": "error",
|
|
@@ -277,12 +279,122 @@ const cjs = {
|
|
|
277
279
|
strict: ["error", "global"]
|
|
278
280
|
}
|
|
279
281
|
};
|
|
280
|
-
const configs$
|
|
281
|
-
base: base$
|
|
282
|
+
const configs$8 = {
|
|
283
|
+
base: base$8,
|
|
282
284
|
cjsAndEsm,
|
|
283
285
|
cjs
|
|
284
286
|
};
|
|
285
287
|
|
|
288
|
+
const requireExtensionRule = {
|
|
289
|
+
meta: {
|
|
290
|
+
type: "suggestion",
|
|
291
|
+
docs: {
|
|
292
|
+
description: "Ensure import and export statements include a file extension",
|
|
293
|
+
category: "Best Practices",
|
|
294
|
+
recommended: true
|
|
295
|
+
},
|
|
296
|
+
fixable: "code",
|
|
297
|
+
schema: []
|
|
298
|
+
},
|
|
299
|
+
create(context) {
|
|
300
|
+
function checkSource(source) {
|
|
301
|
+
const importPath = source.value;
|
|
302
|
+
if (!importPath || !importPath.startsWith(".") || importPath.endsWith(".js")) {
|
|
303
|
+
return;
|
|
304
|
+
}
|
|
305
|
+
const resolvedPath = path.resolve(path.dirname(context.filename), importPath);
|
|
306
|
+
if (!fs.existsSync(resolvedPath)) {
|
|
307
|
+
context.report({
|
|
308
|
+
node: source,
|
|
309
|
+
message: "Relative imports and exports must include a file extension.",
|
|
310
|
+
fix(fixer) {
|
|
311
|
+
const fixedPath = `${importPath}.js`;
|
|
312
|
+
return fixer.replaceText(source, `'${fixedPath}'`);
|
|
313
|
+
}
|
|
314
|
+
});
|
|
315
|
+
}
|
|
316
|
+
}
|
|
317
|
+
return {
|
|
318
|
+
ImportDeclaration(node) {
|
|
319
|
+
checkSource(node.source);
|
|
320
|
+
},
|
|
321
|
+
ExportNamedDeclaration(node) {
|
|
322
|
+
if (node.source) {
|
|
323
|
+
checkSource(node.source);
|
|
324
|
+
}
|
|
325
|
+
},
|
|
326
|
+
ExportAllDeclaration(node) {
|
|
327
|
+
checkSource(node.source);
|
|
328
|
+
}
|
|
329
|
+
};
|
|
330
|
+
}
|
|
331
|
+
};
|
|
332
|
+
|
|
333
|
+
const requireIndexRule = {
|
|
334
|
+
meta: {
|
|
335
|
+
type: "suggestion",
|
|
336
|
+
docs: {
|
|
337
|
+
description: "Ensure directory import and export statements use index.js",
|
|
338
|
+
category: "Best Practices",
|
|
339
|
+
recommended: true
|
|
340
|
+
},
|
|
341
|
+
fixable: "code",
|
|
342
|
+
schema: []
|
|
343
|
+
},
|
|
344
|
+
create(context) {
|
|
345
|
+
function checkSource(source) {
|
|
346
|
+
const importPath = source.value;
|
|
347
|
+
const resolvedPath = path.resolve(path.dirname(context.filename), importPath);
|
|
348
|
+
const isDirectory = fs.existsSync(resolvedPath) && fs.lstatSync(resolvedPath).isDirectory();
|
|
349
|
+
if (isDirectory) {
|
|
350
|
+
context.report({
|
|
351
|
+
node: source,
|
|
352
|
+
message: "Directory imports and exports must use index.js.",
|
|
353
|
+
fix(fixer) {
|
|
354
|
+
const fixedPath = importPath.replace(/\/?$/, "/index.js");
|
|
355
|
+
return fixer.replaceText(source, `'${fixedPath}'`);
|
|
356
|
+
}
|
|
357
|
+
});
|
|
358
|
+
}
|
|
359
|
+
}
|
|
360
|
+
return {
|
|
361
|
+
ImportDeclaration(node) {
|
|
362
|
+
checkSource(node.source);
|
|
363
|
+
},
|
|
364
|
+
ExportNamedDeclaration(node) {
|
|
365
|
+
if (node.source) {
|
|
366
|
+
checkSource(node.source);
|
|
367
|
+
}
|
|
368
|
+
},
|
|
369
|
+
ExportAllDeclaration(node) {
|
|
370
|
+
checkSource(node.source);
|
|
371
|
+
}
|
|
372
|
+
};
|
|
373
|
+
}
|
|
374
|
+
};
|
|
375
|
+
|
|
376
|
+
const base$7 = {
|
|
377
|
+
plugins: {
|
|
378
|
+
"decent-extension": {
|
|
379
|
+
meta: {
|
|
380
|
+
name: "decent-extension",
|
|
381
|
+
version: "1.0.0"
|
|
382
|
+
},
|
|
383
|
+
rules: {
|
|
384
|
+
"require-extension": requireExtensionRule,
|
|
385
|
+
"require-index": requireIndexRule
|
|
386
|
+
}
|
|
387
|
+
}
|
|
388
|
+
},
|
|
389
|
+
rules: {
|
|
390
|
+
"decent-extension/require-extension": "error",
|
|
391
|
+
"decent-extension/require-index": "error"
|
|
392
|
+
}
|
|
393
|
+
};
|
|
394
|
+
const configs$7 = {
|
|
395
|
+
base: base$7
|
|
396
|
+
};
|
|
397
|
+
|
|
286
398
|
const base$6 = {
|
|
287
399
|
settings: {
|
|
288
400
|
jsdoc: {
|
|
@@ -533,7 +645,8 @@ const configs = {
|
|
|
533
645
|
base
|
|
534
646
|
};
|
|
535
647
|
|
|
536
|
-
function defaultConfig(
|
|
648
|
+
function defaultConfig(options) {
|
|
649
|
+
const enableRequireExtensionRule = options?.enableRequireExtensionRule ?? true;
|
|
537
650
|
const languageOptions = {
|
|
538
651
|
globals: {
|
|
539
652
|
...globals__default.node
|
|
@@ -545,7 +658,7 @@ function defaultConfig(parserOptions) {
|
|
|
545
658
|
defaultProject: "tsconfig.json"
|
|
546
659
|
},
|
|
547
660
|
tsconfigRootDir: undefined,
|
|
548
|
-
...parserOptions
|
|
661
|
+
...options?.parserOptions
|
|
549
662
|
}
|
|
550
663
|
};
|
|
551
664
|
return [
|
|
@@ -564,13 +677,15 @@ function defaultConfig(parserOptions) {
|
|
|
564
677
|
{
|
|
565
678
|
files: ["**/*.ts", "**/*.js", "**/*.cjs", "**/*.mjs", "**/*.tsx"],
|
|
566
679
|
plugins: {
|
|
680
|
+
...configs$7.base.plugins,
|
|
567
681
|
...configs$6.base.plugins,
|
|
568
682
|
...configs$4.base.plugins,
|
|
569
683
|
...configs$2.base.plugins,
|
|
570
684
|
...configs.base.plugins
|
|
571
685
|
},
|
|
572
686
|
rules: {
|
|
573
|
-
...configs$
|
|
687
|
+
...configs$8.base.rules,
|
|
688
|
+
...enableRequireExtensionRule ? configs$7.base.rules : {},
|
|
574
689
|
...configs$6.base.rules,
|
|
575
690
|
...configs$4.base.rules,
|
|
576
691
|
...configs$2.base.rules,
|
|
@@ -582,14 +697,14 @@ function defaultConfig(parserOptions) {
|
|
|
582
697
|
languageOptions: {
|
|
583
698
|
sourceType: "script"
|
|
584
699
|
},
|
|
585
|
-
...configs$
|
|
700
|
+
...configs$8.cjsAndEsm
|
|
586
701
|
},
|
|
587
702
|
{
|
|
588
703
|
files: ["**/*.js", "**/*.cjs"],
|
|
589
704
|
languageOptions: {
|
|
590
705
|
sourceType: "script"
|
|
591
706
|
},
|
|
592
|
-
...configs$
|
|
707
|
+
...configs$8.cjs
|
|
593
708
|
},
|
|
594
709
|
{
|
|
595
710
|
files: ["**/*.ts", "**/*.tsx"],
|
|
@@ -608,7 +723,7 @@ function defaultConfig(parserOptions) {
|
|
|
608
723
|
}
|
|
609
724
|
|
|
610
725
|
exports.defaultConfig = defaultConfig;
|
|
611
|
-
exports.eslintConfigs = configs$
|
|
726
|
+
exports.eslintConfigs = configs$8;
|
|
612
727
|
exports.jsdocConfigs = configs$6;
|
|
613
728
|
exports.promiseConfigs = configs$4;
|
|
614
729
|
exports.reactConfigs = configs$3;
|
package/dist/index.d.cts
CHANGED
|
@@ -30,6 +30,10 @@ declare const configs: {
|
|
|
30
30
|
base: ConfigWithExtends;
|
|
31
31
|
};
|
|
32
32
|
|
|
33
|
-
|
|
33
|
+
interface DefaultConfigOptions {
|
|
34
|
+
parserOptions?: NonNullable<ConfigWithExtends['languageOptions']>['parserOptions'];
|
|
35
|
+
enableRequireExtensionRule?: boolean;
|
|
36
|
+
}
|
|
37
|
+
declare function defaultConfig(options?: DefaultConfigOptions): ConfigWithExtends[];
|
|
34
38
|
|
|
35
|
-
export { defaultConfig, configs$6 as eslintConfigs, configs$5 as jsdocConfigs, configs$4 as promiseConfigs, configs$3 as reactConfigs, configs$2 as securityConfigs, configs$1 as typescriptEslintConfigs, configs as unicornConfigs };
|
|
39
|
+
export { type DefaultConfigOptions, defaultConfig, configs$6 as eslintConfigs, configs$5 as jsdocConfigs, configs$4 as promiseConfigs, configs$3 as reactConfigs, configs$2 as securityConfigs, configs$1 as typescriptEslintConfigs, configs as unicornConfigs };
|
package/dist/index.d.mts
CHANGED
|
@@ -30,6 +30,10 @@ declare const configs: {
|
|
|
30
30
|
base: ConfigWithExtends;
|
|
31
31
|
};
|
|
32
32
|
|
|
33
|
-
|
|
33
|
+
interface DefaultConfigOptions {
|
|
34
|
+
parserOptions?: NonNullable<ConfigWithExtends['languageOptions']>['parserOptions'];
|
|
35
|
+
enableRequireExtensionRule?: boolean;
|
|
36
|
+
}
|
|
37
|
+
declare function defaultConfig(options?: DefaultConfigOptions): ConfigWithExtends[];
|
|
34
38
|
|
|
35
|
-
export { defaultConfig, configs$6 as eslintConfigs, configs$5 as jsdocConfigs, configs$4 as promiseConfigs, configs$3 as reactConfigs, configs$2 as securityConfigs, configs$1 as typescriptEslintConfigs, configs as unicornConfigs };
|
|
39
|
+
export { type DefaultConfigOptions, defaultConfig, configs$6 as eslintConfigs, configs$5 as jsdocConfigs, configs$4 as promiseConfigs, configs$3 as reactConfigs, configs$2 as securityConfigs, configs$1 as typescriptEslintConfigs, configs as unicornConfigs };
|
package/dist/index.d.ts
CHANGED
|
@@ -30,6 +30,10 @@ declare const configs: {
|
|
|
30
30
|
base: ConfigWithExtends;
|
|
31
31
|
};
|
|
32
32
|
|
|
33
|
-
|
|
33
|
+
interface DefaultConfigOptions {
|
|
34
|
+
parserOptions?: NonNullable<ConfigWithExtends['languageOptions']>['parserOptions'];
|
|
35
|
+
enableRequireExtensionRule?: boolean;
|
|
36
|
+
}
|
|
37
|
+
declare function defaultConfig(options?: DefaultConfigOptions): ConfigWithExtends[];
|
|
34
38
|
|
|
35
|
-
export { defaultConfig, configs$6 as eslintConfigs, configs$5 as jsdocConfigs, configs$4 as promiseConfigs, configs$3 as reactConfigs, configs$2 as securityConfigs, configs$1 as typescriptEslintConfigs, configs as unicornConfigs };
|
|
39
|
+
export { type DefaultConfigOptions, defaultConfig, configs$6 as eslintConfigs, configs$5 as jsdocConfigs, configs$4 as promiseConfigs, configs$3 as reactConfigs, configs$2 as securityConfigs, configs$1 as typescriptEslintConfigs, configs as unicornConfigs };
|
package/dist/index.mjs
CHANGED
|
@@ -2,6 +2,8 @@ import eslint from '@eslint/js';
|
|
|
2
2
|
import globals from 'globals';
|
|
3
3
|
import tsEslint from 'typescript-eslint';
|
|
4
4
|
import prettier from 'eslint-plugin-prettier/recommended';
|
|
5
|
+
import { existsSync, lstatSync } from 'fs';
|
|
6
|
+
import { resolve, dirname } from 'path';
|
|
5
7
|
import jsdoc from 'eslint-plugin-jsdoc';
|
|
6
8
|
import mocha from 'eslint-plugin-mocha';
|
|
7
9
|
import promise from 'eslint-plugin-promise';
|
|
@@ -12,7 +14,7 @@ import testingLibrary from 'eslint-plugin-testing-library';
|
|
|
12
14
|
import security from 'eslint-plugin-security';
|
|
13
15
|
import unicorn from 'eslint-plugin-unicorn';
|
|
14
16
|
|
|
15
|
-
const base$
|
|
17
|
+
const base$8 = {
|
|
16
18
|
rules: {
|
|
17
19
|
"array-callback-return": ["error", { allowImplicit: true }],
|
|
18
20
|
"block-scoped-var": "error",
|
|
@@ -259,12 +261,122 @@ const cjs = {
|
|
|
259
261
|
strict: ["error", "global"]
|
|
260
262
|
}
|
|
261
263
|
};
|
|
262
|
-
const configs$
|
|
263
|
-
base: base$
|
|
264
|
+
const configs$8 = {
|
|
265
|
+
base: base$8,
|
|
264
266
|
cjsAndEsm,
|
|
265
267
|
cjs
|
|
266
268
|
};
|
|
267
269
|
|
|
270
|
+
const requireExtensionRule = {
|
|
271
|
+
meta: {
|
|
272
|
+
type: "suggestion",
|
|
273
|
+
docs: {
|
|
274
|
+
description: "Ensure import and export statements include a file extension",
|
|
275
|
+
category: "Best Practices",
|
|
276
|
+
recommended: true
|
|
277
|
+
},
|
|
278
|
+
fixable: "code",
|
|
279
|
+
schema: []
|
|
280
|
+
},
|
|
281
|
+
create(context) {
|
|
282
|
+
function checkSource(source) {
|
|
283
|
+
const importPath = source.value;
|
|
284
|
+
if (!importPath || !importPath.startsWith(".") || importPath.endsWith(".js")) {
|
|
285
|
+
return;
|
|
286
|
+
}
|
|
287
|
+
const resolvedPath = resolve(dirname(context.filename), importPath);
|
|
288
|
+
if (!existsSync(resolvedPath)) {
|
|
289
|
+
context.report({
|
|
290
|
+
node: source,
|
|
291
|
+
message: "Relative imports and exports must include a file extension.",
|
|
292
|
+
fix(fixer) {
|
|
293
|
+
const fixedPath = `${importPath}.js`;
|
|
294
|
+
return fixer.replaceText(source, `'${fixedPath}'`);
|
|
295
|
+
}
|
|
296
|
+
});
|
|
297
|
+
}
|
|
298
|
+
}
|
|
299
|
+
return {
|
|
300
|
+
ImportDeclaration(node) {
|
|
301
|
+
checkSource(node.source);
|
|
302
|
+
},
|
|
303
|
+
ExportNamedDeclaration(node) {
|
|
304
|
+
if (node.source) {
|
|
305
|
+
checkSource(node.source);
|
|
306
|
+
}
|
|
307
|
+
},
|
|
308
|
+
ExportAllDeclaration(node) {
|
|
309
|
+
checkSource(node.source);
|
|
310
|
+
}
|
|
311
|
+
};
|
|
312
|
+
}
|
|
313
|
+
};
|
|
314
|
+
|
|
315
|
+
const requireIndexRule = {
|
|
316
|
+
meta: {
|
|
317
|
+
type: "suggestion",
|
|
318
|
+
docs: {
|
|
319
|
+
description: "Ensure directory import and export statements use index.js",
|
|
320
|
+
category: "Best Practices",
|
|
321
|
+
recommended: true
|
|
322
|
+
},
|
|
323
|
+
fixable: "code",
|
|
324
|
+
schema: []
|
|
325
|
+
},
|
|
326
|
+
create(context) {
|
|
327
|
+
function checkSource(source) {
|
|
328
|
+
const importPath = source.value;
|
|
329
|
+
const resolvedPath = resolve(dirname(context.filename), importPath);
|
|
330
|
+
const isDirectory = existsSync(resolvedPath) && lstatSync(resolvedPath).isDirectory();
|
|
331
|
+
if (isDirectory) {
|
|
332
|
+
context.report({
|
|
333
|
+
node: source,
|
|
334
|
+
message: "Directory imports and exports must use index.js.",
|
|
335
|
+
fix(fixer) {
|
|
336
|
+
const fixedPath = importPath.replace(/\/?$/, "/index.js");
|
|
337
|
+
return fixer.replaceText(source, `'${fixedPath}'`);
|
|
338
|
+
}
|
|
339
|
+
});
|
|
340
|
+
}
|
|
341
|
+
}
|
|
342
|
+
return {
|
|
343
|
+
ImportDeclaration(node) {
|
|
344
|
+
checkSource(node.source);
|
|
345
|
+
},
|
|
346
|
+
ExportNamedDeclaration(node) {
|
|
347
|
+
if (node.source) {
|
|
348
|
+
checkSource(node.source);
|
|
349
|
+
}
|
|
350
|
+
},
|
|
351
|
+
ExportAllDeclaration(node) {
|
|
352
|
+
checkSource(node.source);
|
|
353
|
+
}
|
|
354
|
+
};
|
|
355
|
+
}
|
|
356
|
+
};
|
|
357
|
+
|
|
358
|
+
const base$7 = {
|
|
359
|
+
plugins: {
|
|
360
|
+
"decent-extension": {
|
|
361
|
+
meta: {
|
|
362
|
+
name: "decent-extension",
|
|
363
|
+
version: "1.0.0"
|
|
364
|
+
},
|
|
365
|
+
rules: {
|
|
366
|
+
"require-extension": requireExtensionRule,
|
|
367
|
+
"require-index": requireIndexRule
|
|
368
|
+
}
|
|
369
|
+
}
|
|
370
|
+
},
|
|
371
|
+
rules: {
|
|
372
|
+
"decent-extension/require-extension": "error",
|
|
373
|
+
"decent-extension/require-index": "error"
|
|
374
|
+
}
|
|
375
|
+
};
|
|
376
|
+
const configs$7 = {
|
|
377
|
+
base: base$7
|
|
378
|
+
};
|
|
379
|
+
|
|
268
380
|
const base$6 = {
|
|
269
381
|
settings: {
|
|
270
382
|
jsdoc: {
|
|
@@ -515,7 +627,8 @@ const configs = {
|
|
|
515
627
|
base
|
|
516
628
|
};
|
|
517
629
|
|
|
518
|
-
function defaultConfig(
|
|
630
|
+
function defaultConfig(options) {
|
|
631
|
+
const enableRequireExtensionRule = options?.enableRequireExtensionRule ?? true;
|
|
519
632
|
const languageOptions = {
|
|
520
633
|
globals: {
|
|
521
634
|
...globals.node
|
|
@@ -527,7 +640,7 @@ function defaultConfig(parserOptions) {
|
|
|
527
640
|
defaultProject: "tsconfig.json"
|
|
528
641
|
},
|
|
529
642
|
tsconfigRootDir: import.meta.dirname,
|
|
530
|
-
...parserOptions
|
|
643
|
+
...options?.parserOptions
|
|
531
644
|
}
|
|
532
645
|
};
|
|
533
646
|
return [
|
|
@@ -546,13 +659,15 @@ function defaultConfig(parserOptions) {
|
|
|
546
659
|
{
|
|
547
660
|
files: ["**/*.ts", "**/*.js", "**/*.cjs", "**/*.mjs", "**/*.tsx"],
|
|
548
661
|
plugins: {
|
|
662
|
+
...configs$7.base.plugins,
|
|
549
663
|
...configs$6.base.plugins,
|
|
550
664
|
...configs$4.base.plugins,
|
|
551
665
|
...configs$2.base.plugins,
|
|
552
666
|
...configs.base.plugins
|
|
553
667
|
},
|
|
554
668
|
rules: {
|
|
555
|
-
...configs$
|
|
669
|
+
...configs$8.base.rules,
|
|
670
|
+
...enableRequireExtensionRule ? configs$7.base.rules : {},
|
|
556
671
|
...configs$6.base.rules,
|
|
557
672
|
...configs$4.base.rules,
|
|
558
673
|
...configs$2.base.rules,
|
|
@@ -564,14 +679,14 @@ function defaultConfig(parserOptions) {
|
|
|
564
679
|
languageOptions: {
|
|
565
680
|
sourceType: "script"
|
|
566
681
|
},
|
|
567
|
-
...configs$
|
|
682
|
+
...configs$8.cjsAndEsm
|
|
568
683
|
},
|
|
569
684
|
{
|
|
570
685
|
files: ["**/*.js", "**/*.cjs"],
|
|
571
686
|
languageOptions: {
|
|
572
687
|
sourceType: "script"
|
|
573
688
|
},
|
|
574
|
-
...configs$
|
|
689
|
+
...configs$8.cjs
|
|
575
690
|
},
|
|
576
691
|
{
|
|
577
692
|
files: ["**/*.ts", "**/*.tsx"],
|
|
@@ -589,4 +704,4 @@ function defaultConfig(parserOptions) {
|
|
|
589
704
|
];
|
|
590
705
|
}
|
|
591
706
|
|
|
592
|
-
export { defaultConfig, configs$
|
|
707
|
+
export { defaultConfig, configs$8 as eslintConfigs, configs$6 as jsdocConfigs, configs$4 as promiseConfigs, configs$3 as reactConfigs, configs$2 as securityConfigs, configs$1 as typescriptEslintConfigs, configs as unicornConfigs };
|
package/package.json
CHANGED
package/src/extension.ts
ADDED
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import type { ConfigWithExtends } from 'typescript-eslint';
|
|
2
|
+
import { requireExtensionRule } from './rules/requireExtensionRule.js';
|
|
3
|
+
import { requireIndexRule } from './rules/requireIndexRule.js';
|
|
4
|
+
|
|
5
|
+
const base: ConfigWithExtends = {
|
|
6
|
+
plugins: {
|
|
7
|
+
'decent-extension': {
|
|
8
|
+
meta: {
|
|
9
|
+
name: 'decent-extension',
|
|
10
|
+
version: '1.0.0',
|
|
11
|
+
},
|
|
12
|
+
rules: {
|
|
13
|
+
'require-extension': requireExtensionRule,
|
|
14
|
+
'require-index': requireIndexRule,
|
|
15
|
+
},
|
|
16
|
+
},
|
|
17
|
+
},
|
|
18
|
+
rules: {
|
|
19
|
+
'decent-extension/require-extension': 'error',
|
|
20
|
+
'decent-extension/require-index': 'error',
|
|
21
|
+
},
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
export const configs = {
|
|
25
|
+
base,
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
export default {
|
|
29
|
+
configs,
|
|
30
|
+
};
|
package/src/index.ts
CHANGED
|
@@ -3,6 +3,7 @@ import globals from 'globals';
|
|
|
3
3
|
import tsEslint, { type ConfigWithExtends } from 'typescript-eslint';
|
|
4
4
|
import prettier from 'eslint-plugin-prettier/recommended';
|
|
5
5
|
import { configs as eslintConfigs } from './eslint.js';
|
|
6
|
+
import { configs as extensionConfigs } from './extension.js';
|
|
6
7
|
import { configs as jsdocConfigs } from './jsdoc.js';
|
|
7
8
|
import { configs as mochaConfigs } from './mocha.js';
|
|
8
9
|
import { configs as promiseConfigs } from './promise.js';
|
|
@@ -21,7 +22,13 @@ export {
|
|
|
21
22
|
unicornConfigs,
|
|
22
23
|
};
|
|
23
24
|
|
|
24
|
-
export
|
|
25
|
+
export interface DefaultConfigOptions {
|
|
26
|
+
parserOptions?: NonNullable<ConfigWithExtends['languageOptions']>['parserOptions'];
|
|
27
|
+
enableRequireExtensionRule?: boolean;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export function defaultConfig(options?: DefaultConfigOptions): ConfigWithExtends[] {
|
|
31
|
+
const enableRequireExtensionRule = options?.enableRequireExtensionRule ?? true;
|
|
25
32
|
const languageOptions: ConfigWithExtends['languageOptions'] = {
|
|
26
33
|
globals: {
|
|
27
34
|
...globals.node,
|
|
@@ -33,7 +40,7 @@ export function defaultConfig(parserOptions?: NonNullable<ConfigWithExtends['lan
|
|
|
33
40
|
defaultProject: 'tsconfig.json',
|
|
34
41
|
},
|
|
35
42
|
tsconfigRootDir: import.meta.dirname,
|
|
36
|
-
...parserOptions,
|
|
43
|
+
...options?.parserOptions,
|
|
37
44
|
},
|
|
38
45
|
};
|
|
39
46
|
|
|
@@ -53,6 +60,7 @@ export function defaultConfig(parserOptions?: NonNullable<ConfigWithExtends['lan
|
|
|
53
60
|
{
|
|
54
61
|
files: ['**/*.ts', '**/*.js', '**/*.cjs', '**/*.mjs', '**/*.tsx'],
|
|
55
62
|
plugins: {
|
|
63
|
+
...extensionConfigs.base.plugins,
|
|
56
64
|
...jsdocConfigs.base.plugins,
|
|
57
65
|
...promiseConfigs.base.plugins,
|
|
58
66
|
...securityConfigs.base.plugins,
|
|
@@ -60,6 +68,7 @@ export function defaultConfig(parserOptions?: NonNullable<ConfigWithExtends['lan
|
|
|
60
68
|
},
|
|
61
69
|
rules: {
|
|
62
70
|
...eslintConfigs.base.rules,
|
|
71
|
+
...(enableRequireExtensionRule ? extensionConfigs.base.rules : {}),
|
|
63
72
|
...jsdocConfigs.base.rules,
|
|
64
73
|
...promiseConfigs.base.rules,
|
|
65
74
|
...securityConfigs.base.rules,
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import { existsSync } from 'fs';
|
|
2
|
+
import { dirname, resolve } from 'path';
|
|
3
|
+
import type { Rule } from 'eslint';
|
|
4
|
+
|
|
5
|
+
export const requireExtensionRule: Rule.RuleModule = {
|
|
6
|
+
meta: {
|
|
7
|
+
type: 'suggestion',
|
|
8
|
+
docs: {
|
|
9
|
+
description: 'Ensure import and export statements include a file extension',
|
|
10
|
+
category: 'Best Practices',
|
|
11
|
+
recommended: true,
|
|
12
|
+
},
|
|
13
|
+
fixable: 'code',
|
|
14
|
+
schema: [],
|
|
15
|
+
},
|
|
16
|
+
create(context: Rule.RuleContext) {
|
|
17
|
+
function checkSource(source: Parameters<NonNullable<Rule.NodeListener['ImportDeclaration']>>[0]['source']): void {
|
|
18
|
+
const importPath = source.value as string;
|
|
19
|
+
|
|
20
|
+
if (!importPath || !importPath.startsWith('.') || importPath.endsWith('.js')) {
|
|
21
|
+
return;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
const resolvedPath = resolve(dirname(context.filename), importPath);
|
|
25
|
+
|
|
26
|
+
// If the import/export path doesn't end with a file extension, report an error
|
|
27
|
+
// eslint-disable-next-line security/detect-non-literal-fs-filename
|
|
28
|
+
if (!existsSync(resolvedPath)) {
|
|
29
|
+
context.report({
|
|
30
|
+
node: source,
|
|
31
|
+
message: 'Relative imports and exports must include a file extension.',
|
|
32
|
+
fix(fixer) {
|
|
33
|
+
const fixedPath = `${importPath}.js`;
|
|
34
|
+
return fixer.replaceText(source, `'${fixedPath}'`);
|
|
35
|
+
},
|
|
36
|
+
});
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
return {
|
|
41
|
+
ImportDeclaration(node: Parameters<NonNullable<Rule.NodeListener['ImportDeclaration']>>[0]): void {
|
|
42
|
+
checkSource(node.source);
|
|
43
|
+
},
|
|
44
|
+
ExportNamedDeclaration(node: Parameters<NonNullable<Rule.NodeListener['ExportNamedDeclaration']>>[0]): void {
|
|
45
|
+
if (node.source) {
|
|
46
|
+
checkSource(node.source);
|
|
47
|
+
}
|
|
48
|
+
},
|
|
49
|
+
ExportAllDeclaration(node: Parameters<NonNullable<Rule.NodeListener['ExportAllDeclaration']>>[0]): void {
|
|
50
|
+
checkSource(node.source);
|
|
51
|
+
},
|
|
52
|
+
};
|
|
53
|
+
},
|
|
54
|
+
};
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import { existsSync, lstatSync } from 'fs';
|
|
2
|
+
import { resolve, dirname } from 'path';
|
|
3
|
+
import type { Rule } from 'eslint';
|
|
4
|
+
|
|
5
|
+
export const requireIndexRule: Rule.RuleModule = {
|
|
6
|
+
meta: {
|
|
7
|
+
type: 'suggestion',
|
|
8
|
+
docs: {
|
|
9
|
+
description: 'Ensure directory import and export statements use index.js',
|
|
10
|
+
category: 'Best Practices',
|
|
11
|
+
recommended: true,
|
|
12
|
+
},
|
|
13
|
+
fixable: 'code',
|
|
14
|
+
schema: [],
|
|
15
|
+
},
|
|
16
|
+
create(context: Rule.RuleContext) {
|
|
17
|
+
function checkSource(source: Parameters<NonNullable<Rule.NodeListener['ImportDeclaration']>>[0]['source']): void {
|
|
18
|
+
const importPath = source.value as string;
|
|
19
|
+
|
|
20
|
+
// Resolve the path relative to the file being linted
|
|
21
|
+
const resolvedPath = resolve(dirname(context.filename), importPath);
|
|
22
|
+
|
|
23
|
+
// eslint-disable-next-line security/detect-non-literal-fs-filename
|
|
24
|
+
const isDirectory = existsSync(resolvedPath) && lstatSync(resolvedPath).isDirectory();
|
|
25
|
+
if (isDirectory) {
|
|
26
|
+
context.report({
|
|
27
|
+
node: source,
|
|
28
|
+
message: 'Directory imports and exports must use index.js.',
|
|
29
|
+
fix(fixer) {
|
|
30
|
+
const fixedPath = importPath.replace(/\/?$/, '/index.js');
|
|
31
|
+
return fixer.replaceText(source, `'${fixedPath}'`);
|
|
32
|
+
},
|
|
33
|
+
});
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
return {
|
|
38
|
+
ImportDeclaration(node: Parameters<NonNullable<Rule.NodeListener['ImportDeclaration']>>[0]): void {
|
|
39
|
+
checkSource(node.source);
|
|
40
|
+
},
|
|
41
|
+
ExportNamedDeclaration(node: Parameters<NonNullable<Rule.NodeListener['ExportNamedDeclaration']>>[0]): void {
|
|
42
|
+
if (node.source) {
|
|
43
|
+
checkSource(node.source);
|
|
44
|
+
}
|
|
45
|
+
},
|
|
46
|
+
ExportAllDeclaration(node: Parameters<NonNullable<Rule.NodeListener['ExportAllDeclaration']>>[0]): void {
|
|
47
|
+
checkSource(node.source);
|
|
48
|
+
},
|
|
49
|
+
};
|
|
50
|
+
},
|
|
51
|
+
};
|