eslint-config-decent 1.0.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 +217 -25
- package/dist/index.d.cts +11 -3
- package/dist/index.d.mts +11 -3
- package/dist/index.d.ts +11 -3
- package/dist/index.mjs +210 -23
- package/package.json +19 -1
- package/src/extension.ts +30 -0
- package/src/index.ts +19 -4
- package/src/react.ts +77 -0
- package/src/rules/requireExtensionRule.ts +54 -0
- package/src/rules/requireIndexRule.ts +51 -0
- package/src/types/eslint-plugin-jsx-a11y.d.ts +9 -0
- package/src/types/eslint-plugin-react-hooks.d.ts +5 -0
- package/src/types/eslint-plugin-react.d.ts +9 -0
- package/src/types/eslint-plugin-testing-library.d.ts +9 -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,9 +4,15 @@ 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');
|
|
12
|
+
const a11y = require('eslint-plugin-jsx-a11y');
|
|
13
|
+
const react = require('eslint-plugin-react');
|
|
14
|
+
const reactHooks = require('eslint-plugin-react-hooks');
|
|
15
|
+
const testingLibrary = require('eslint-plugin-testing-library');
|
|
10
16
|
const security = require('eslint-plugin-security');
|
|
11
17
|
const unicorn = require('eslint-plugin-unicorn');
|
|
12
18
|
|
|
@@ -19,10 +25,14 @@ const prettier__default = /*#__PURE__*/_interopDefaultCompat(prettier);
|
|
|
19
25
|
const jsdoc__default = /*#__PURE__*/_interopDefaultCompat(jsdoc);
|
|
20
26
|
const mocha__default = /*#__PURE__*/_interopDefaultCompat(mocha);
|
|
21
27
|
const promise__default = /*#__PURE__*/_interopDefaultCompat(promise);
|
|
28
|
+
const a11y__default = /*#__PURE__*/_interopDefaultCompat(a11y);
|
|
29
|
+
const react__default = /*#__PURE__*/_interopDefaultCompat(react);
|
|
30
|
+
const reactHooks__default = /*#__PURE__*/_interopDefaultCompat(reactHooks);
|
|
31
|
+
const testingLibrary__default = /*#__PURE__*/_interopDefaultCompat(testingLibrary);
|
|
22
32
|
const security__default = /*#__PURE__*/_interopDefaultCompat(security);
|
|
23
33
|
const unicorn__default = /*#__PURE__*/_interopDefaultCompat(unicorn);
|
|
24
34
|
|
|
25
|
-
const base$
|
|
35
|
+
const base$8 = {
|
|
26
36
|
rules: {
|
|
27
37
|
"array-callback-return": ["error", { allowImplicit: true }],
|
|
28
38
|
"block-scoped-var": "error",
|
|
@@ -269,13 +279,123 @@ const cjs = {
|
|
|
269
279
|
strict: ["error", "global"]
|
|
270
280
|
}
|
|
271
281
|
};
|
|
272
|
-
const configs$
|
|
273
|
-
base: base$
|
|
282
|
+
const configs$8 = {
|
|
283
|
+
base: base$8,
|
|
274
284
|
cjsAndEsm,
|
|
275
285
|
cjs
|
|
276
286
|
};
|
|
277
287
|
|
|
278
|
-
const
|
|
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
|
+
|
|
398
|
+
const base$6 = {
|
|
279
399
|
settings: {
|
|
280
400
|
jsdoc: {
|
|
281
401
|
preferredTypes: {
|
|
@@ -300,11 +420,11 @@ const base$5 = {
|
|
|
300
420
|
"unicorn/prefer-set-has": "error"
|
|
301
421
|
}
|
|
302
422
|
};
|
|
303
|
-
const configs$
|
|
304
|
-
base: base$
|
|
423
|
+
const configs$6 = {
|
|
424
|
+
base: base$6
|
|
305
425
|
};
|
|
306
426
|
|
|
307
|
-
const base$
|
|
427
|
+
const base$5 = {
|
|
308
428
|
plugins: {
|
|
309
429
|
mocha: mocha__default
|
|
310
430
|
},
|
|
@@ -322,11 +442,11 @@ const base$4 = {
|
|
|
322
442
|
"mocha/no-mocha-arrows": "off"
|
|
323
443
|
}
|
|
324
444
|
};
|
|
325
|
-
const configs$
|
|
326
|
-
base: base$
|
|
445
|
+
const configs$5 = {
|
|
446
|
+
base: base$5
|
|
327
447
|
};
|
|
328
448
|
|
|
329
|
-
const base$
|
|
449
|
+
const base$4 = {
|
|
330
450
|
plugins: {
|
|
331
451
|
promise: promise__default
|
|
332
452
|
},
|
|
@@ -343,6 +463,70 @@ const base$3 = {
|
|
|
343
463
|
"promise/param-names": "error"
|
|
344
464
|
}
|
|
345
465
|
};
|
|
466
|
+
const configs$4 = {
|
|
467
|
+
base: base$4
|
|
468
|
+
};
|
|
469
|
+
|
|
470
|
+
const base$3 = {
|
|
471
|
+
plugins: {
|
|
472
|
+
"jsx-a11y": a11y__default,
|
|
473
|
+
react: react__default,
|
|
474
|
+
"react-hooks": reactHooks__default,
|
|
475
|
+
"testing-library": testingLibrary__default
|
|
476
|
+
},
|
|
477
|
+
rules: {
|
|
478
|
+
...a11y__default.configs.recommended.rules,
|
|
479
|
+
"jsx-a11y/aria-proptypes": "error",
|
|
480
|
+
"jsx-a11y/aria-role": ["error", { ignoreNonDOM: true }],
|
|
481
|
+
"jsx-a11y/aria-unsupported-elements": "error",
|
|
482
|
+
"jsx-a11y/role-has-required-aria-props": "error",
|
|
483
|
+
...react__default.configs.recommended.rules,
|
|
484
|
+
"react/default-props-match-prop-types": "error",
|
|
485
|
+
"react/display-name": ["error", { ignoreTranspilerName: false }],
|
|
486
|
+
"react/forbid-foreign-prop-types": ["error", { allowInPropTypes: true }],
|
|
487
|
+
"react/iframe-missing-sandbox": "warn",
|
|
488
|
+
"react/jsx-closing-bracket-location": "error",
|
|
489
|
+
"react/jsx-fragments": "error",
|
|
490
|
+
"react/jsx-no-leaked-render": ["error", { validStrategies: ["ternary"] }],
|
|
491
|
+
"react/jsx-no-script-url": "error",
|
|
492
|
+
"react/jsx-no-target-blank": "error",
|
|
493
|
+
"react/jsx-no-undef": "error",
|
|
494
|
+
"react/jsx-no-useless-fragment": "error",
|
|
495
|
+
"react/jsx-pascal-case": [
|
|
496
|
+
"error",
|
|
497
|
+
{
|
|
498
|
+
allowAllCaps: true,
|
|
499
|
+
ignore: []
|
|
500
|
+
}
|
|
501
|
+
],
|
|
502
|
+
"react/jsx-props-no-multi-spaces": "error",
|
|
503
|
+
"react/no-access-state-in-setstate": "error",
|
|
504
|
+
"react/no-arrow-function-lifecycle": "error",
|
|
505
|
+
"react/no-danger-with-children": "error",
|
|
506
|
+
"react/no-deprecated": "error",
|
|
507
|
+
"react/no-did-mount-set-state": "error",
|
|
508
|
+
"react/no-did-update-set-state": "error",
|
|
509
|
+
"react/no-direct-mutation-state": "error",
|
|
510
|
+
"react/no-invalid-html-attribute": "error",
|
|
511
|
+
"react/no-namespace": "error",
|
|
512
|
+
"react/no-redundant-should-component-update": "error",
|
|
513
|
+
"react/no-this-in-sfc": "error",
|
|
514
|
+
"react/no-typos": "error",
|
|
515
|
+
"react/no-unsafe": "error",
|
|
516
|
+
"react/no-unstable-nested-components": "error",
|
|
517
|
+
"react/no-unused-class-component-methods": "error",
|
|
518
|
+
"react/no-unused-prop-types": "error",
|
|
519
|
+
"react/no-unused-state": "error",
|
|
520
|
+
"react/no-will-update-set-state": "error",
|
|
521
|
+
"react/prefer-stateless-function": "error",
|
|
522
|
+
"react/require-render-return": "error",
|
|
523
|
+
"react/self-closing-comp": "error",
|
|
524
|
+
"react/style-prop-object": "error",
|
|
525
|
+
"react-hooks/rules-of-hooks": "error",
|
|
526
|
+
"react-hooks/exhaustive-deps": "error",
|
|
527
|
+
...testingLibrary__default.configs.react.rules
|
|
528
|
+
}
|
|
529
|
+
};
|
|
346
530
|
const configs$3 = {
|
|
347
531
|
base: base$3
|
|
348
532
|
};
|
|
@@ -461,7 +645,8 @@ const configs = {
|
|
|
461
645
|
base
|
|
462
646
|
};
|
|
463
647
|
|
|
464
|
-
function defaultConfig(
|
|
648
|
+
function defaultConfig(options) {
|
|
649
|
+
const enableRequireExtensionRule = options?.enableRequireExtensionRule ?? true;
|
|
465
650
|
const languageOptions = {
|
|
466
651
|
globals: {
|
|
467
652
|
...globals__default.node
|
|
@@ -473,7 +658,7 @@ function defaultConfig(parserOptions) {
|
|
|
473
658
|
defaultProject: "tsconfig.json"
|
|
474
659
|
},
|
|
475
660
|
tsconfigRootDir: undefined,
|
|
476
|
-
...parserOptions
|
|
661
|
+
...options?.parserOptions
|
|
477
662
|
}
|
|
478
663
|
};
|
|
479
664
|
return [
|
|
@@ -486,21 +671,23 @@ function defaultConfig(parserOptions) {
|
|
|
486
671
|
{
|
|
487
672
|
languageOptions,
|
|
488
673
|
settings: {
|
|
489
|
-
...configs$
|
|
674
|
+
...configs$6.base.settings
|
|
490
675
|
}
|
|
491
676
|
},
|
|
492
677
|
{
|
|
493
|
-
files: ["**/*.ts", "**/*.js", "**/*.cjs", "**/*.mjs"],
|
|
678
|
+
files: ["**/*.ts", "**/*.js", "**/*.cjs", "**/*.mjs", "**/*.tsx"],
|
|
494
679
|
plugins: {
|
|
495
|
-
...configs$
|
|
496
|
-
...configs$
|
|
680
|
+
...configs$7.base.plugins,
|
|
681
|
+
...configs$6.base.plugins,
|
|
682
|
+
...configs$4.base.plugins,
|
|
497
683
|
...configs$2.base.plugins,
|
|
498
684
|
...configs.base.plugins
|
|
499
685
|
},
|
|
500
686
|
rules: {
|
|
687
|
+
...configs$8.base.rules,
|
|
688
|
+
...enableRequireExtensionRule ? configs$7.base.rules : {},
|
|
501
689
|
...configs$6.base.rules,
|
|
502
|
-
...configs$
|
|
503
|
-
...configs$3.base.rules,
|
|
690
|
+
...configs$4.base.rules,
|
|
504
691
|
...configs$2.base.rules,
|
|
505
692
|
...configs.base.rules
|
|
506
693
|
}
|
|
@@ -510,31 +697,36 @@ function defaultConfig(parserOptions) {
|
|
|
510
697
|
languageOptions: {
|
|
511
698
|
sourceType: "script"
|
|
512
699
|
},
|
|
513
|
-
...configs$
|
|
700
|
+
...configs$8.cjsAndEsm
|
|
514
701
|
},
|
|
515
702
|
{
|
|
516
703
|
files: ["**/*.js", "**/*.cjs"],
|
|
517
704
|
languageOptions: {
|
|
518
705
|
sourceType: "script"
|
|
519
706
|
},
|
|
520
|
-
...configs$
|
|
707
|
+
...configs$8.cjs
|
|
521
708
|
},
|
|
522
709
|
{
|
|
523
|
-
files: ["**/*.ts"],
|
|
710
|
+
files: ["**/*.ts", "**/*.tsx"],
|
|
524
711
|
...configs$1.base
|
|
525
712
|
},
|
|
713
|
+
{
|
|
714
|
+
files: ["**/*.tsx"],
|
|
715
|
+
...configs$3.base
|
|
716
|
+
},
|
|
526
717
|
{
|
|
527
718
|
files: ["**/*.tests.ts", "tests/tests.ts"],
|
|
528
|
-
...configs$
|
|
719
|
+
...configs$5.base
|
|
529
720
|
},
|
|
530
721
|
prettier__default
|
|
531
722
|
];
|
|
532
723
|
}
|
|
533
724
|
|
|
534
725
|
exports.defaultConfig = defaultConfig;
|
|
535
|
-
exports.eslintConfigs = configs$
|
|
536
|
-
exports.jsdocConfigs = configs$
|
|
537
|
-
exports.promiseConfigs = configs$
|
|
726
|
+
exports.eslintConfigs = configs$8;
|
|
727
|
+
exports.jsdocConfigs = configs$6;
|
|
728
|
+
exports.promiseConfigs = configs$4;
|
|
729
|
+
exports.reactConfigs = configs$3;
|
|
538
730
|
exports.securityConfigs = configs$2;
|
|
539
731
|
exports.typescriptEslintConfigs = configs$1;
|
|
540
732
|
exports.unicornConfigs = configs;
|
package/dist/index.d.cts
CHANGED
|
@@ -1,11 +1,15 @@
|
|
|
1
1
|
import { ConfigWithExtends } from 'typescript-eslint';
|
|
2
2
|
|
|
3
|
-
declare const configs$
|
|
3
|
+
declare const configs$6: {
|
|
4
4
|
base: ConfigWithExtends;
|
|
5
5
|
cjsAndEsm: ConfigWithExtends;
|
|
6
6
|
cjs: ConfigWithExtends;
|
|
7
7
|
};
|
|
8
8
|
|
|
9
|
+
declare const configs$5: {
|
|
10
|
+
base: ConfigWithExtends;
|
|
11
|
+
};
|
|
12
|
+
|
|
9
13
|
declare const configs$4: {
|
|
10
14
|
base: ConfigWithExtends;
|
|
11
15
|
};
|
|
@@ -26,6 +30,10 @@ declare const configs: {
|
|
|
26
30
|
base: ConfigWithExtends;
|
|
27
31
|
};
|
|
28
32
|
|
|
29
|
-
|
|
33
|
+
interface DefaultConfigOptions {
|
|
34
|
+
parserOptions?: NonNullable<ConfigWithExtends['languageOptions']>['parserOptions'];
|
|
35
|
+
enableRequireExtensionRule?: boolean;
|
|
36
|
+
}
|
|
37
|
+
declare function defaultConfig(options?: DefaultConfigOptions): ConfigWithExtends[];
|
|
30
38
|
|
|
31
|
-
export { defaultConfig, configs$
|
|
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
|
@@ -1,11 +1,15 @@
|
|
|
1
1
|
import { ConfigWithExtends } from 'typescript-eslint';
|
|
2
2
|
|
|
3
|
-
declare const configs$
|
|
3
|
+
declare const configs$6: {
|
|
4
4
|
base: ConfigWithExtends;
|
|
5
5
|
cjsAndEsm: ConfigWithExtends;
|
|
6
6
|
cjs: ConfigWithExtends;
|
|
7
7
|
};
|
|
8
8
|
|
|
9
|
+
declare const configs$5: {
|
|
10
|
+
base: ConfigWithExtends;
|
|
11
|
+
};
|
|
12
|
+
|
|
9
13
|
declare const configs$4: {
|
|
10
14
|
base: ConfigWithExtends;
|
|
11
15
|
};
|
|
@@ -26,6 +30,10 @@ declare const configs: {
|
|
|
26
30
|
base: ConfigWithExtends;
|
|
27
31
|
};
|
|
28
32
|
|
|
29
|
-
|
|
33
|
+
interface DefaultConfigOptions {
|
|
34
|
+
parserOptions?: NonNullable<ConfigWithExtends['languageOptions']>['parserOptions'];
|
|
35
|
+
enableRequireExtensionRule?: boolean;
|
|
36
|
+
}
|
|
37
|
+
declare function defaultConfig(options?: DefaultConfigOptions): ConfigWithExtends[];
|
|
30
38
|
|
|
31
|
-
export { defaultConfig, configs$
|
|
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
|
@@ -1,11 +1,15 @@
|
|
|
1
1
|
import { ConfigWithExtends } from 'typescript-eslint';
|
|
2
2
|
|
|
3
|
-
declare const configs$
|
|
3
|
+
declare const configs$6: {
|
|
4
4
|
base: ConfigWithExtends;
|
|
5
5
|
cjsAndEsm: ConfigWithExtends;
|
|
6
6
|
cjs: ConfigWithExtends;
|
|
7
7
|
};
|
|
8
8
|
|
|
9
|
+
declare const configs$5: {
|
|
10
|
+
base: ConfigWithExtends;
|
|
11
|
+
};
|
|
12
|
+
|
|
9
13
|
declare const configs$4: {
|
|
10
14
|
base: ConfigWithExtends;
|
|
11
15
|
};
|
|
@@ -26,6 +30,10 @@ declare const configs: {
|
|
|
26
30
|
base: ConfigWithExtends;
|
|
27
31
|
};
|
|
28
32
|
|
|
29
|
-
|
|
33
|
+
interface DefaultConfigOptions {
|
|
34
|
+
parserOptions?: NonNullable<ConfigWithExtends['languageOptions']>['parserOptions'];
|
|
35
|
+
enableRequireExtensionRule?: boolean;
|
|
36
|
+
}
|
|
37
|
+
declare function defaultConfig(options?: DefaultConfigOptions): ConfigWithExtends[];
|
|
30
38
|
|
|
31
|
-
export { defaultConfig, configs$
|
|
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,13 +2,19 @@ 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';
|
|
10
|
+
import a11y from 'eslint-plugin-jsx-a11y';
|
|
11
|
+
import react from 'eslint-plugin-react';
|
|
12
|
+
import reactHooks from 'eslint-plugin-react-hooks';
|
|
13
|
+
import testingLibrary from 'eslint-plugin-testing-library';
|
|
8
14
|
import security from 'eslint-plugin-security';
|
|
9
15
|
import unicorn from 'eslint-plugin-unicorn';
|
|
10
16
|
|
|
11
|
-
const base$
|
|
17
|
+
const base$8 = {
|
|
12
18
|
rules: {
|
|
13
19
|
"array-callback-return": ["error", { allowImplicit: true }],
|
|
14
20
|
"block-scoped-var": "error",
|
|
@@ -255,13 +261,123 @@ const cjs = {
|
|
|
255
261
|
strict: ["error", "global"]
|
|
256
262
|
}
|
|
257
263
|
};
|
|
258
|
-
const configs$
|
|
259
|
-
base: base$
|
|
264
|
+
const configs$8 = {
|
|
265
|
+
base: base$8,
|
|
260
266
|
cjsAndEsm,
|
|
261
267
|
cjs
|
|
262
268
|
};
|
|
263
269
|
|
|
264
|
-
const
|
|
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
|
+
|
|
380
|
+
const base$6 = {
|
|
265
381
|
settings: {
|
|
266
382
|
jsdoc: {
|
|
267
383
|
preferredTypes: {
|
|
@@ -286,11 +402,11 @@ const base$5 = {
|
|
|
286
402
|
"unicorn/prefer-set-has": "error"
|
|
287
403
|
}
|
|
288
404
|
};
|
|
289
|
-
const configs$
|
|
290
|
-
base: base$
|
|
405
|
+
const configs$6 = {
|
|
406
|
+
base: base$6
|
|
291
407
|
};
|
|
292
408
|
|
|
293
|
-
const base$
|
|
409
|
+
const base$5 = {
|
|
294
410
|
plugins: {
|
|
295
411
|
mocha
|
|
296
412
|
},
|
|
@@ -308,11 +424,11 @@ const base$4 = {
|
|
|
308
424
|
"mocha/no-mocha-arrows": "off"
|
|
309
425
|
}
|
|
310
426
|
};
|
|
311
|
-
const configs$
|
|
312
|
-
base: base$
|
|
427
|
+
const configs$5 = {
|
|
428
|
+
base: base$5
|
|
313
429
|
};
|
|
314
430
|
|
|
315
|
-
const base$
|
|
431
|
+
const base$4 = {
|
|
316
432
|
plugins: {
|
|
317
433
|
promise
|
|
318
434
|
},
|
|
@@ -329,6 +445,70 @@ const base$3 = {
|
|
|
329
445
|
"promise/param-names": "error"
|
|
330
446
|
}
|
|
331
447
|
};
|
|
448
|
+
const configs$4 = {
|
|
449
|
+
base: base$4
|
|
450
|
+
};
|
|
451
|
+
|
|
452
|
+
const base$3 = {
|
|
453
|
+
plugins: {
|
|
454
|
+
"jsx-a11y": a11y,
|
|
455
|
+
react,
|
|
456
|
+
"react-hooks": reactHooks,
|
|
457
|
+
"testing-library": testingLibrary
|
|
458
|
+
},
|
|
459
|
+
rules: {
|
|
460
|
+
...a11y.configs.recommended.rules,
|
|
461
|
+
"jsx-a11y/aria-proptypes": "error",
|
|
462
|
+
"jsx-a11y/aria-role": ["error", { ignoreNonDOM: true }],
|
|
463
|
+
"jsx-a11y/aria-unsupported-elements": "error",
|
|
464
|
+
"jsx-a11y/role-has-required-aria-props": "error",
|
|
465
|
+
...react.configs.recommended.rules,
|
|
466
|
+
"react/default-props-match-prop-types": "error",
|
|
467
|
+
"react/display-name": ["error", { ignoreTranspilerName: false }],
|
|
468
|
+
"react/forbid-foreign-prop-types": ["error", { allowInPropTypes: true }],
|
|
469
|
+
"react/iframe-missing-sandbox": "warn",
|
|
470
|
+
"react/jsx-closing-bracket-location": "error",
|
|
471
|
+
"react/jsx-fragments": "error",
|
|
472
|
+
"react/jsx-no-leaked-render": ["error", { validStrategies: ["ternary"] }],
|
|
473
|
+
"react/jsx-no-script-url": "error",
|
|
474
|
+
"react/jsx-no-target-blank": "error",
|
|
475
|
+
"react/jsx-no-undef": "error",
|
|
476
|
+
"react/jsx-no-useless-fragment": "error",
|
|
477
|
+
"react/jsx-pascal-case": [
|
|
478
|
+
"error",
|
|
479
|
+
{
|
|
480
|
+
allowAllCaps: true,
|
|
481
|
+
ignore: []
|
|
482
|
+
}
|
|
483
|
+
],
|
|
484
|
+
"react/jsx-props-no-multi-spaces": "error",
|
|
485
|
+
"react/no-access-state-in-setstate": "error",
|
|
486
|
+
"react/no-arrow-function-lifecycle": "error",
|
|
487
|
+
"react/no-danger-with-children": "error",
|
|
488
|
+
"react/no-deprecated": "error",
|
|
489
|
+
"react/no-did-mount-set-state": "error",
|
|
490
|
+
"react/no-did-update-set-state": "error",
|
|
491
|
+
"react/no-direct-mutation-state": "error",
|
|
492
|
+
"react/no-invalid-html-attribute": "error",
|
|
493
|
+
"react/no-namespace": "error",
|
|
494
|
+
"react/no-redundant-should-component-update": "error",
|
|
495
|
+
"react/no-this-in-sfc": "error",
|
|
496
|
+
"react/no-typos": "error",
|
|
497
|
+
"react/no-unsafe": "error",
|
|
498
|
+
"react/no-unstable-nested-components": "error",
|
|
499
|
+
"react/no-unused-class-component-methods": "error",
|
|
500
|
+
"react/no-unused-prop-types": "error",
|
|
501
|
+
"react/no-unused-state": "error",
|
|
502
|
+
"react/no-will-update-set-state": "error",
|
|
503
|
+
"react/prefer-stateless-function": "error",
|
|
504
|
+
"react/require-render-return": "error",
|
|
505
|
+
"react/self-closing-comp": "error",
|
|
506
|
+
"react/style-prop-object": "error",
|
|
507
|
+
"react-hooks/rules-of-hooks": "error",
|
|
508
|
+
"react-hooks/exhaustive-deps": "error",
|
|
509
|
+
...testingLibrary.configs.react.rules
|
|
510
|
+
}
|
|
511
|
+
};
|
|
332
512
|
const configs$3 = {
|
|
333
513
|
base: base$3
|
|
334
514
|
};
|
|
@@ -447,7 +627,8 @@ const configs = {
|
|
|
447
627
|
base
|
|
448
628
|
};
|
|
449
629
|
|
|
450
|
-
function defaultConfig(
|
|
630
|
+
function defaultConfig(options) {
|
|
631
|
+
const enableRequireExtensionRule = options?.enableRequireExtensionRule ?? true;
|
|
451
632
|
const languageOptions = {
|
|
452
633
|
globals: {
|
|
453
634
|
...globals.node
|
|
@@ -459,7 +640,7 @@ function defaultConfig(parserOptions) {
|
|
|
459
640
|
defaultProject: "tsconfig.json"
|
|
460
641
|
},
|
|
461
642
|
tsconfigRootDir: import.meta.dirname,
|
|
462
|
-
...parserOptions
|
|
643
|
+
...options?.parserOptions
|
|
463
644
|
}
|
|
464
645
|
};
|
|
465
646
|
return [
|
|
@@ -472,21 +653,23 @@ function defaultConfig(parserOptions) {
|
|
|
472
653
|
{
|
|
473
654
|
languageOptions,
|
|
474
655
|
settings: {
|
|
475
|
-
...configs$
|
|
656
|
+
...configs$6.base.settings
|
|
476
657
|
}
|
|
477
658
|
},
|
|
478
659
|
{
|
|
479
|
-
files: ["**/*.ts", "**/*.js", "**/*.cjs", "**/*.mjs"],
|
|
660
|
+
files: ["**/*.ts", "**/*.js", "**/*.cjs", "**/*.mjs", "**/*.tsx"],
|
|
480
661
|
plugins: {
|
|
481
|
-
...configs$
|
|
482
|
-
...configs$
|
|
662
|
+
...configs$7.base.plugins,
|
|
663
|
+
...configs$6.base.plugins,
|
|
664
|
+
...configs$4.base.plugins,
|
|
483
665
|
...configs$2.base.plugins,
|
|
484
666
|
...configs.base.plugins
|
|
485
667
|
},
|
|
486
668
|
rules: {
|
|
669
|
+
...configs$8.base.rules,
|
|
670
|
+
...enableRequireExtensionRule ? configs$7.base.rules : {},
|
|
487
671
|
...configs$6.base.rules,
|
|
488
|
-
...configs$
|
|
489
|
-
...configs$3.base.rules,
|
|
672
|
+
...configs$4.base.rules,
|
|
490
673
|
...configs$2.base.rules,
|
|
491
674
|
...configs.base.rules
|
|
492
675
|
}
|
|
@@ -496,25 +679,29 @@ function defaultConfig(parserOptions) {
|
|
|
496
679
|
languageOptions: {
|
|
497
680
|
sourceType: "script"
|
|
498
681
|
},
|
|
499
|
-
...configs$
|
|
682
|
+
...configs$8.cjsAndEsm
|
|
500
683
|
},
|
|
501
684
|
{
|
|
502
685
|
files: ["**/*.js", "**/*.cjs"],
|
|
503
686
|
languageOptions: {
|
|
504
687
|
sourceType: "script"
|
|
505
688
|
},
|
|
506
|
-
...configs$
|
|
689
|
+
...configs$8.cjs
|
|
507
690
|
},
|
|
508
691
|
{
|
|
509
|
-
files: ["**/*.ts"],
|
|
692
|
+
files: ["**/*.ts", "**/*.tsx"],
|
|
510
693
|
...configs$1.base
|
|
511
694
|
},
|
|
695
|
+
{
|
|
696
|
+
files: ["**/*.tsx"],
|
|
697
|
+
...configs$3.base
|
|
698
|
+
},
|
|
512
699
|
{
|
|
513
700
|
files: ["**/*.tests.ts", "tests/tests.ts"],
|
|
514
|
-
...configs$
|
|
701
|
+
...configs$5.base
|
|
515
702
|
},
|
|
516
703
|
prettier
|
|
517
704
|
];
|
|
518
705
|
}
|
|
519
706
|
|
|
520
|
-
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
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "eslint-config-decent",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.2.0",
|
|
4
4
|
"description": "A decent ESLint configuration",
|
|
5
5
|
"main": "./dist/index.cjs",
|
|
6
6
|
"module": "./dist/index.mjs",
|
|
@@ -78,7 +78,11 @@
|
|
|
78
78
|
"eslint-plugin-mocha": "^10.4.3",
|
|
79
79
|
"eslint-plugin-prettier": "^5.1.3",
|
|
80
80
|
"eslint-plugin-promise": "^6.2.0",
|
|
81
|
+
"eslint-plugin-jsx-a11y": "^6.8.0",
|
|
82
|
+
"eslint-plugin-react": "^7.34.2",
|
|
83
|
+
"eslint-plugin-react-hooks": "^4.6.2",
|
|
81
84
|
"eslint-plugin-security": "^3.0.0",
|
|
85
|
+
"eslint-plugin-testing-library": "^6.2.2",
|
|
82
86
|
"eslint-plugin-unicorn": "^53.0.0",
|
|
83
87
|
"globals": "^15.3.0",
|
|
84
88
|
"typescript-eslint": "^8.0.0-alpha.25"
|
|
@@ -96,5 +100,19 @@
|
|
|
96
100
|
"rimraf": "^5.0.7",
|
|
97
101
|
"typescript": "^5.4.5",
|
|
98
102
|
"unbuild": "2.0.0"
|
|
103
|
+
},
|
|
104
|
+
"overrides": {
|
|
105
|
+
"eslint-plugin-jsx-a11y": {
|
|
106
|
+
"eslint": "$eslint"
|
|
107
|
+
},
|
|
108
|
+
"eslint-plugin-react": {
|
|
109
|
+
"eslint": "$eslint"
|
|
110
|
+
},
|
|
111
|
+
"eslint-plugin-react-hooks": {
|
|
112
|
+
"eslint": "$eslint"
|
|
113
|
+
},
|
|
114
|
+
"eslint-plugin-testing-library": {
|
|
115
|
+
"eslint": "$eslint"
|
|
116
|
+
}
|
|
99
117
|
}
|
|
100
118
|
}
|
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,9 +3,11 @@ 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';
|
|
10
|
+
import { configs as reactConfigs } from './react.js';
|
|
9
11
|
import { configs as securityConfigs } from './security.js';
|
|
10
12
|
import { configs as typescriptEslintConfigs } from './typescriptEslint.js';
|
|
11
13
|
import { configs as unicornConfigs } from './unicorn.js';
|
|
@@ -14,12 +16,19 @@ export {
|
|
|
14
16
|
eslintConfigs, //
|
|
15
17
|
jsdocConfigs,
|
|
16
18
|
promiseConfigs,
|
|
19
|
+
reactConfigs,
|
|
17
20
|
securityConfigs,
|
|
18
21
|
typescriptEslintConfigs,
|
|
19
22
|
unicornConfigs,
|
|
20
23
|
};
|
|
21
24
|
|
|
22
|
-
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;
|
|
23
32
|
const languageOptions: ConfigWithExtends['languageOptions'] = {
|
|
24
33
|
globals: {
|
|
25
34
|
...globals.node,
|
|
@@ -31,7 +40,7 @@ export function defaultConfig(parserOptions?: NonNullable<ConfigWithExtends['lan
|
|
|
31
40
|
defaultProject: 'tsconfig.json',
|
|
32
41
|
},
|
|
33
42
|
tsconfigRootDir: import.meta.dirname,
|
|
34
|
-
...parserOptions,
|
|
43
|
+
...options?.parserOptions,
|
|
35
44
|
},
|
|
36
45
|
};
|
|
37
46
|
|
|
@@ -49,8 +58,9 @@ export function defaultConfig(parserOptions?: NonNullable<ConfigWithExtends['lan
|
|
|
49
58
|
},
|
|
50
59
|
},
|
|
51
60
|
{
|
|
52
|
-
files: ['**/*.ts', '**/*.js', '**/*.cjs', '**/*.mjs'],
|
|
61
|
+
files: ['**/*.ts', '**/*.js', '**/*.cjs', '**/*.mjs', '**/*.tsx'],
|
|
53
62
|
plugins: {
|
|
63
|
+
...extensionConfigs.base.plugins,
|
|
54
64
|
...jsdocConfigs.base.plugins,
|
|
55
65
|
...promiseConfigs.base.plugins,
|
|
56
66
|
...securityConfigs.base.plugins,
|
|
@@ -58,6 +68,7 @@ export function defaultConfig(parserOptions?: NonNullable<ConfigWithExtends['lan
|
|
|
58
68
|
},
|
|
59
69
|
rules: {
|
|
60
70
|
...eslintConfigs.base.rules,
|
|
71
|
+
...(enableRequireExtensionRule ? extensionConfigs.base.rules : {}),
|
|
61
72
|
...jsdocConfigs.base.rules,
|
|
62
73
|
...promiseConfigs.base.rules,
|
|
63
74
|
...securityConfigs.base.rules,
|
|
@@ -79,9 +90,13 @@ export function defaultConfig(parserOptions?: NonNullable<ConfigWithExtends['lan
|
|
|
79
90
|
...eslintConfigs.cjs,
|
|
80
91
|
},
|
|
81
92
|
{
|
|
82
|
-
files: ['**/*.ts'],
|
|
93
|
+
files: ['**/*.ts', '**/*.tsx'],
|
|
83
94
|
...typescriptEslintConfigs.base,
|
|
84
95
|
},
|
|
96
|
+
{
|
|
97
|
+
files: ['**/*.tsx'],
|
|
98
|
+
...reactConfigs.base,
|
|
99
|
+
},
|
|
85
100
|
{
|
|
86
101
|
files: ['**/*.tests.ts', 'tests/tests.ts'],
|
|
87
102
|
|
package/src/react.ts
ADDED
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
import a11y from 'eslint-plugin-jsx-a11y';
|
|
2
|
+
import react from 'eslint-plugin-react';
|
|
3
|
+
import reactHooks from 'eslint-plugin-react-hooks';
|
|
4
|
+
import testingLibrary from 'eslint-plugin-testing-library';
|
|
5
|
+
import type { ConfigWithExtends } from 'typescript-eslint';
|
|
6
|
+
|
|
7
|
+
const base: ConfigWithExtends = {
|
|
8
|
+
plugins: {
|
|
9
|
+
'jsx-a11y': a11y,
|
|
10
|
+
react,
|
|
11
|
+
'react-hooks': reactHooks,
|
|
12
|
+
'testing-library': testingLibrary,
|
|
13
|
+
},
|
|
14
|
+
rules: {
|
|
15
|
+
...a11y.configs.recommended.rules,
|
|
16
|
+
'jsx-a11y/aria-proptypes': 'error',
|
|
17
|
+
'jsx-a11y/aria-role': ['error', { ignoreNonDOM: true }],
|
|
18
|
+
'jsx-a11y/aria-unsupported-elements': 'error',
|
|
19
|
+
'jsx-a11y/role-has-required-aria-props': 'error',
|
|
20
|
+
|
|
21
|
+
...react.configs.recommended.rules,
|
|
22
|
+
'react/default-props-match-prop-types': 'error',
|
|
23
|
+
'react/display-name': ['error', { ignoreTranspilerName: false }],
|
|
24
|
+
'react/forbid-foreign-prop-types': ['error', { allowInPropTypes: true }],
|
|
25
|
+
'react/iframe-missing-sandbox': 'warn',
|
|
26
|
+
'react/jsx-closing-bracket-location': 'error',
|
|
27
|
+
'react/jsx-fragments': 'error',
|
|
28
|
+
'react/jsx-no-leaked-render': ['error', { validStrategies: ['ternary'] }],
|
|
29
|
+
'react/jsx-no-script-url': 'error',
|
|
30
|
+
'react/jsx-no-target-blank': 'error',
|
|
31
|
+
'react/jsx-no-undef': 'error',
|
|
32
|
+
'react/jsx-no-useless-fragment': 'error',
|
|
33
|
+
'react/jsx-pascal-case': [
|
|
34
|
+
'error',
|
|
35
|
+
{
|
|
36
|
+
allowAllCaps: true,
|
|
37
|
+
ignore: [],
|
|
38
|
+
},
|
|
39
|
+
],
|
|
40
|
+
'react/jsx-props-no-multi-spaces': 'error',
|
|
41
|
+
'react/no-access-state-in-setstate': 'error',
|
|
42
|
+
'react/no-arrow-function-lifecycle': 'error',
|
|
43
|
+
'react/no-danger-with-children': 'error',
|
|
44
|
+
'react/no-deprecated': 'error',
|
|
45
|
+
'react/no-did-mount-set-state': 'error',
|
|
46
|
+
'react/no-did-update-set-state': 'error',
|
|
47
|
+
'react/no-direct-mutation-state': 'error',
|
|
48
|
+
'react/no-invalid-html-attribute': 'error',
|
|
49
|
+
'react/no-namespace': 'error',
|
|
50
|
+
'react/no-redundant-should-component-update': 'error',
|
|
51
|
+
'react/no-this-in-sfc': 'error',
|
|
52
|
+
'react/no-typos': 'error',
|
|
53
|
+
'react/no-unsafe': 'error',
|
|
54
|
+
'react/no-unstable-nested-components': 'error',
|
|
55
|
+
'react/no-unused-class-component-methods': 'error',
|
|
56
|
+
'react/no-unused-prop-types': 'error',
|
|
57
|
+
'react/no-unused-state': 'error',
|
|
58
|
+
'react/no-will-update-set-state': 'error',
|
|
59
|
+
'react/prefer-stateless-function': 'error',
|
|
60
|
+
'react/require-render-return': 'error',
|
|
61
|
+
'react/self-closing-comp': 'error',
|
|
62
|
+
'react/style-prop-object': 'error',
|
|
63
|
+
|
|
64
|
+
'react-hooks/rules-of-hooks': 'error',
|
|
65
|
+
'react-hooks/exhaustive-deps': 'error',
|
|
66
|
+
|
|
67
|
+
...testingLibrary.configs.react.rules,
|
|
68
|
+
},
|
|
69
|
+
};
|
|
70
|
+
|
|
71
|
+
export const configs = {
|
|
72
|
+
base,
|
|
73
|
+
};
|
|
74
|
+
|
|
75
|
+
export default {
|
|
76
|
+
configs,
|
|
77
|
+
};
|
|
@@ -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
|
+
};
|