@wordpress/eslint-plugin 25.0.1-next.v.202604201441.0 → 25.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 +2 -0
- package/configs/babel-parser-compat.js +1 -3
- package/configs/custom.js +1 -0
- package/package.json +5 -5
- package/rules/__tests__/no-setting-ds-tokens.js +52 -0
- package/rules/__tests__/no-unknown-ds-tokens.js +54 -0
- package/rules/__tests__/no-unsafe-render-order.js +136 -0
- package/rules/__tests__/use-import-as.js +271 -0
- package/rules/__tests__/use-recommended-components.js +3 -1
- package/rules/no-ds-tokens.js +1 -3
- package/rules/no-setting-ds-tokens.js +57 -0
- package/rules/no-unknown-ds-tokens.js +86 -120
- package/rules/no-unsafe-render-order.js +158 -0
- package/rules/no-unsafe-wp-apis.js +1 -1
- package/rules/use-import-as.js +310 -0
- package/rules/use-recommended-components.js +21 -1
- package/utils/ds-token-utils.js +58 -0
|
@@ -1,3 +1,19 @@
|
|
|
1
|
+
const {
|
|
2
|
+
DS_TOKEN_PREFIX,
|
|
3
|
+
collectTokenOccurrences,
|
|
4
|
+
getStaticNodeValue,
|
|
5
|
+
wpdsTokensRegex,
|
|
6
|
+
} = require( '../utils/ds-token-utils' );
|
|
7
|
+
|
|
8
|
+
const wpdsDeclarationRegex = new RegExp(
|
|
9
|
+
`(?:^|[^\\w])--${ DS_TOKEN_PREFIX }[\\w-]+\\s*:`,
|
|
10
|
+
'i'
|
|
11
|
+
);
|
|
12
|
+
const dynamicDeclarationStartRegex = new RegExp(
|
|
13
|
+
`--${ DS_TOKEN_PREFIX }[\\w-]*$`
|
|
14
|
+
);
|
|
15
|
+
const dynamicDeclarationEndRegex = /^[\w-]*\s*:/;
|
|
16
|
+
|
|
1
17
|
module.exports = /** @type {import('eslint').Rule.RuleModule} */ ( {
|
|
2
18
|
meta: {
|
|
3
19
|
type: 'problem',
|
|
@@ -12,6 +28,9 @@ module.exports = /** @type {import('eslint').Rule.RuleModule} */ ( {
|
|
|
12
28
|
},
|
|
13
29
|
},
|
|
14
30
|
create( context ) {
|
|
31
|
+
const staticDeclarationAST = `:matches(Literal[value=${ wpdsDeclarationRegex }], TemplateLiteral[expressions.length=0] TemplateElement[value.raw=${ wpdsDeclarationRegex }])`;
|
|
32
|
+
const dynamicTemplateLiteralAST = `TemplateLiteral[expressions.length>0]:has(TemplateElement[value.raw=${ wpdsTokensRegex }])`;
|
|
33
|
+
|
|
15
34
|
return {
|
|
16
35
|
/** @param {import('estree').Property} node */
|
|
17
36
|
'ObjectExpression > Property[key.value=/^--wpds-/]'( node ) {
|
|
@@ -20,6 +39,44 @@ module.exports = /** @type {import('eslint').Rule.RuleModule} */ ( {
|
|
|
20
39
|
messageId: 'disallowedSet',
|
|
21
40
|
} );
|
|
22
41
|
},
|
|
42
|
+
/** @param {import('estree').Literal | import('estree').TemplateElement} node */
|
|
43
|
+
[ staticDeclarationAST ]( node ) {
|
|
44
|
+
context.report( {
|
|
45
|
+
node,
|
|
46
|
+
messageId: 'disallowedSet',
|
|
47
|
+
} );
|
|
48
|
+
},
|
|
49
|
+
/** @param {import('estree').TemplateLiteral} node */
|
|
50
|
+
[ dynamicTemplateLiteralAST ]( node ) {
|
|
51
|
+
for ( let index = 0; index < node.quasis.length; index++ ) {
|
|
52
|
+
const quasi = node.quasis[ index ];
|
|
53
|
+
const value = getStaticNodeValue( quasi );
|
|
54
|
+
const nextValue = node.quasis[ index + 1 ]
|
|
55
|
+
? getStaticNodeValue( node.quasis[ index + 1 ] )
|
|
56
|
+
: undefined;
|
|
57
|
+
|
|
58
|
+
if ( ! value ) {
|
|
59
|
+
continue;
|
|
60
|
+
}
|
|
61
|
+
const hasStaticDeclaration = collectTokenOccurrences(
|
|
62
|
+
value,
|
|
63
|
+
DS_TOKEN_PREFIX
|
|
64
|
+
).some( ( { declaration } ) => declaration );
|
|
65
|
+
const hasSplitDeclaration =
|
|
66
|
+
! quasi.tail &&
|
|
67
|
+
dynamicDeclarationStartRegex.test( value ) &&
|
|
68
|
+
nextValue &&
|
|
69
|
+
dynamicDeclarationEndRegex.test( nextValue );
|
|
70
|
+
|
|
71
|
+
if ( hasStaticDeclaration || hasSplitDeclaration ) {
|
|
72
|
+
context.report( {
|
|
73
|
+
node,
|
|
74
|
+
messageId: 'disallowedSet',
|
|
75
|
+
} );
|
|
76
|
+
return;
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
},
|
|
23
80
|
};
|
|
24
81
|
},
|
|
25
82
|
} );
|
|
@@ -1,48 +1,69 @@
|
|
|
1
1
|
const tokenListModule = require( '@wordpress/theme/design-tokens.js' );
|
|
2
2
|
const tokenList = tokenListModule.default || tokenListModule;
|
|
3
3
|
|
|
4
|
-
const
|
|
4
|
+
const {
|
|
5
|
+
DS_TOKEN_PREFIX,
|
|
6
|
+
collectTokenOccurrences,
|
|
7
|
+
getStaticNodeValue,
|
|
8
|
+
wpdsTokensRegex,
|
|
9
|
+
} = require( '../utils/ds-token-utils' );
|
|
10
|
+
|
|
11
|
+
const knownTokens = new Set( tokenList );
|
|
5
12
|
|
|
6
13
|
/**
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
* @param {string} value - The CSS value string to search.
|
|
11
|
-
* @param {string} [prefix=''] - Optional prefix to filter variables (e.g., 'wpds-').
|
|
12
|
-
* @return {{ tokens: Set<string>, bare: Set<string> }}
|
|
13
|
-
* `tokens` — every unique matched token;
|
|
14
|
-
* `bare` — the subset that appeared at least once without a `var()` wrapper.
|
|
15
|
-
*
|
|
16
|
-
* @example
|
|
17
|
-
* classifyTokens(
|
|
18
|
-
* 'var(--wpds-color-fg) --wpds-color-bg',
|
|
19
|
-
* 'wpds-'
|
|
20
|
-
* );
|
|
21
|
-
* // → { tokens: Set {'--wpds-color-fg','--wpds-color-bg'},
|
|
22
|
-
* // bare: Set {'--wpds-color-bg'} }
|
|
14
|
+
* @param {Array<{ token: string, bare: boolean, declaration: boolean }>} occurrences
|
|
15
|
+
* @param {{ includeBareTokens?: boolean }} [options]
|
|
23
16
|
*/
|
|
24
|
-
function
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
);
|
|
29
|
-
const
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
17
|
+
function getInvalidTokenNames(
|
|
18
|
+
occurrences,
|
|
19
|
+
{ includeBareTokens = true } = {}
|
|
20
|
+
) {
|
|
21
|
+
const unknownTokens = new Set();
|
|
22
|
+
const bareTokens = new Set();
|
|
23
|
+
|
|
24
|
+
for ( const { token, bare, declaration } of occurrences ) {
|
|
25
|
+
if ( ! knownTokens.has( token ) ) {
|
|
26
|
+
unknownTokens.add( token );
|
|
27
|
+
continue;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
if ( includeBareTokens && bare && ! declaration ) {
|
|
31
|
+
bareTokens.add( token );
|
|
38
32
|
}
|
|
39
33
|
}
|
|
40
34
|
|
|
41
|
-
return {
|
|
35
|
+
return {
|
|
36
|
+
unknownTokens: [ ...unknownTokens ],
|
|
37
|
+
bareTokens: [ ...bareTokens ],
|
|
38
|
+
};
|
|
42
39
|
}
|
|
43
40
|
|
|
44
|
-
|
|
45
|
-
|
|
41
|
+
/**
|
|
42
|
+
* @param {string[]} tokenNames
|
|
43
|
+
*/
|
|
44
|
+
function formatTokenNames( tokenNames ) {
|
|
45
|
+
return tokenNames.map( ( token ) => `'${ token }'` ).join( ', ' );
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* @param {import('eslint').Rule.RuleContext} context
|
|
50
|
+
* @param {import('estree').Node} node
|
|
51
|
+
* @param {'onlyKnownTokens' | 'bareToken'} messageId
|
|
52
|
+
* @param {string[]} tokenNames
|
|
53
|
+
*/
|
|
54
|
+
function reportTokenNames( context, node, messageId, tokenNames ) {
|
|
55
|
+
if ( tokenNames.length === 0 ) {
|
|
56
|
+
return;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
context.report( {
|
|
60
|
+
node,
|
|
61
|
+
messageId,
|
|
62
|
+
data: {
|
|
63
|
+
tokenNames: formatTokenNames( tokenNames ),
|
|
64
|
+
},
|
|
65
|
+
} );
|
|
66
|
+
}
|
|
46
67
|
|
|
47
68
|
module.exports = /** @type {import('eslint').Rule.RuleModule} */ ( {
|
|
48
69
|
meta: {
|
|
@@ -78,8 +99,7 @@ module.exports = /** @type {import('eslint').Rule.RuleModule} */ ( {
|
|
|
78
99
|
*/
|
|
79
100
|
[ dynamicTemplateLiteralAST ]( node ) {
|
|
80
101
|
let hasDynamic = false;
|
|
81
|
-
const
|
|
82
|
-
const bareTokens = [];
|
|
102
|
+
const occurrences = [];
|
|
83
103
|
|
|
84
104
|
for ( const quasi of node.quasis ) {
|
|
85
105
|
const raw = quasi.value.raw;
|
|
@@ -93,30 +113,26 @@ module.exports = /** @type {import('eslint').Rule.RuleModule} */ ( {
|
|
|
93
113
|
hasDynamic = true;
|
|
94
114
|
}
|
|
95
115
|
|
|
96
|
-
|
|
116
|
+
let quasiOccurrences = collectTokenOccurrences(
|
|
97
117
|
value,
|
|
98
118
|
DS_TOKEN_PREFIX
|
|
99
119
|
);
|
|
100
120
|
|
|
101
|
-
// Remove the trailing incomplete token — it's the one
|
|
102
|
-
// being dynamically constructed by the next expression.
|
|
103
121
|
if ( isFollowedByExpression ) {
|
|
104
122
|
const endMatch = value.match( /(--([\w-]+))$/ );
|
|
105
123
|
if ( endMatch ) {
|
|
106
|
-
|
|
107
|
-
|
|
124
|
+
quasiOccurrences = quasiOccurrences.filter(
|
|
125
|
+
( { token } ) => token !== endMatch[ 1 ]
|
|
126
|
+
);
|
|
108
127
|
}
|
|
109
128
|
}
|
|
110
129
|
|
|
111
|
-
|
|
112
|
-
if ( ! knownTokens.has( token ) ) {
|
|
113
|
-
unknownTokens.push( token );
|
|
114
|
-
} else if ( bare.has( token ) ) {
|
|
115
|
-
bareTokens.push( token );
|
|
116
|
-
}
|
|
117
|
-
}
|
|
130
|
+
occurrences.push( ...quasiOccurrences );
|
|
118
131
|
}
|
|
119
132
|
|
|
133
|
+
const { unknownTokens, bareTokens } =
|
|
134
|
+
getInvalidTokenNames( occurrences );
|
|
135
|
+
|
|
120
136
|
if ( hasDynamic ) {
|
|
121
137
|
context.report( {
|
|
122
138
|
node,
|
|
@@ -124,95 +140,45 @@ module.exports = /** @type {import('eslint').Rule.RuleModule} */ ( {
|
|
|
124
140
|
} );
|
|
125
141
|
}
|
|
126
142
|
|
|
127
|
-
|
|
128
|
-
context
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
.join( ', ' ),
|
|
135
|
-
},
|
|
136
|
-
} );
|
|
137
|
-
}
|
|
138
|
-
|
|
139
|
-
if ( bareTokens.length > 0 ) {
|
|
140
|
-
context.report( {
|
|
141
|
-
node,
|
|
142
|
-
messageId: 'bareToken',
|
|
143
|
-
data: {
|
|
144
|
-
tokenNames: bareTokens
|
|
145
|
-
.map( ( token ) => `'${ token }'` )
|
|
146
|
-
.join( ', ' ),
|
|
147
|
-
},
|
|
148
|
-
} );
|
|
149
|
-
}
|
|
143
|
+
reportTokenNames(
|
|
144
|
+
context,
|
|
145
|
+
node,
|
|
146
|
+
'onlyKnownTokens',
|
|
147
|
+
unknownTokens
|
|
148
|
+
);
|
|
149
|
+
reportTokenNames( context, node, 'bareToken', bareTokens );
|
|
150
150
|
},
|
|
151
151
|
/** @param {import('estree').Literal | import('estree').TemplateElement} node */
|
|
152
152
|
[ staticTokensAST ]( node ) {
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
if ( ! node.value ) {
|
|
156
|
-
return;
|
|
157
|
-
}
|
|
158
|
-
|
|
159
|
-
if ( typeof node.value === 'string' ) {
|
|
160
|
-
computedValue = node.value;
|
|
161
|
-
} else if (
|
|
162
|
-
typeof node.value === 'object' &&
|
|
163
|
-
'raw' in node.value
|
|
164
|
-
) {
|
|
165
|
-
computedValue = node.value.cooked ?? node.value.raw;
|
|
166
|
-
}
|
|
153
|
+
const computedValue = getStaticNodeValue( node );
|
|
167
154
|
|
|
168
155
|
if ( ! computedValue ) {
|
|
169
156
|
return;
|
|
170
157
|
}
|
|
171
158
|
|
|
172
|
-
const
|
|
159
|
+
const occurrences = collectTokenOccurrences(
|
|
173
160
|
computedValue,
|
|
174
161
|
DS_TOKEN_PREFIX
|
|
175
162
|
);
|
|
176
|
-
const unknownTokens = [ ...usedTokens ].filter(
|
|
177
|
-
( token ) => ! knownTokens.has( token )
|
|
178
|
-
);
|
|
179
|
-
|
|
180
|
-
if ( unknownTokens.length > 0 ) {
|
|
181
|
-
context.report( {
|
|
182
|
-
node,
|
|
183
|
-
messageId: 'onlyKnownTokens',
|
|
184
|
-
data: {
|
|
185
|
-
tokenNames: unknownTokens
|
|
186
|
-
.map( ( token ) => `'${ token }'` )
|
|
187
|
-
.join( ', ' ),
|
|
188
|
-
},
|
|
189
|
-
} );
|
|
190
|
-
}
|
|
191
|
-
|
|
192
163
|
// Skip bare-token check for property keys
|
|
193
164
|
// (e.g. `{ '--wpds-token': value }` declaring a custom property).
|
|
194
165
|
const isPropertyKey =
|
|
195
166
|
node.parent?.type === 'Property' &&
|
|
196
167
|
node.parent.key === node;
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
knownTokens.has( token ) && bare.has( token )
|
|
202
|
-
);
|
|
203
|
-
|
|
204
|
-
if ( bareTokens.length > 0 ) {
|
|
205
|
-
context.report( {
|
|
206
|
-
node,
|
|
207
|
-
messageId: 'bareToken',
|
|
208
|
-
data: {
|
|
209
|
-
tokenNames: bareTokens
|
|
210
|
-
.map( ( token ) => `'${ token }'` )
|
|
211
|
-
.join( ', ' ),
|
|
212
|
-
},
|
|
213
|
-
} );
|
|
168
|
+
const { unknownTokens, bareTokens } = getInvalidTokenNames(
|
|
169
|
+
occurrences,
|
|
170
|
+
{
|
|
171
|
+
includeBareTokens: ! isPropertyKey,
|
|
214
172
|
}
|
|
215
|
-
|
|
173
|
+
);
|
|
174
|
+
|
|
175
|
+
reportTokenNames(
|
|
176
|
+
context,
|
|
177
|
+
node,
|
|
178
|
+
'onlyKnownTokens',
|
|
179
|
+
unknownTokens
|
|
180
|
+
);
|
|
181
|
+
reportTokenNames( context, node, 'bareToken', bareTokens );
|
|
216
182
|
},
|
|
217
183
|
};
|
|
218
184
|
},
|
|
@@ -0,0 +1,158 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Components tracked by this rule.
|
|
3
|
+
*
|
|
4
|
+
* @type {Set<string>}
|
|
5
|
+
*/
|
|
6
|
+
const TRACKED_COMPONENTS = new Set( [ 'Link', 'Text', 'VisuallyHidden' ] );
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* @type {import('eslint').Rule.RuleModule}
|
|
10
|
+
*/
|
|
11
|
+
module.exports = {
|
|
12
|
+
meta: {
|
|
13
|
+
type: 'problem',
|
|
14
|
+
docs: {
|
|
15
|
+
description:
|
|
16
|
+
'Prevent render-prop composition orders that silently remove semantics.',
|
|
17
|
+
url: 'https://github.com/WordPress/gutenberg/blob/HEAD/packages/eslint-plugin/docs/rules/no-unsafe-render-order.md',
|
|
18
|
+
},
|
|
19
|
+
schema: [
|
|
20
|
+
{
|
|
21
|
+
type: 'object',
|
|
22
|
+
properties: {
|
|
23
|
+
checkLocalImports: {
|
|
24
|
+
type: 'boolean',
|
|
25
|
+
description:
|
|
26
|
+
'When true, also checks tracked components imported from relative paths.',
|
|
27
|
+
},
|
|
28
|
+
},
|
|
29
|
+
additionalProperties: false,
|
|
30
|
+
},
|
|
31
|
+
],
|
|
32
|
+
messages: {
|
|
33
|
+
visuallyHiddenOrder:
|
|
34
|
+
'Do not pass `VisuallyHidden` via `render`. Make `VisuallyHidden` the outer component instead.',
|
|
35
|
+
linkTextOrder:
|
|
36
|
+
'Use `Text` as the outer component and pass `Link` via `render` so the resulting element stays an `<a>`.',
|
|
37
|
+
},
|
|
38
|
+
},
|
|
39
|
+
|
|
40
|
+
create( context ) {
|
|
41
|
+
const checkLocalImports =
|
|
42
|
+
context.options[ 0 ]?.checkLocalImports ?? false;
|
|
43
|
+
const trackedImports = new Map();
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* @param {string} source
|
|
47
|
+
* @return {boolean} Whether the import should be tracked.
|
|
48
|
+
*/
|
|
49
|
+
function shouldTrackImportSource( source ) {
|
|
50
|
+
if ( source === '@wordpress/ui' ) {
|
|
51
|
+
return true;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
if ( checkLocalImports ) {
|
|
55
|
+
return source.startsWith( '.' ) || source.startsWith( '/' );
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
return false;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* @param {import('estree-jsx').JSXIdentifier|import('estree-jsx').JSXMemberExpression} node
|
|
63
|
+
* @return {string|null} Tracked component name or null.
|
|
64
|
+
*/
|
|
65
|
+
function resolveTrackedJsxName( node ) {
|
|
66
|
+
if ( node.type !== 'JSXIdentifier' ) {
|
|
67
|
+
return null;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
return trackedImports.get( node.name ) ?? null;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* @param {Array} attributes JSX attributes to inspect.
|
|
75
|
+
* @param {string} attributeName Attribute name to match.
|
|
76
|
+
* @return {import('estree-jsx').JSXAttribute|undefined} Matching attribute.
|
|
77
|
+
*/
|
|
78
|
+
function getJsxAttribute( attributes, attributeName ) {
|
|
79
|
+
return attributes.find(
|
|
80
|
+
( attribute ) =>
|
|
81
|
+
attribute.type === 'JSXAttribute' &&
|
|
82
|
+
attribute.name?.name === attributeName
|
|
83
|
+
);
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/**
|
|
87
|
+
* @param {Array} attributes
|
|
88
|
+
* @return {string|null} Resolved JSX name inside `render={ <... /> }`.
|
|
89
|
+
*/
|
|
90
|
+
function getRenderedComponentName( attributes ) {
|
|
91
|
+
const renderAttribute = getJsxAttribute( attributes, 'render' );
|
|
92
|
+
|
|
93
|
+
if (
|
|
94
|
+
! renderAttribute?.value ||
|
|
95
|
+
renderAttribute.value.type !== 'JSXExpressionContainer' ||
|
|
96
|
+
renderAttribute.value.expression.type !== 'JSXElement'
|
|
97
|
+
) {
|
|
98
|
+
return null;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
return resolveTrackedJsxName(
|
|
102
|
+
renderAttribute.value.expression.openingElement.name
|
|
103
|
+
);
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
return {
|
|
107
|
+
ImportDeclaration( node ) {
|
|
108
|
+
const source = node.source.value;
|
|
109
|
+
|
|
110
|
+
if (
|
|
111
|
+
typeof source !== 'string' ||
|
|
112
|
+
! shouldTrackImportSource( source )
|
|
113
|
+
) {
|
|
114
|
+
return;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
node.specifiers.forEach( ( specifier ) => {
|
|
118
|
+
if ( specifier.type === 'ImportSpecifier' ) {
|
|
119
|
+
const importedName = specifier.imported.name;
|
|
120
|
+
|
|
121
|
+
if ( TRACKED_COMPONENTS.has( importedName ) ) {
|
|
122
|
+
trackedImports.set(
|
|
123
|
+
specifier.local.name,
|
|
124
|
+
importedName
|
|
125
|
+
);
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
} );
|
|
129
|
+
},
|
|
130
|
+
|
|
131
|
+
JSXOpeningElement( node ) {
|
|
132
|
+
const renderedComponentName = getRenderedComponentName(
|
|
133
|
+
node.attributes
|
|
134
|
+
);
|
|
135
|
+
|
|
136
|
+
if ( renderedComponentName === 'VisuallyHidden' ) {
|
|
137
|
+
context.report( {
|
|
138
|
+
node,
|
|
139
|
+
messageId: 'visuallyHiddenOrder',
|
|
140
|
+
} );
|
|
141
|
+
return;
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
const elementName = resolveTrackedJsxName( node.name );
|
|
145
|
+
|
|
146
|
+
if (
|
|
147
|
+
elementName === 'Link' &&
|
|
148
|
+
renderedComponentName === 'Text'
|
|
149
|
+
) {
|
|
150
|
+
context.report( {
|
|
151
|
+
node,
|
|
152
|
+
messageId: 'linkTextOrder',
|
|
153
|
+
} );
|
|
154
|
+
}
|
|
155
|
+
},
|
|
156
|
+
};
|
|
157
|
+
},
|
|
158
|
+
};
|