@automattic/eslint-plugin-wpvip 1.2.0 → 1.3.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 +6 -2
- package/configs/index.js +31 -4
- package/configs/javascript.js +12 -1
- package/configs/recommended.js +6 -15
- package/configs/weak-typescript.js +33 -0
- package/index.js +1 -0
- package/init.js +6 -0
- package/package.json +29 -11
- package/utils/load-optional-config.js +30 -0
package/README.md
CHANGED
|
@@ -10,13 +10,15 @@ Install `eslint` and `@automattic/eslint-plugin-wpvip` to your project.
|
|
|
10
10
|
npm install --save-dev eslint @automattic/eslint-plugin-wpvip
|
|
11
11
|
```
|
|
12
12
|
|
|
13
|
+
Optional integrations are auto-detected when your project also installs `typescript`, `jest`, `react`, or `prettier`. These packages are declared as optional peer dependencies so consumers can opt in to the stacks they actually use.
|
|
14
|
+
|
|
13
15
|
## Contributing
|
|
14
16
|
|
|
15
17
|
See [CONTRIBUTING.md](https://github.com/Automattic/eslint-config-wpvip/blob/trunk/CONTRIBUTING.md) for details on development, testing, publishing, etc.
|
|
16
18
|
|
|
17
19
|
## Configuration
|
|
18
20
|
|
|
19
|
-
Create an `.eslintrc.js` file.
|
|
21
|
+
Create an `.eslintrc.js` file. If you use the `recommended` preset, you can preload the optional integrations with `init`.
|
|
20
22
|
|
|
21
23
|
```js
|
|
22
24
|
require( '@automattic/eslint-plugin-wpvip/init' );
|
|
@@ -29,6 +31,8 @@ module.exports = {
|
|
|
29
31
|
|
|
30
32
|
And that's it! It works automatically with most Babel and TypeScript projects. Code editors that are configured to work with ESLint will automatically pick up the rules and flag any errors or warnings.
|
|
31
33
|
|
|
34
|
+
If your project uses only JavaScript, you do not need to install the optional peers. If you use the modular `typescript`, `testing`, `react`, or `prettier` configs directly, install the corresponding package in your project first.
|
|
35
|
+
|
|
32
36
|
You may also wish to define an `.eslintignore` file if there are files or paths that you do not want to lint.
|
|
33
37
|
|
|
34
38
|
Package scripts can be useful to run linting and formatting commands automatically. Here are some suggested scripts for your project's `package.json`—only copy the ones that are useful to you. The `cmd:` scripts help you compose commands without repeating verbose CLI arguments.
|
|
@@ -59,8 +63,8 @@ Of course, this recommended config may not be ideal for every project, so feel f
|
|
|
59
63
|
module.exports = {
|
|
60
64
|
extends: [
|
|
61
65
|
'plugin:@automattic/wpvip/javascript',
|
|
62
|
-
'plugin:@automattic/wpvip/typescript', // when "typescript" is installed
|
|
63
66
|
'plugin:@automattic/wpvip/formatting',
|
|
67
|
+
'plugin:@automattic/wpvip/typescript', // when "typescript" is installed
|
|
64
68
|
'plugin:@automattic/wpvip/testing', // when "jest" is installed
|
|
65
69
|
'plugin:@automattic/wpvip/react', // when "react" is installed
|
|
66
70
|
'plugin:@automattic/wpvip/prettier', // when "prettier" is installed
|
package/configs/index.js
CHANGED
|
@@ -5,14 +5,41 @@ const configs = {
|
|
|
5
5
|
formatting: require( './formatting' ),
|
|
6
6
|
javascript: require( './javascript' ),
|
|
7
7
|
jsdoc: require( './jsdoc' ),
|
|
8
|
-
prettier: require( './prettier' ),
|
|
9
|
-
react: require( './react' ),
|
|
10
8
|
recommended: require( './recommended' ),
|
|
11
|
-
testing: require( './testing' ),
|
|
12
|
-
typescript: require( './typescript' ),
|
|
13
9
|
'weak-javascript': require( './weak-javascript' ),
|
|
14
10
|
'weak-testing': require( './weak-testing' ),
|
|
15
11
|
'weak-typescript': require( './weak-typescript' ),
|
|
16
12
|
};
|
|
17
13
|
|
|
14
|
+
// Optional configs are lazy-loaded to avoid MODULE_NOT_FOUND crashes when
|
|
15
|
+
// optional peers (typescript, react, jest, prettier) are not installed.
|
|
16
|
+
// These are only accessed when explicitly requested (e.g., via direct extends)
|
|
17
|
+
// or via recommended config, which guards their loading via loadOptionalConfig.
|
|
18
|
+
const optionalConfigs = [ 'prettier', 'react', 'testing', 'typescript' ];
|
|
19
|
+
|
|
20
|
+
function requireOptionalConfig( name ) {
|
|
21
|
+
switch ( name ) {
|
|
22
|
+
case 'prettier':
|
|
23
|
+
return require( './prettier' );
|
|
24
|
+
case 'react':
|
|
25
|
+
return require( './react' );
|
|
26
|
+
case 'testing':
|
|
27
|
+
return require( './testing' );
|
|
28
|
+
case 'typescript':
|
|
29
|
+
return require( './typescript' );
|
|
30
|
+
default:
|
|
31
|
+
throw new Error( `Unknown optional config: ${ name }` );
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
for ( const name of optionalConfigs ) {
|
|
36
|
+
Object.defineProperty( configs, name, {
|
|
37
|
+
enumerable: true,
|
|
38
|
+
configurable: true,
|
|
39
|
+
get() {
|
|
40
|
+
return requireOptionalConfig( name );
|
|
41
|
+
},
|
|
42
|
+
} );
|
|
43
|
+
}
|
|
44
|
+
|
|
18
45
|
module.exports = configs;
|
package/configs/javascript.js
CHANGED
|
@@ -14,6 +14,15 @@ const SecurityPluginConfigs = require( 'eslint-plugin-security' );
|
|
|
14
14
|
const UnusedImportsPlugin = require( 'eslint-plugin-unused-imports' );
|
|
15
15
|
const globals = require( 'globals' );
|
|
16
16
|
|
|
17
|
+
// Resolve the TypeScript resolver from this package's perspective so that
|
|
18
|
+
// eslint-plugin-import can load it regardless of how the consuming project
|
|
19
|
+
// hoists its dependencies. Passing the absolute path as the resolver name
|
|
20
|
+
// bypasses `eslint-plugin-import`'s name-based lookup, which otherwise tries
|
|
21
|
+
// to resolve `eslint-import-resolver-typescript` from each linted source
|
|
22
|
+
// file's path and silently falls back to requiring `typescript` (the
|
|
23
|
+
// compiler) when the resolver is nested under this plugin's node_modules.
|
|
24
|
+
const typescriptResolverPath = require.resolve( 'eslint-import-resolver-typescript' );
|
|
25
|
+
|
|
17
26
|
/** @type import('eslint').Linter.Config[] */
|
|
18
27
|
module.exports = [
|
|
19
28
|
JsonPlugin.configs.recommended,
|
|
@@ -273,8 +282,10 @@ module.exports = [
|
|
|
273
282
|
node: {
|
|
274
283
|
extensions: [ '.js', '.jsx', '.ts', '.tsx', '.cjs', '.mjs', '.cts', '.mts' ],
|
|
275
284
|
},
|
|
276
|
-
|
|
285
|
+
[ typescriptResolverPath ]: {},
|
|
277
286
|
},
|
|
278
287
|
},
|
|
279
288
|
},
|
|
280
289
|
];
|
|
290
|
+
|
|
291
|
+
module.exports.typescriptResolverPath = typescriptResolverPath;
|
package/configs/recommended.js
CHANGED
|
@@ -1,22 +1,13 @@
|
|
|
1
|
-
const
|
|
1
|
+
const loadOptionalConfig = require( '../utils/load-optional-config' );
|
|
2
2
|
|
|
3
3
|
/** @type import('eslint').Linter.Config[] */
|
|
4
4
|
const configs = [ ...require( './javascript' ), ...require( './formatting' ) ];
|
|
5
5
|
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
6
|
+
configs.push( ...loadOptionalConfig( 'typescript', () => require( './typescript' ) ) );
|
|
7
|
+
configs.push( ...loadOptionalConfig( 'jest', () => require( './testing' ) ) );
|
|
8
|
+
configs.push( ...loadOptionalConfig( 'react', () => require( './react' ) ) );
|
|
9
|
+
configs.push( ...loadOptionalConfig( 'prettier', () => require( './prettier' ) ) );
|
|
9
10
|
|
|
10
|
-
|
|
11
|
-
configs.push( ...require( './testing' ) );
|
|
12
|
-
}
|
|
13
|
-
|
|
14
|
-
if ( isPackageInstalled( 'react' ) ) {
|
|
15
|
-
configs.push( ...require( './react' ) );
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
if ( isPackageInstalled( 'prettier' ) ) {
|
|
19
|
-
configs.push( ...require( './prettier' ) );
|
|
20
|
-
}
|
|
11
|
+
configs.typescriptResolverPath = require( './javascript' ).typescriptResolverPath;
|
|
21
12
|
|
|
22
13
|
module.exports = configs;
|
|
@@ -16,14 +16,47 @@ module.exports = [
|
|
|
16
16
|
* to "off"). If a rule is already set to a warning, do not disable it.
|
|
17
17
|
*/
|
|
18
18
|
rules: {
|
|
19
|
+
'@typescript-eslint/await-thenable': 'warn',
|
|
20
|
+
'@typescript-eslint/ban-ts-comment': 'warn',
|
|
21
|
+
'@typescript-eslint/default-param-last': 'warn',
|
|
22
|
+
'@typescript-eslint/explicit-member-accessibility': [
|
|
23
|
+
'warn',
|
|
24
|
+
{ overrides: { constructors: 'off' } },
|
|
25
|
+
],
|
|
26
|
+
'@typescript-eslint/no-base-to-string': 'warn',
|
|
27
|
+
'@typescript-eslint/no-duplicate-type-constituents': 'warn',
|
|
28
|
+
'@typescript-eslint/no-dynamic-delete': 'warn',
|
|
29
|
+
'@typescript-eslint/no-empty-object-type': 'warn',
|
|
19
30
|
'@typescript-eslint/no-explicit-any': 'warn',
|
|
20
31
|
'@typescript-eslint/no-floating-promises': 'warn',
|
|
32
|
+
'@typescript-eslint/no-namespace': 'warn',
|
|
33
|
+
'@typescript-eslint/no-non-null-assertion': 'warn',
|
|
21
34
|
'@typescript-eslint/no-misused-promises': 'warn',
|
|
35
|
+
'@typescript-eslint/no-invalid-void-type': [ 'warn', { allowAsThisParameter: true } ],
|
|
36
|
+
'@typescript-eslint/no-redundant-type-constituents': 'warn',
|
|
37
|
+
'@typescript-eslint/no-require-imports': 'warn',
|
|
38
|
+
'@typescript-eslint/no-this-alias': 'warn',
|
|
22
39
|
'@typescript-eslint/no-unsafe-argument': 'warn',
|
|
23
40
|
'@typescript-eslint/no-unsafe-assignment': 'warn',
|
|
24
41
|
'@typescript-eslint/no-unsafe-call': 'warn',
|
|
42
|
+
'@typescript-eslint/no-unsafe-enum-comparison': 'warn',
|
|
43
|
+
'@typescript-eslint/no-unsafe-function-type': 'warn',
|
|
25
44
|
'@typescript-eslint/no-unsafe-member-access': 'warn',
|
|
26
45
|
'@typescript-eslint/no-unsafe-return': 'warn',
|
|
46
|
+
'no-unused-vars': 'off',
|
|
47
|
+
'@typescript-eslint/no-unused-vars': [
|
|
48
|
+
'warn',
|
|
49
|
+
{
|
|
50
|
+
argsIgnorePattern: '^_',
|
|
51
|
+
caughtErrorsIgnorePattern: '^_',
|
|
52
|
+
destructuredArrayIgnorePattern: '^_',
|
|
53
|
+
ignoreRestSiblings: true,
|
|
54
|
+
varsIgnorePattern: '^_',
|
|
55
|
+
},
|
|
56
|
+
],
|
|
57
|
+
'@typescript-eslint/no-wrapper-object-types': 'warn',
|
|
58
|
+
'@typescript-eslint/only-throw-error': 'warn',
|
|
59
|
+
'@typescript-eslint/prefer-promise-reject-errors': 'warn',
|
|
27
60
|
'@typescript-eslint/require-await': 'warn',
|
|
28
61
|
'@typescript-eslint/restrict-plus-operands': 'warn',
|
|
29
62
|
'@typescript-eslint/restrict-template-expressions': 'warn',
|
package/index.js
CHANGED
package/init.js
ADDED
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
const loadOptionalConfig = require( './utils/load-optional-config' );
|
|
2
|
+
|
|
3
|
+
loadOptionalConfig( 'typescript', () => require( './configs/typescript' ) );
|
|
4
|
+
loadOptionalConfig( 'jest', () => require( './configs/testing' ) );
|
|
5
|
+
loadOptionalConfig( 'react', () => require( './configs/react' ) );
|
|
6
|
+
loadOptionalConfig( 'prettier', () => require( './configs/prettier' ) );
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@automattic/eslint-plugin-wpvip",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.3.0",
|
|
4
4
|
"description": "ESLint plugin for internal WordPress VIP projects",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"scripts": {
|
|
@@ -38,32 +38,50 @@
|
|
|
38
38
|
"eslint-import-resolver-typescript": "^4.4.4",
|
|
39
39
|
"eslint-plugin-import": "^2.32.0",
|
|
40
40
|
"eslint-plugin-jest": "^29.12.2",
|
|
41
|
-
"eslint-plugin-jsdoc": "^
|
|
41
|
+
"eslint-plugin-jsdoc": "^63.0.0",
|
|
42
42
|
"eslint-plugin-json": "^4.0.1",
|
|
43
43
|
"eslint-plugin-jsx-a11y": "^6.10.2",
|
|
44
44
|
"eslint-plugin-prettier": "^5.5.5",
|
|
45
45
|
"eslint-plugin-promise": "^7.2.1",
|
|
46
46
|
"eslint-plugin-react": "^7.37.5",
|
|
47
47
|
"eslint-plugin-react-hooks": "^7.0.1",
|
|
48
|
-
"eslint-plugin-security": "^
|
|
48
|
+
"eslint-plugin-security": "^4.0.0",
|
|
49
49
|
"eslint-plugin-unused-imports": "^4.3.0",
|
|
50
50
|
"find-package-json": "^1.2.0",
|
|
51
51
|
"globals": "^16.0.0 || ^17.0.0",
|
|
52
52
|
"typescript-eslint": "^8.48.1"
|
|
53
53
|
},
|
|
54
54
|
"peerDependencies": {
|
|
55
|
-
"eslint": "^9.7.0"
|
|
55
|
+
"eslint": "^9.7.0",
|
|
56
|
+
"jest": "^29.0.0 || ^30.0.0",
|
|
57
|
+
"prettier": "^3.0.0",
|
|
58
|
+
"react": "^18.0.0 || ^19.0.0",
|
|
59
|
+
"typescript": "^5.0.0 || ^6.0.0"
|
|
60
|
+
},
|
|
61
|
+
"peerDependenciesMeta": {
|
|
62
|
+
"jest": {
|
|
63
|
+
"optional": true
|
|
64
|
+
},
|
|
65
|
+
"prettier": {
|
|
66
|
+
"optional": true
|
|
67
|
+
},
|
|
68
|
+
"react": {
|
|
69
|
+
"optional": true
|
|
70
|
+
},
|
|
71
|
+
"typescript": {
|
|
72
|
+
"optional": true
|
|
73
|
+
}
|
|
56
74
|
},
|
|
57
75
|
"devDependencies": {
|
|
58
76
|
"@tsconfig/node20": "^20.1.8",
|
|
59
|
-
"@types/eslint": "9.6.1",
|
|
60
|
-
"@types/jest": "30.0.0",
|
|
61
|
-
"@types/react": "19.2.7",
|
|
77
|
+
"@types/eslint": "^9.6.1",
|
|
78
|
+
"@types/jest": "^30.0.0",
|
|
79
|
+
"@types/react": "~19.2.7",
|
|
62
80
|
"eslint": "^9.39.1",
|
|
63
|
-
"jest": "30.2
|
|
81
|
+
"jest": "^30.4.2",
|
|
64
82
|
"prettier": "npm:wp-prettier@3.0.3",
|
|
65
|
-
"react": "19.2.3",
|
|
66
|
-
"ts-jest": "29.4.6",
|
|
67
|
-
"typescript": "
|
|
83
|
+
"react": "~19.2.3",
|
|
84
|
+
"ts-jest": "^29.4.6",
|
|
85
|
+
"typescript": "^6.0.3"
|
|
68
86
|
}
|
|
69
87
|
}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
const debugLog = require( './debug-log' );
|
|
2
|
+
const isPackageInstalled = require( './is-package-installed' );
|
|
3
|
+
|
|
4
|
+
module.exports = function loadOptionalConfig( packageName, loadConfig ) {
|
|
5
|
+
if ( ! isPackageInstalled( packageName ) ) {
|
|
6
|
+
return [];
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
try {
|
|
10
|
+
return loadConfig();
|
|
11
|
+
} catch ( error ) {
|
|
12
|
+
if ( 'MODULE_NOT_FOUND' !== error?.code ) {
|
|
13
|
+
throw error;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
// Only swallow the error if the missing module is the optional peer itself.
|
|
17
|
+
// This prevents masking errors from transitive dependencies or unrelated modules.
|
|
18
|
+
const missingModule = error.message?.match( /Cannot find module '([^']+)'/ )?.[ 1 ] || '';
|
|
19
|
+
const isOptionalPeerMissing =
|
|
20
|
+
missingModule === packageName || missingModule.startsWith( `${ packageName }/` );
|
|
21
|
+
|
|
22
|
+
if ( ! isOptionalPeerMissing ) {
|
|
23
|
+
throw error;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
debugLog( `Skipping optional config for ${ packageName }: ${ error.message }` );
|
|
27
|
+
|
|
28
|
+
return [];
|
|
29
|
+
}
|
|
30
|
+
};
|