@wordpress/eslint-plugin 24.3.1-next.v.202602271551.0 → 24.4.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.
@@ -31,7 +31,7 @@ function arrayLast( array ) {
31
31
  function getReferences( context, specifiers ) {
32
32
  const variables = specifiers.reduce(
33
33
  ( acc, specifier ) =>
34
- acc.concat( context.getDeclaredVariables( specifier ) ),
34
+ acc.concat( context.sourceCode.getDeclaredVariables( specifier ) ),
35
35
  []
36
36
  );
37
37
  const references = variables.reduce(
@@ -58,7 +58,9 @@ function collectAllNodesFromCallbackFunctions( context, node ) {
58
58
  ( acc, { identifier: { parent } } ) =>
59
59
  parent && parent.arguments && parent.arguments.length > 0
60
60
  ? acc.concat(
61
- context.getDeclaredVariables( parent.arguments[ 0 ] )
61
+ context.sourceCode.getDeclaredVariables(
62
+ parent.arguments[ 0 ]
63
+ )
62
64
  )
63
65
  : acc,
64
66
  []
@@ -153,9 +155,10 @@ function getFixes( fixer, context, callNode ) {
153
155
  fixer.replaceText( callNode.arguments[ 0 ], variableName ),
154
156
  ];
155
157
 
156
- const imports = context
157
- .getAncestors()[ 0 ]
158
- .body.filter( ( node ) => node.type === 'ImportDeclaration' );
158
+ const ancestors = context.sourceCode.getAncestors( callNode );
159
+ const imports = ancestors[ 0 ].body.filter(
160
+ ( node ) => node.type === 'ImportDeclaration'
161
+ );
159
162
  const packageImports = imports.filter(
160
163
  ( node ) => node.source.value === importName
161
164
  );
@@ -22,7 +22,8 @@ module.exports = {
22
22
  },
23
23
  create( context ) {
24
24
  const mode = context.options[ 0 ] || 'always';
25
- const comments = context.getSourceCode().getAllComments();
25
+ const sourceCode = context.sourceCode;
26
+ const comments = sourceCode.getAllComments();
26
27
 
27
28
  /**
28
29
  * Locality classification of an import, one of "External",
@@ -194,9 +195,7 @@ module.exports = {
194
195
  return null;
195
196
  }
196
197
 
197
- const text = context
198
- .getSourceCode()
199
- .getText();
198
+ const text = sourceCode.getText();
200
199
 
201
200
  // Trim preceding and trailing newlines.
202
201
  let [ start, end ] = comment.range;
@@ -61,6 +61,7 @@ function extractTranslatorKeys( commentText ) {
61
61
  module.exports = {
62
62
  meta: {
63
63
  type: 'problem',
64
+ schema: [],
64
65
  messages: {
65
66
  missing:
66
67
  'Translation function with placeholders is missing preceding translator comment',
@@ -71,6 +72,7 @@ module.exports = {
71
72
  },
72
73
  },
73
74
  create( context ) {
75
+ const sourceCode = context.sourceCode;
74
76
  return {
75
77
  CallExpression( node ) {
76
78
  const {
@@ -107,7 +109,7 @@ module.exports = {
107
109
  return;
108
110
  }
109
111
 
110
- const comments = context.getCommentsBefore( node ).slice();
112
+ const comments = sourceCode.getCommentsBefore( node ).slice();
111
113
 
112
114
  let parentNode = parent;
113
115
 
@@ -123,7 +125,9 @@ module.exports = {
123
125
  parentNode.type !== 'Program' &&
124
126
  Math.abs( parentNode.loc.start.line - currentLine ) <= 1
125
127
  ) {
126
- comments.push( ...context.getCommentsBefore( parentNode ) );
128
+ comments.push(
129
+ ...sourceCode.getCommentsBefore( parentNode )
130
+ );
127
131
  parentNode = parentNode.parent;
128
132
  }
129
133
 
@@ -0,0 +1,17 @@
1
+ /**
2
+ * Internal dependencies
3
+ */
4
+ const { createDOMGlobalRule } = require( '../utils/dom-globals' );
5
+
6
+ module.exports = createDOMGlobalRule( {
7
+ description: 'Disallow use of DOM globals in class constructors',
8
+ message:
9
+ "Use of DOM global '{{name}}' is forbidden in class constructors, consider moving this to componentDidMount() or equivalent for non React components",
10
+ test( scope ) {
11
+ if ( scope.block?.parent ) {
12
+ const { type, kind } = scope.block.parent;
13
+ return type === 'MethodDefinition' && kind === 'constructor';
14
+ }
15
+ return false;
16
+ },
17
+ } );
@@ -0,0 +1,12 @@
1
+ /**
2
+ * Internal dependencies
3
+ */
4
+ const { createDOMGlobalRule } = require( '../utils/dom-globals' );
5
+
6
+ module.exports = createDOMGlobalRule( {
7
+ description: 'Disallow use of DOM globals in module scope',
8
+ message: "Use of DOM global '{{name}}' is forbidden in module scope",
9
+ test( scope ) {
10
+ return scope.type === 'module' || scope.type === 'global';
11
+ },
12
+ } );
@@ -0,0 +1,26 @@
1
+ /**
2
+ * Internal dependencies
3
+ */
4
+ const {
5
+ createDOMGlobalRule,
6
+ isReturnValueJSX,
7
+ } = require( '../utils/dom-globals' );
8
+
9
+ module.exports = createDOMGlobalRule( {
10
+ description:
11
+ 'Disallow use of DOM globals in render() method of a React class component',
12
+ message:
13
+ "Use of DOM global '{{name}}' is forbidden in render(), consider moving this to componentDidMount()",
14
+ test( scope ) {
15
+ if ( scope.block?.parent ) {
16
+ const { type, kind, key } = scope.block.parent;
17
+ return (
18
+ type === 'MethodDefinition' &&
19
+ kind === 'method' &&
20
+ key.name === 'render' &&
21
+ isReturnValueJSX( scope )
22
+ );
23
+ }
24
+ return false;
25
+ },
26
+ } );
@@ -0,0 +1,17 @@
1
+ /**
2
+ * Internal dependencies
3
+ */
4
+ const {
5
+ createDOMGlobalRule,
6
+ isReturnValueJSX,
7
+ } = require( '../utils/dom-globals' );
8
+
9
+ module.exports = createDOMGlobalRule( {
10
+ description:
11
+ 'Disallow use of DOM globals in the render cycle of a React function component',
12
+ message:
13
+ "Use of DOM global '{{name}}' is forbidden in the render-cycle of a React FC, consider moving this inside useEffect()",
14
+ test( scope ) {
15
+ return isReturnValueJSX( scope );
16
+ },
17
+ } );
@@ -22,7 +22,7 @@ module.exports = {
22
22
  },
23
23
  create( context ) {
24
24
  let saveFunctionDepth = 0;
25
- const filename = context.getFilename();
25
+ const filename = context.filename;
26
26
 
27
27
  // Skip deprecated files as they preserve old behavior including translation functions
28
28
  const normalizedFilename = filename.replace( /\\/g, '/' );
@@ -3,7 +3,7 @@ module.exports = /** @type {import('eslint').Rule.RuleModule} */ ( {
3
3
  type: 'problem',
4
4
  docs: {
5
5
  description:
6
- 'Disallow setting any CSS custom property beginning with --wpds- in inline styles',
6
+ 'Disallow setting any CSS custom property beginning with --wpds-',
7
7
  },
8
8
  schema: [],
9
9
  messages: {
@@ -14,9 +14,7 @@ module.exports = /** @type {import('eslint').Rule.RuleModule} */ ( {
14
14
  create( context ) {
15
15
  return {
16
16
  /** @param {import('estree').Property} node */
17
- 'JSXAttribute[name.name="style"] ObjectExpression > Property[key.value=/^--wpds-/]'(
18
- node
19
- ) {
17
+ 'ObjectExpression > Property[key.value=/^--wpds-/]'( node ) {
20
18
  context.report( {
21
19
  node: node.key,
22
20
  messageId: 'disallowedSet',
@@ -4,35 +4,41 @@ const tokenList = tokenListModule.default || tokenListModule;
4
4
  const DS_TOKEN_PREFIX = 'wpds-';
5
5
 
6
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.
7
+ * Single-pass extraction that finds all `--prefix-*` tokens in a CSS value
8
+ * string and classifies each occurrence as `var()`-wrapped or bare.
9
9
  *
10
- * @param {string} value - The CSS value string to search for variables.
10
+ * @param {string} value - The CSS value string to search.
11
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' }).
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.
13
15
  *
14
16
  * @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'
17
+ * classifyTokens(
18
+ * 'var(--wpds-color-fg) --wpds-color-bg',
19
+ * 'wpds-'
20
20
  * );
21
- * // → Set { '--wpds-border-color', '--wpds-border-fallback', '--wpds-color-fg' }
21
+ * // → { tokens: Set {'--wpds-color-fg','--wpds-color-bg'},
22
+ * // bare: Set {'--wpds-color-bg'} }
22
23
  */
23
- function extractCSSVariables( value, prefix = '' ) {
24
- const regex = /--[\w-]+/g;
25
- const variables = new Set();
24
+ function classifyTokens( value, prefix = '' ) {
25
+ const regex = new RegExp(
26
+ `(?:^|[^\\w])(var\\(\\s*)?(--${ prefix }[\\w-]+)`,
27
+ 'g'
28
+ );
29
+ const tokens = new Set();
30
+ const bare = new Set();
26
31
 
27
32
  let match;
28
33
  while ( ( match = regex.exec( value ) ) !== null ) {
29
- const variableName = match[ 0 ];
30
- if ( variableName.startsWith( `--${ prefix }` ) ) {
31
- variables.add( variableName );
34
+ const token = match[ 2 ];
35
+ tokens.add( token );
36
+ if ( ! match[ 1 ] ) {
37
+ bare.add( token );
32
38
  }
33
39
  }
34
40
 
35
- return variables;
41
+ return { tokens, bare };
36
42
  }
37
43
 
38
44
  const knownTokens = new Set( tokenList );
@@ -50,6 +56,8 @@ module.exports = /** @type {import('eslint').Rule.RuleModule} */ ( {
50
56
  'The following CSS variables are not valid Design System tokens: {{ tokenNames }}',
51
57
  dynamicToken:
52
58
  'Design System tokens must not be dynamically constructed, as they cannot be statically verified for correctness or processed automatically to inject fallbacks.',
59
+ bareToken:
60
+ 'Design System tokens must be wrapped in `var()` for build-time fallback injection to work: {{ tokenNames }}',
53
61
  },
54
62
  },
55
63
  create( context ) {
@@ -71,6 +79,7 @@ module.exports = /** @type {import('eslint').Rule.RuleModule} */ ( {
71
79
  [ dynamicTemplateLiteralAST ]( node ) {
72
80
  let hasDynamic = false;
73
81
  const unknownTokens = [];
82
+ const bareTokens = [];
74
83
 
75
84
  for ( const quasi of node.quasis ) {
76
85
  const raw = quasi.value.raw;
@@ -84,7 +93,7 @@ module.exports = /** @type {import('eslint').Rule.RuleModule} */ ( {
84
93
  hasDynamic = true;
85
94
  }
86
95
 
87
- const tokens = extractCSSVariables(
96
+ const { tokens, bare } = classifyTokens(
88
97
  value,
89
98
  DS_TOKEN_PREFIX
90
99
  );
@@ -95,12 +104,15 @@ module.exports = /** @type {import('eslint').Rule.RuleModule} */ ( {
95
104
  const endMatch = value.match( /(--([\w-]+))$/ );
96
105
  if ( endMatch ) {
97
106
  tokens.delete( endMatch[ 1 ] );
107
+ bare.delete( endMatch[ 1 ] );
98
108
  }
99
109
  }
100
110
 
101
111
  for ( const token of tokens ) {
102
112
  if ( ! knownTokens.has( token ) ) {
103
113
  unknownTokens.push( token );
114
+ } else if ( bare.has( token ) ) {
115
+ bareTokens.push( token );
104
116
  }
105
117
  }
106
118
  }
@@ -123,6 +135,18 @@ module.exports = /** @type {import('eslint').Rule.RuleModule} */ ( {
123
135
  },
124
136
  } );
125
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
+ }
126
150
  },
127
151
  /** @param {import('estree').Literal | import('estree').TemplateElement} node */
128
152
  [ staticTokensAST ]( node ) {
@@ -145,7 +169,7 @@ module.exports = /** @type {import('eslint').Rule.RuleModule} */ ( {
145
169
  return;
146
170
  }
147
171
 
148
- const usedTokens = extractCSSVariables(
172
+ const { tokens: usedTokens, bare } = classifyTokens(
149
173
  computedValue,
150
174
  DS_TOKEN_PREFIX
151
175
  );
@@ -164,6 +188,31 @@ module.exports = /** @type {import('eslint').Rule.RuleModule} */ ( {
164
188
  },
165
189
  } );
166
190
  }
191
+
192
+ // Skip bare-token check for property keys
193
+ // (e.g. `{ '--wpds-token': value }` declaring a custom property).
194
+ const isPropertyKey =
195
+ node.parent?.type === 'Property' &&
196
+ node.parent.key === node;
197
+
198
+ if ( ! isPropertyKey ) {
199
+ const bareTokens = [ ...usedTokens ].filter(
200
+ ( token ) =>
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
+ } );
214
+ }
215
+ }
167
216
  },
168
217
  };
169
218
  },
@@ -11,15 +11,16 @@
11
11
  const FUNCTION_SCOPE_JSX_IDENTIFIERS = new WeakMap();
12
12
 
13
13
  /**
14
- * Returns the closest function scope for the current ESLint context object, or
15
- * undefined if it cannot be determined.
14
+ * Returns the closest function scope for the given node, or undefined if it
15
+ * cannot be determined.
16
16
  *
17
17
  * @param {ESLintRuleContext} context ESLint context object.
18
+ * @param {ESTreeNode} node Current AST node.
18
19
  *
19
20
  * @return {ESLintScope|undefined} Function scope, if known.
20
21
  */
21
- function getClosestFunctionScope( context ) {
22
- let functionScope = context.getScope();
22
+ function getClosestFunctionScope( context, node ) {
23
+ let functionScope = context.sourceCode.getScope( node );
23
24
  while ( functionScope.type !== 'function' && functionScope.upper ) {
24
25
  functionScope = functionScope.upper;
25
26
  }
@@ -73,7 +74,7 @@ module.exports = /** @type {import('eslint').Rule} */ ( {
73
74
  // identifiers. Account for this by visiting JSX identifiers
74
75
  // first, and tracking them in a map per function scope, which
75
76
  // is later merged with the known variable references.
76
- const functionScope = getClosestFunctionScope( context );
77
+ const functionScope = getClosestFunctionScope( context, node );
77
78
  if ( ! functionScope ) {
78
79
  return;
79
80
  }
@@ -88,7 +89,7 @@ module.exports = /** @type {import('eslint').Rule} */ ( {
88
89
  FUNCTION_SCOPE_JSX_IDENTIFIERS.get( functionScope ).add( node );
89
90
  },
90
91
  'ReturnStatement:exit'( node ) {
91
- const functionScope = getClosestFunctionScope( context );
92
+ const functionScope = getClosestFunctionScope( context, node );
92
93
  if ( ! functionScope ) {
93
94
  return;
94
95
  }
@@ -71,7 +71,7 @@ module.exports = {
71
71
  // Consider whether `setTimeout` is a reference to the global
72
72
  // by checking references to see if `setTimeout` resolves to a
73
73
  // variable in scope.
74
- const { references } = context.getScope();
74
+ const { references } = context.sourceCode.getScope( node );
75
75
  const hasResolvedReference = references.some(
76
76
  ( reference ) =>
77
77
  reference.identifier.name === 'setTimeout' &&
@@ -0,0 +1,114 @@
1
+ /**
2
+ * Allowlist: only the listed components are permitted from these packages.
3
+ * Any other named import will be flagged with the package's message.
4
+ *
5
+ * `message` supports `{{ name }}` and `{{ source }}` placeholders.
6
+ *
7
+ * @type {Record<string, { allowed: string[], message?: string }>}
8
+ */
9
+ const ALLOWLIST = {
10
+ '@wordpress/ui': {
11
+ allowed: [ 'Badge', 'Stack' ],
12
+ message:
13
+ '`{{ name }}` from `{{ source }}` is not yet recommended for use in a WordPress environment.',
14
+ },
15
+ };
16
+
17
+ /**
18
+ * Denylist: the listed components are flagged with a message pointing
19
+ * to a recommended alternative.
20
+ *
21
+ * Messages support `{{ name }}` and `{{ source }}` placeholders.
22
+ *
23
+ * @type {Record<string, Record<string, string>>}
24
+ */
25
+ const DENYLIST = {
26
+ '@wordpress/components': {
27
+ __experimentalZStack:
28
+ '{{ name }} is planned for deprecation. Write your own CSS instead.',
29
+ },
30
+ };
31
+
32
+ /** @type {import('eslint').Rule.RuleModule} */
33
+ const rule = {
34
+ meta: {
35
+ type: 'suggestion',
36
+ docs: {
37
+ description:
38
+ 'Encourage the use of recommended UI components in a WordPress environment.',
39
+ url: 'https://github.com/WordPress/gutenberg/blob/HEAD/packages/eslint-plugin/docs/rules/use-recommended-components.md',
40
+ },
41
+ schema: [],
42
+ },
43
+ create( context ) {
44
+ return {
45
+ /** @param {import('estree').ImportDeclaration} node */
46
+ ImportDeclaration( node ) {
47
+ if ( typeof node.source.value !== 'string' ) {
48
+ return;
49
+ }
50
+
51
+ const source = node.source.value;
52
+
53
+ const allowlistEntry = ALLOWLIST[ source ];
54
+ const denylistEntry = DENYLIST[ source ];
55
+
56
+ if ( ! allowlistEntry && ! denylistEntry ) {
57
+ return;
58
+ }
59
+
60
+ node.specifiers.forEach( ( specifier ) => {
61
+ if ( specifier.type !== 'ImportSpecifier' ) {
62
+ return;
63
+ }
64
+
65
+ const name = specifier.imported.name;
66
+
67
+ if (
68
+ allowlistEntry &&
69
+ ! allowlistEntry.allowed.includes( name )
70
+ ) {
71
+ context.report( {
72
+ node: specifier,
73
+ message: resolveMessage(
74
+ allowlistEntry.message,
75
+ name,
76
+ source
77
+ ),
78
+ } );
79
+ }
80
+
81
+ if ( denylistEntry?.hasOwnProperty( name ) ) {
82
+ context.report( {
83
+ node: specifier,
84
+ message: resolveMessage(
85
+ denylistEntry[ name ],
86
+ name,
87
+ source
88
+ ),
89
+ } );
90
+ }
91
+ } );
92
+ },
93
+ };
94
+ },
95
+ };
96
+
97
+ /**
98
+ * @param {string|undefined} template
99
+ * @param {string} name
100
+ * @param {string} source
101
+ * @return {string} Resolved message string.
102
+ */
103
+ function resolveMessage( template, name, source ) {
104
+ if ( ! template ) {
105
+ return `\`${ name }\` from \`${ source }\` is not recommended.`;
106
+ }
107
+ return template
108
+ .replace( /\{\{\s*name\s*\}\}/g, name )
109
+ .replace( /\{\{\s*source\s*\}\}/g, source );
110
+ }
111
+
112
+ module.exports = rule;
113
+ module.exports.ALLOWLIST = ALLOWLIST;
114
+ module.exports.DENYLIST = DENYLIST;
@@ -0,0 +1,156 @@
1
+ /**
2
+ * Shared utilities for SSR-safety rules that prevent DOM global usage in
3
+ * unsafe contexts.
4
+ *
5
+ * These rules replace the unmaintained `eslint-plugin-ssr-friendly` package.
6
+ */
7
+
8
+ const { browser: browserGlobals, node: nodeGlobals } = require( 'globals' );
9
+
10
+ /**
11
+ * Returns true if the given name is a browser-only DOM global (exists in
12
+ * browser globals but not in node globals).
13
+ *
14
+ * @param {string} name Identifier name to check.
15
+ * @return {boolean} Whether the name is a DOM-only global.
16
+ */
17
+ function isDOMGlobal( name ) {
18
+ return name in browserGlobals && ! ( name in nodeGlobals );
19
+ }
20
+
21
+ /**
22
+ * Returns true if the given scope's block returns JSX (heuristic for
23
+ * detecting React render functions).
24
+ *
25
+ * @param {import('eslint').Scope.Scope} scope Scope to check.
26
+ * @return {boolean} Whether the scope returns JSX.
27
+ */
28
+ function isReturnValueJSX( scope ) {
29
+ if ( scope.type !== 'function' ) {
30
+ return false;
31
+ }
32
+
33
+ const { block } = scope;
34
+
35
+ // Concise arrow function: const C = () => <div />
36
+ if (
37
+ block.type === 'ArrowFunctionExpression' &&
38
+ block.body.type !== 'BlockStatement'
39
+ ) {
40
+ return (
41
+ block.body.type === 'JSXElement' ||
42
+ block.body.type === 'JSXFragment'
43
+ );
44
+ }
45
+
46
+ const body = block?.body?.body;
47
+ if ( ! body || typeof body.find !== 'function' ) {
48
+ return false;
49
+ }
50
+
51
+ return body.some(
52
+ ( statement ) =>
53
+ statement?.type === 'ReturnStatement' &&
54
+ statement.argument &&
55
+ ( statement.argument.type === 'JSXElement' ||
56
+ statement.argument.type === 'JSXFragment' )
57
+ );
58
+ }
59
+
60
+ /**
61
+ * Returns true if the reference's identifier should be skipped from
62
+ * reporting. Allows `typeof <global>` checks and TypeScript type references.
63
+ *
64
+ * @param {import('eslint').Scope.Reference} reference Variable reference.
65
+ * @return {boolean} Whether the reference should be skipped.
66
+ */
67
+ function shouldSkipReference( reference ) {
68
+ const { parent } = reference.identifier;
69
+ return (
70
+ ( parent.type === 'UnaryExpression' && parent.operator === 'typeof' ) ||
71
+ parent.type === 'TSTypeReference' ||
72
+ parent.type === 'TSInterfaceHeritage' ||
73
+ parent.type === 'TSTypeQuery'
74
+ );
75
+ }
76
+
77
+ /**
78
+ * Creates an ESLint rule that reports DOM global usage based on a scope
79
+ * predicate.
80
+ *
81
+ * @param {Object} options Rule options.
82
+ * @param {string} options.description Rule description.
83
+ * @param {string} options.message Error message template (use {{name}} for the global name).
84
+ * @param {(scope: import('eslint').Scope.Scope) => boolean} options.test Predicate that receives the reference's scope and
85
+ * returns true if usage should be reported.
86
+ * @return {import('eslint').Rule.RuleModule} ESLint rule module.
87
+ */
88
+ function createDOMGlobalRule( { description, message, test } ) {
89
+ return {
90
+ meta: {
91
+ type: 'problem',
92
+ schema: [],
93
+ docs: {
94
+ description,
95
+ },
96
+ messages: {
97
+ defaultMessage: message,
98
+ },
99
+ },
100
+ create( context ) {
101
+ return {
102
+ Program( node ) {
103
+ const scope = context.sourceCode.getScope( node );
104
+
105
+ // Report variables declared elsewhere (e.g. variables
106
+ // defined as "global" by eslint).
107
+ for ( const variable of scope.variables ) {
108
+ if (
109
+ variable.defs.length === 0 &&
110
+ isDOMGlobal( variable.name )
111
+ ) {
112
+ for ( const reference of variable.references ) {
113
+ if (
114
+ ! shouldSkipReference( reference ) &&
115
+ test( reference.from )
116
+ ) {
117
+ context.report( {
118
+ node: reference.identifier,
119
+ messageId: 'defaultMessage',
120
+ data: {
121
+ name: reference.identifier.name,
122
+ },
123
+ } );
124
+ }
125
+ }
126
+ }
127
+ }
128
+
129
+ // Report variables not declared at all.
130
+ for ( const reference of scope.through ) {
131
+ if (
132
+ isDOMGlobal( reference.identifier.name ) &&
133
+ ! shouldSkipReference( reference ) &&
134
+ test( reference.from )
135
+ ) {
136
+ context.report( {
137
+ node: reference.identifier,
138
+ messageId: 'defaultMessage',
139
+ data: {
140
+ name: reference.identifier.name,
141
+ },
142
+ } );
143
+ }
144
+ }
145
+ },
146
+ };
147
+ },
148
+ };
149
+ }
150
+
151
+ module.exports = {
152
+ isDOMGlobal,
153
+ isReturnValueJSX,
154
+ shouldSkipReference,
155
+ createDOMGlobalRule,
156
+ };