@wordpress/eslint-plugin 24.0.1-next.ba3aee3a2.0 → 24.1.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 +3 -1
- package/configs/custom.js +2 -0
- package/package.json +5 -4
- package/rules/__tests__/components-no-missing-40px-size-prop.js +353 -0
- package/rules/__tests__/components-no-unsafe-button-disabled.js +235 -0
- package/rules/__tests__/dependency-group.js +33 -0
- package/rules/__tests__/no-setting-ds-tokens.js +46 -0
- package/rules/__tests__/no-unknown-ds-tokens.js +60 -0
- package/rules/components-no-missing-40px-size-prop.js +327 -0
- package/rules/components-no-unsafe-button-disabled.js +126 -0
- package/rules/dependency-group.js +64 -1
- package/rules/no-setting-ds-tokens.js +27 -0
- package/rules/no-unknown-ds-tokens.js +101 -0
- package/utils/has-truthy-jsx-attribute.js +50 -0
- package/utils/index.js +2 -0
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
const tokenListModule = require( '@wordpress/theme/design-tokens.js' );
|
|
2
|
+
const tokenList = tokenListModule.default || tokenListModule;
|
|
3
|
+
|
|
4
|
+
const DS_TOKEN_PREFIX = 'wpds-';
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Extracts all unique CSS custom properties (variables) from a given CSS value string,
|
|
8
|
+
* including those in fallback positions, optionally filtering by a specific prefix.
|
|
9
|
+
*
|
|
10
|
+
* @param {string} value - The CSS value string to search for variables.
|
|
11
|
+
* @param {string} [prefix=''] - Optional prefix to filter variables (e.g., 'wpds-').
|
|
12
|
+
* @return {Set<string>} A Set of unique matched CSS variable names (e.g., Set { '--wpds-token' }).
|
|
13
|
+
*
|
|
14
|
+
* @example
|
|
15
|
+
* extractCSSVariables(
|
|
16
|
+
* 'border: 1px solid var(--wpds-border-color, var(--wpds-border-fallback)); ' +
|
|
17
|
+
* 'color: var(--wpds-color-fg, black); ' +
|
|
18
|
+
* 'background: var(--unrelated-bg);',
|
|
19
|
+
* 'wpds'
|
|
20
|
+
* );
|
|
21
|
+
* // → Set { '--wpds-border-color', '--wpds-border-fallback', '--wpds-color-fg' }
|
|
22
|
+
*/
|
|
23
|
+
function extractCSSVariables( value, prefix = '' ) {
|
|
24
|
+
const regex = /--[\w-]+/g;
|
|
25
|
+
const variables = new Set();
|
|
26
|
+
|
|
27
|
+
let match;
|
|
28
|
+
while ( ( match = regex.exec( value ) ) !== null ) {
|
|
29
|
+
const variableName = match[ 0 ];
|
|
30
|
+
if ( variableName.startsWith( `--${ prefix }` ) ) {
|
|
31
|
+
variables.add( variableName );
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
return variables;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
const knownTokens = new Set( tokenList );
|
|
39
|
+
const wpdsTokensRegex = new RegExp( `[^\\w]--${ DS_TOKEN_PREFIX }`, 'i' );
|
|
40
|
+
|
|
41
|
+
module.exports = /** @type {import('eslint').Rule.RuleModule} */ ( {
|
|
42
|
+
meta: {
|
|
43
|
+
type: 'problem',
|
|
44
|
+
docs: {
|
|
45
|
+
description: 'Prevent use of non-existing --wpds-* variables',
|
|
46
|
+
},
|
|
47
|
+
schema: [],
|
|
48
|
+
messages: {
|
|
49
|
+
onlyKnownTokens:
|
|
50
|
+
'The following CSS variables are not valid Design System tokens: {{ tokenNames }}',
|
|
51
|
+
},
|
|
52
|
+
},
|
|
53
|
+
create( context ) {
|
|
54
|
+
const disallowedTokensAST = `JSXAttribute[name.name="style"] :matches(Literal[value=${ wpdsTokensRegex }], TemplateLiteral TemplateElement[value.raw=${ wpdsTokensRegex }])`;
|
|
55
|
+
return {
|
|
56
|
+
/** @param {import('estree').Literal | import('estree').TemplateElement} node */
|
|
57
|
+
[ disallowedTokensAST ]( node ) {
|
|
58
|
+
let computedValue;
|
|
59
|
+
|
|
60
|
+
if ( ! node.value ) {
|
|
61
|
+
return;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
if ( typeof node.value === 'string' ) {
|
|
65
|
+
// Get the node's value when it's a "string"
|
|
66
|
+
computedValue = node.value;
|
|
67
|
+
} else if (
|
|
68
|
+
typeof node.value === 'object' &&
|
|
69
|
+
'raw' in node.value
|
|
70
|
+
) {
|
|
71
|
+
// Get the node's value when it's a `template literal`
|
|
72
|
+
computedValue = node.value.cooked ?? node.value.raw;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
if ( ! computedValue ) {
|
|
76
|
+
return;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
const usedTokens = extractCSSVariables(
|
|
80
|
+
computedValue,
|
|
81
|
+
DS_TOKEN_PREFIX
|
|
82
|
+
);
|
|
83
|
+
const unknownTokens = [ ...usedTokens ].filter(
|
|
84
|
+
( token ) => ! knownTokens.has( token )
|
|
85
|
+
);
|
|
86
|
+
|
|
87
|
+
if ( unknownTokens.length > 0 ) {
|
|
88
|
+
context.report( {
|
|
89
|
+
node,
|
|
90
|
+
messageId: 'onlyKnownTokens',
|
|
91
|
+
data: {
|
|
92
|
+
tokenNames: unknownTokens
|
|
93
|
+
.map( ( token ) => `'${ token }'` )
|
|
94
|
+
.join( ', ' ),
|
|
95
|
+
},
|
|
96
|
+
} );
|
|
97
|
+
}
|
|
98
|
+
},
|
|
99
|
+
};
|
|
100
|
+
},
|
|
101
|
+
} );
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Check if a JSX/React attribute exists and has a truthy value.
|
|
3
|
+
*
|
|
4
|
+
* This utility analyzes JSX attribute nodes from an ESLint AST to determine
|
|
5
|
+
* if a specific attribute is present with a truthy value.
|
|
6
|
+
*
|
|
7
|
+
* Handles the following patterns:
|
|
8
|
+
* - Boolean shorthand: `<Component prop />` → truthy
|
|
9
|
+
* - Explicit true: `<Component prop={true} />` → truthy
|
|
10
|
+
* - Explicit false: `<Component prop={false} />` → NOT truthy
|
|
11
|
+
* - String values: `<Component prop="value" />` → truthy (if non-empty)
|
|
12
|
+
* - Dynamic expressions: `<Component prop={someVar} />` → assumed truthy
|
|
13
|
+
*
|
|
14
|
+
* @param {Array} attributes - Array of JSX attribute nodes from the ESLint AST
|
|
15
|
+
* @param {string} attrName - The attribute name to check
|
|
16
|
+
* @return {boolean} Whether the attribute exists with a truthy value
|
|
17
|
+
*/
|
|
18
|
+
function hasTruthyJsxAttribute( attributes, attrName ) {
|
|
19
|
+
const attr = attributes.find(
|
|
20
|
+
( a ) => a.type === 'JSXAttribute' && a.name && a.name.name === attrName
|
|
21
|
+
);
|
|
22
|
+
|
|
23
|
+
if ( ! attr ) {
|
|
24
|
+
return false;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
// Boolean attribute without value (e.g., `<Button disabled />`)
|
|
28
|
+
if ( attr.value === null ) {
|
|
29
|
+
return true;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
// Expression like `prop={true}` or `prop={false}`
|
|
33
|
+
if (
|
|
34
|
+
attr.value.type === 'JSXExpressionContainer' &&
|
|
35
|
+
attr.value.expression.type === 'Literal'
|
|
36
|
+
) {
|
|
37
|
+
return attr.value.expression.value !== false;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
// String value - truthy if not empty
|
|
41
|
+
if ( attr.value.type === 'Literal' ) {
|
|
42
|
+
return Boolean( attr.value.value );
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
// For any other expression (variables, function calls, etc.),
|
|
46
|
+
// assume it could be truthy since we can't statically analyze it
|
|
47
|
+
return true;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
module.exports = { hasTruthyJsxAttribute };
|
package/utils/index.js
CHANGED
|
@@ -9,6 +9,7 @@ const {
|
|
|
9
9
|
const { getTranslateFunctionArgs } = require( './get-translate-function-args' );
|
|
10
10
|
const { getTextContentFromNode } = require( './get-text-content-from-node' );
|
|
11
11
|
const { getTranslateFunctionName } = require( './get-translate-function-name' );
|
|
12
|
+
const { hasTruthyJsxAttribute } = require( './has-truthy-jsx-attribute' );
|
|
12
13
|
const isPackageInstalled = require( './is-package-installed' );
|
|
13
14
|
|
|
14
15
|
module.exports = {
|
|
@@ -18,5 +19,6 @@ module.exports = {
|
|
|
18
19
|
getTranslateFunctionArgs,
|
|
19
20
|
getTextContentFromNode,
|
|
20
21
|
getTranslateFunctionName,
|
|
22
|
+
hasTruthyJsxAttribute,
|
|
21
23
|
isPackageInstalled,
|
|
22
24
|
};
|