@wordpress/eslint-plugin 18.1.0 → 19.0.1

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 CHANGED
@@ -10,7 +10,7 @@ Install the module
10
10
  npm install @wordpress/eslint-plugin --save-dev
11
11
  ```
12
12
 
13
- **Note**: This package requires `node` 14.0.0 or later, and `npm` 6.14.4 or later. It is not compatible with older versions.
13
+ **Note**: This package requires Node.js version with long-term support status (check [Active LTS or Maintenance LTS releases](https://nodejs.org/en/about/previous-releases)). It is not compatible with older versions.
14
14
 
15
15
  ## Usage
16
16
 
@@ -69,25 +69,26 @@ The granular rulesets will not define any environment globals. As such, if they
69
69
 
70
70
  ### Rules
71
71
 
72
- | Rule | Description | Recommended |
73
- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------- | ----------- |
74
- | [data-no-store-string-literals](https://github.com/WordPress/gutenberg/tree/HEAD/packages/eslint-plugin/docs/rules/data-no-store-string-literals.md) | Discourage passing string literals to reference data stores | |
75
- | [dependency-group](https://github.com/WordPress/gutenberg/tree/HEAD/packages/eslint-plugin/docs/rules/dependency-group.md) | Enforce dependencies docblocks formatting | ✓ |
76
- | [is-gutenberg-plugin](docs/rules/is-gutenberg-plugin.md) | Governs the use of the `process.env.IS_GUTENBERG_PLUGIN` constant | ✓ |
77
- | [no-base-control-with-label-without-id](https://github.com/WordPress/gutenberg/tree/HEAD/packages/eslint-plugin/docs/rules/no-base-control-with-label-without-id.md) | Disallow the usage of BaseControl component with a label prop set but omitting the id property ||
78
- | [no-unguarded-get-range-at](https://github.com/WordPress/gutenberg/tree/HEAD/packages/eslint-plugin/docs/rules/no-unguarded-get-range-at.md) | Disallow the usage of unguarded `getRangeAt` calls | ✓ |
79
- | [no-unsafe-wp-apis](https://github.com/WordPress/gutenberg/tree/HEAD/packages/eslint-plugin/docs/rules/no-unsafe-wp-apis.md) | Disallow the usage of unsafe APIs from `@wordpress/*` packages ||
80
- | [no-unused-vars-before-return](https://github.com/WordPress/gutenberg/tree/HEAD/packages/eslint-plugin/docs/rules/no-unused-vars-before-return.md) | Disallow assigning variable values if unused before a return | ✓ |
81
- | [react-no-unsafe-timeout](https://github.com/WordPress/gutenberg/tree/HEAD/packages/eslint-plugin/docs/rules/react-no-unsafe-timeout.md) | Disallow unsafe `setTimeout` in component | |
82
- | [valid-sprintf](https://github.com/WordPress/gutenberg/tree/HEAD/packages/eslint-plugin/docs/rules/valid-sprintf.md) | Enforce valid sprintf usage | ✓ |
83
- | [i18n-ellipsis](https://github.com/WordPress/gutenberg/tree/HEAD/packages/eslint-plugin/docs/rules/i18n-ellipsis.md) | Disallow using three dots in translatable strings | ✓ |
84
- | [i18n-no-collapsible-whitespace](https://github.com/WordPress/gutenberg/tree/HEAD/packages/eslint-plugin/docs/rules/i18n-no-collapsible-whitespace.md) | Disallow collapsible whitespace in translatable strings | ✓ |
85
- | [i18n-no-placeholders-only](https://github.com/WordPress/gutenberg/tree/HEAD/packages/eslint-plugin/docs/rules/i18n-no-placeholders-only.md) | Prevent using only placeholders in translatable strings | ✓ |
86
- | [i18n-no-variables](https://github.com/WordPress/gutenberg/tree/HEAD/packages/eslint-plugin/docs/rules/i18n-no-variables.md) | Enforce string literals as translation function arguments | ✓ |
87
- | [i18n-text-domain](https://github.com/WordPress/gutenberg/tree/HEAD/packages/eslint-plugin/docs/rules/i18n-text-domain.md) | Enforce passing valid text domains | ✓ |
88
- | [i18n-translator-comments](https://github.com/WordPress/gutenberg/tree/HEAD/packages/eslint-plugin/docs/rules/i18n-translator-comments.md) | Enforce adding translator comments | ✓ |
89
- | [i18n-no-flanking-whitespace](https://github.com/WordPress/gutenberg/tree/HEAD/packages/eslint-plugin/docs/rules/i18n-no-flanking-whitespace.md) | Disallow leading or trailing whitespace in translatable strings | |
90
- | [i18n-hyphenated-range](https://github.com/WordPress/gutenberg/tree/HEAD/packages/eslint-plugin/docs/rules/i18n-hyphenated-range.md) | Disallow hyphenated numerical ranges in translatable strings | |
72
+ | Rule | Description | Recommended |
73
+ | -------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------- | ----------- |
74
+ | [data-no-store-string-literals](https://github.com/WordPress/gutenberg/tree/HEAD/packages/eslint-plugin/docs/rules/data-no-store-string-literals.md) | Discourage passing string literals to reference data stores. | |
75
+ | [dependency-group](https://github.com/WordPress/gutenberg/tree/HEAD/packages/eslint-plugin/docs/rules/dependency-group.md) | Enforce dependencies docblocks formatting. | ✓ |
76
+ | [i18n-ellipsis](https://github.com/WordPress/gutenberg/tree/HEAD/packages/eslint-plugin/docs/rules/i18n-ellipsis.md) | Disallow using three dots in translatable strings. | ✓ |
77
+ | [i18n-hyphenated-range](https://github.com/WordPress/gutenberg/tree/HEAD/packages/eslint-plugin/docs/rules/i18n-hyphenated-range.md) | Disallow hyphenated numerical ranges in translatable strings. | |
78
+ | [i18n-no-collapsible-whitespace](https://github.com/WordPress/gutenberg/tree/HEAD/packages/eslint-plugin/docs/rules/i18n-no-collapsible-whitespace.md) | Disallow collapsible whitespace in translatable strings. | ✓ |
79
+ | [i18n-no-flanking-whitespace](https://github.com/WordPress/gutenberg/tree/HEAD/packages/eslint-plugin/docs/rules/i18n-no-flanking-whitespace.md) | Disallow leading or trailing whitespace in translatable strings. | |
80
+ | [i18n-no-placeholders-only](https://github.com/WordPress/gutenberg/tree/HEAD/packages/eslint-plugin/docs/rules/i18n-no-placeholders-only.md) | Prevent using only placeholders in translatable strings. | ✓ |
81
+ | [i18n-no-variables](https://github.com/WordPress/gutenberg/tree/HEAD/packages/eslint-plugin/docs/rules/i18n-no-variables.md) | Enforce string literals as translation function arguments. ||
82
+ | [i18n-text-domain](https://github.com/WordPress/gutenberg/tree/HEAD/packages/eslint-plugin/docs/rules/i18n-text-domain.md) | Enforce passing valid text domains. | ✓ |
83
+ | [i18n-translator-comments](https://github.com/WordPress/gutenberg/tree/HEAD/packages/eslint-plugin/docs/rules/i18n-translator-comments.md) | Enforce adding translator comments. | ✓ |
84
+ | [no-base-control-with-label-without-id](https://github.com/WordPress/gutenberg/tree/HEAD/packages/eslint-plugin/docs/rules/no-base-control-with-label-without-id.md) | Disallow the usage of BaseControl component with a label prop set but omitting the id property. | ✓ |
85
+ | [no-unguarded-get-range-at](https://github.com/WordPress/gutenberg/tree/HEAD/packages/eslint-plugin/docs/rules/no-unguarded-get-range-at.md) | Disallow the usage of unguarded `getRangeAt` calls. | ✓ |
86
+ | [no-unsafe-wp-apis](https://github.com/WordPress/gutenberg/tree/HEAD/packages/eslint-plugin/docs/rules/no-unsafe-wp-apis.md) | Disallow the usage of unsafe APIs from `@wordpress/*` packagesl | ✓ |
87
+ | [no-unused-vars-before-return](https://github.com/WordPress/gutenberg/tree/HEAD/packages/eslint-plugin/docs/rules/no-unused-vars-before-return.md) | Disallow assigning variable values if unused before a return. | ✓ |
88
+ | [no-wp-process-env](https://github.com/WordPress/gutenberg/tree/HEAD/packages/eslint-plugin/docs/rules/no-wp-process-env.md) | Disallow legacy usage of WordPress variables via `process.env` like `process.env.SCRIPT_DEBUG`. | ✓ |
89
+ | [react-no-unsafe-timeout](https://github.com/WordPress/gutenberg/tree/HEAD/packages/eslint-plugin/docs/rules/react-no-unsafe-timeout.md) | Disallow unsafe `setTimeout` in component. | |
90
+ | [valid-sprintf](https://github.com/WordPress/gutenberg/tree/HEAD/packages/eslint-plugin/docs/rules/valid-sprintf.md) | Enforce valid sprintf usage. | |
91
+ | [wp-global-usage](https://github.com/WordPress/gutenberg/tree/HEAD/packages/eslint-plugin/docs/rules/wp-global-usage.md) | Enforce correct usage of WordPress globals like `globalThis.SCRIPT_DEBUG`. | |
91
92
 
92
93
  ### Legacy
93
94
 
package/configs/custom.js CHANGED
@@ -7,6 +7,7 @@ module.exports = {
7
7
  '@wordpress/no-global-active-element': 'error',
8
8
  '@wordpress/no-global-get-selection': 'error',
9
9
  '@wordpress/no-unsafe-wp-apis': 'error',
10
+ '@wordpress/no-wp-process-env': 'error',
10
11
  },
11
12
  overrides: [
12
13
  {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@wordpress/eslint-plugin",
3
- "version": "18.1.0",
3
+ "version": "19.0.1",
4
4
  "description": "ESLint plugin for WordPress development.",
5
5
  "author": "The WordPress Contributors",
6
6
  "license": "GPL-2.0-or-later",
@@ -20,8 +20,8 @@
20
20
  "url": "https://github.com/WordPress/gutenberg/issues"
21
21
  },
22
22
  "engines": {
23
- "node": ">=14",
24
- "npm": ">=6.14.4"
23
+ "node": ">=18.12.0",
24
+ "npm": ">=8.19.2"
25
25
  },
26
26
  "files": [
27
27
  "configs",
@@ -34,8 +34,8 @@
34
34
  "@babel/eslint-parser": "^7.16.0",
35
35
  "@typescript-eslint/eslint-plugin": "^6.4.1",
36
36
  "@typescript-eslint/parser": "^6.4.1",
37
- "@wordpress/babel-preset-default": "^7.42.0",
38
- "@wordpress/prettier-config": "^3.15.0",
37
+ "@wordpress/babel-preset-default": "^8.0.1",
38
+ "@wordpress/prettier-config": "^4.0.1",
39
39
  "cosmiconfig": "^7.0.0",
40
40
  "eslint-config-prettier": "^8.3.0",
41
41
  "eslint-plugin-import": "^2.25.2",
@@ -66,5 +66,5 @@
66
66
  "publishConfig": {
67
67
  "access": "public"
68
68
  },
69
- "gitHead": "42f38f287506a6b3ed8ccba839b18ad066821044"
69
+ "gitHead": "0e973525f7787401b5a544e0727774d52a78639f"
70
70
  }
@@ -0,0 +1,62 @@
1
+ /**
2
+ * External dependencies
3
+ */
4
+ import { RuleTester } from 'eslint';
5
+
6
+ /**
7
+ * Internal dependencies
8
+ */
9
+ import rule from '../no-wp-process-env';
10
+
11
+ const ruleTester = new RuleTester( {
12
+ parserOptions: {
13
+ ecmaVersion: 6,
14
+ },
15
+ } );
16
+
17
+ ruleTester.run( 'no-wp-process-env', rule, {
18
+ valid: [
19
+ { code: 'process.env.NODE_ENV' },
20
+ { code: 'process.env.WHATEVER' },
21
+ { code: 'process.env[foo]' },
22
+ { code: 'process.env["foo"]' },
23
+ { code: `process['env']["foo"]` },
24
+ { code: "process['env'][`foo`]" },
25
+ { code: "process.env[`${ '' }IS_GUTENBERG_PLUGIN`]" },
26
+ { code: `a.b.c` },
27
+ { code: `x['y']['z']` },
28
+ { code: `d[e][f]` },
29
+ { code: `process[ (()=>'env')() ][ {_:'SCRIPT_DEBUG'}['_'] ]` },
30
+ ],
31
+ invalid: [
32
+ {
33
+ code: 'process.env.IS_GUTENBERG_PLUGIN',
34
+ errors: [ { messageId: 'useGlobalThis' } ],
35
+ output: 'globalThis.IS_GUTENBERG_PLUGIN',
36
+ },
37
+ {
38
+ code: 'process.env.SCRIPT_DEBUG',
39
+ errors: [ { messageId: 'useGlobalThis' } ],
40
+ output: 'globalThis.SCRIPT_DEBUG',
41
+ },
42
+ {
43
+ code: 'process.env.IS_WORDPRESS_CORE',
44
+ errors: [ { messageId: 'useGlobalThis' } ],
45
+ output: 'globalThis.IS_WORDPRESS_CORE',
46
+ },
47
+ {
48
+ code: `process['env']["IS_GUTENBERG_PLUGIN"]`,
49
+ errors: [ { messageId: 'useGlobalThis' } ],
50
+ output: 'globalThis.IS_GUTENBERG_PLUGIN',
51
+ },
52
+ {
53
+ code: 'process[`env`][`IS_GUTENBERG_PLUGIN`]',
54
+ errors: [ { messageId: 'useGlobalThis' } ],
55
+ output: 'globalThis.IS_GUTENBERG_PLUGIN',
56
+ },
57
+ {
58
+ code: 'process.env.GUTENBERG_PHASE',
59
+ errors: [ { messageId: 'noGutenbergPhase' } ],
60
+ },
61
+ ],
62
+ } );
@@ -0,0 +1,129 @@
1
+ /**
2
+ * External dependencies
3
+ */
4
+ import { RuleTester } from 'eslint';
5
+
6
+ /**
7
+ * Internal dependencies
8
+ */
9
+ import rule from '../wp-global-usage';
10
+
11
+ const ruleTester = new RuleTester( {
12
+ parserOptions: {
13
+ ecmaVersion: 6,
14
+ },
15
+ } );
16
+
17
+ ruleTester.run( 'wp-global-usage', rule, {
18
+ valid: [
19
+ { code: "const text = 'SCRIPT_DEBUG'" },
20
+ { code: 'const config = { SCRIPT_DEBUG: true }' },
21
+ { code: 'if ( globalThis.IS_GUTENBERG_PLUGIN ) {}' },
22
+ { code: 'if ( globalThis.IS_WORDPRESS_CORE ) {}' },
23
+ { code: 'if ( globalThis.SCRIPT_DEBUG ) {}' },
24
+ { code: 'if ( process.env.SCRIPT_DEBUG ) {}' },
25
+ { code: 'if ( ! globalThis.IS_GUTENBERG_PLUGIN ) {}' },
26
+ {
27
+ // Ensure whitespace is ok.
28
+ code: `if (
29
+ globalThis.
30
+ IS_GUTENBERG_PLUGIN
31
+ ) {}`,
32
+ },
33
+ { code: 'const test = globalThis.IS_GUTENBERG_PLUGIN ? foo : bar' },
34
+ { code: 'const test = ! globalThis.IS_GUTENBERG_PLUGIN ? bar : foo' },
35
+ {
36
+ // Ensure whitespace is ok.
37
+ code: `const test = ! globalThis.
38
+ IS_GUTENBERG_PLUGIN ? bar : foo`,
39
+ },
40
+ ],
41
+ invalid: [
42
+ {
43
+ code: 'if ( IS_GUTENBERG_PLUGIN ) {}',
44
+ errors: [
45
+ {
46
+ messageId: 'usedWithoutGlobalThis',
47
+ data: { name: 'IS_GUTENBERG_PLUGIN' },
48
+ },
49
+ ],
50
+ output: 'if ( globalThis.IS_GUTENBERG_PLUGIN ) {}',
51
+ },
52
+ {
53
+ code: 'if ( window.IS_GUTENBERG_PLUGIN ) {}',
54
+ errors: [
55
+ {
56
+ messageId: 'usedWithoutGlobalThis',
57
+ data: { name: 'IS_GUTENBERG_PLUGIN' },
58
+ },
59
+ ],
60
+ output: 'if ( globalThis.IS_GUTENBERG_PLUGIN ) {}',
61
+ },
62
+ {
63
+ code: 'if ( SCRIPT_DEBUG ) {}',
64
+ errors: [
65
+ {
66
+ messageId: 'usedWithoutGlobalThis',
67
+ data: { name: 'SCRIPT_DEBUG' },
68
+ },
69
+ ],
70
+ output: 'if ( globalThis.SCRIPT_DEBUG ) {}',
71
+ },
72
+ {
73
+ code: 'if ( IS_WORDPRESS_CORE ) {}',
74
+ errors: [
75
+ {
76
+ messageId: 'usedWithoutGlobalThis',
77
+ data: { name: 'IS_WORDPRESS_CORE' },
78
+ },
79
+ ],
80
+ output: 'if ( globalThis.IS_WORDPRESS_CORE ) {}',
81
+ },
82
+ {
83
+ code: "if ( window[ 'IS_GUTENBERG_PLUGIN' ] ) {}",
84
+ errors: [
85
+ {
86
+ messageId: 'usedWithoutGlobalThis',
87
+ data: { name: 'IS_GUTENBERG_PLUGIN' },
88
+ },
89
+ ],
90
+ output: 'if ( globalThis.IS_GUTENBERG_PLUGIN ) {}',
91
+ },
92
+ {
93
+ code: 'if ( true ) { globalThis.IS_GUTENBERG_PLUGIN === 2 }',
94
+ errors: [
95
+ {
96
+ messageId: 'usedOutsideConditional',
97
+ data: { name: 'IS_GUTENBERG_PLUGIN' },
98
+ },
99
+ ],
100
+ },
101
+ {
102
+ code: 'if ( globalThis.IS_GUTENBERG_PLUGIN === 2 ) {}',
103
+ errors: [
104
+ {
105
+ messageId: 'usedOutsideConditional',
106
+ data: { name: 'IS_GUTENBERG_PLUGIN' },
107
+ },
108
+ ],
109
+ },
110
+ {
111
+ code: 'if ( true || globalThis.IS_GUTENBERG_PLUGIN === 2 ) {}',
112
+ errors: [
113
+ {
114
+ messageId: 'usedOutsideConditional',
115
+ data: { name: 'IS_GUTENBERG_PLUGIN' },
116
+ },
117
+ ],
118
+ },
119
+ {
120
+ code: 'const isFeatureActive = globalThis.IS_GUTENBERG_PLUGIN;',
121
+ errors: [
122
+ {
123
+ messageId: 'usedOutsideConditional',
124
+ data: { name: 'IS_GUTENBERG_PLUGIN' },
125
+ },
126
+ ],
127
+ },
128
+ ],
129
+ } );
@@ -0,0 +1,93 @@
1
+ const NAMES = new Set(
2
+ /** @type {const} */ ( [
3
+ 'GUTENBERG_PHASE',
4
+ 'IS_GUTENBERG_PLUGIN',
5
+ 'IS_WORDPRESS_CORE',
6
+ 'SCRIPT_DEBUG',
7
+ ] )
8
+ );
9
+
10
+ /** @type {import('eslint').Rule.RuleModule} */
11
+ module.exports = {
12
+ meta: {
13
+ type: 'problem',
14
+ schema: [],
15
+ fixable: true,
16
+ messages: {
17
+ useGlobalThis:
18
+ '`{{ name }}` should not be accessed from process.env. Use `globalThis.{{name}}`.',
19
+ noGutenbergPhase:
20
+ 'The GUTENBERG_PHASE environement variable is no longer available. Use IS_GUTENBERG_PLUGIN (boolean).',
21
+ },
22
+ },
23
+ create( context ) {
24
+ return {
25
+ MemberExpression( node ) {
26
+ const propertyNameOrValue = memberProperty( node );
27
+ if ( ! propertyNameOrValue ) {
28
+ return;
29
+ }
30
+ if ( ! NAMES.has( propertyNameOrValue ) ) {
31
+ return;
32
+ }
33
+
34
+ if ( node.object.type !== 'MemberExpression' ) {
35
+ return;
36
+ }
37
+
38
+ const obj = node.object;
39
+ const envCandidateProperty = memberProperty( obj );
40
+ if ( envCandidateProperty !== 'env' ) {
41
+ return;
42
+ }
43
+
44
+ if (
45
+ obj.object.type !== 'Identifier' ||
46
+ obj.object.name !== 'process'
47
+ ) {
48
+ return;
49
+ }
50
+
51
+ if ( propertyNameOrValue === 'GUTENBERG_PHASE' ) {
52
+ context.report( {
53
+ node,
54
+ messageId: 'noGutenbergPhase',
55
+ } );
56
+ return;
57
+ }
58
+
59
+ context.report( {
60
+ node,
61
+ messageId: 'useGlobalThis',
62
+ data: { name: propertyNameOrValue },
63
+ fix( fixer ) {
64
+ return fixer.replaceText(
65
+ node,
66
+ `globalThis.${ propertyNameOrValue }`
67
+ );
68
+ },
69
+ } );
70
+ },
71
+ };
72
+ },
73
+ };
74
+
75
+ /**
76
+ * @param {import('estree').MemberExpression} node
77
+ */
78
+ function memberProperty( node ) {
79
+ switch ( node.property.type ) {
80
+ case 'Identifier':
81
+ return node.property.name;
82
+ case 'Literal':
83
+ return node.property.value;
84
+ case 'TemplateLiteral':
85
+ if (
86
+ ! node.property.expressions.length &&
87
+ node.property.quasis.length === 1
88
+ ) {
89
+ return node.property.quasis[ 0 ].value.raw;
90
+ }
91
+ }
92
+ return null;
93
+ }
@@ -0,0 +1,170 @@
1
+ const NAMES = new Set(
2
+ /** @type {const} */ ( [
3
+ 'IS_GUTENBERG_PLUGIN',
4
+ 'IS_WORDPRESS_CORE',
5
+ 'SCRIPT_DEBUG',
6
+ ] )
7
+ );
8
+
9
+ /**
10
+ * Tests whether the IS_GUTENBERG_PLUGIN variable is used as the condition for an
11
+ * if statement or ternary, triggering a violation if not.
12
+ *
13
+ * @example
14
+ * ```js
15
+ * // good
16
+ * if ( process.env.IS_GUTENBERG_PLUGIN ) {
17
+ *
18
+ * // bad
19
+ * const isFeatureActive = process.env.IS_GUTENBERG_PLUGIN;
20
+ * ```
21
+ *
22
+ * @param {import('estree').Node} node The IS_GUTENBERG_PLUGIN identifier node.
23
+ */
24
+ function isUsedInConditional( node ) {
25
+ /** @type {import('estree').Node|undefined} */
26
+ let current = node;
27
+
28
+ // Simple negation is the only expresion allowed in the conditional:
29
+ // if ( ! globalThis.SCRIPT_DEBUG ) {}
30
+ // const D = ! globalThis.SCRIPT_DEBUG ? 'yes' : 'no';
31
+ if (
32
+ current.parent.type === 'UnaryExpression' &&
33
+ current.parent.operator === '!'
34
+ ) {
35
+ current = current.parent;
36
+ }
37
+
38
+ // Check if the current node is the test of a conditional
39
+
40
+ /** @type {import('estree').Node|undefined} */
41
+ const parent = current.parent;
42
+
43
+ if ( parent.type === 'IfStatement' && parent.test === current ) {
44
+ return true;
45
+ }
46
+ if ( parent.type === 'ConditionalExpression' && parent.test === current ) {
47
+ return true;
48
+ }
49
+ return false;
50
+ }
51
+
52
+ /** @type {import('eslint').Rule.RuleModule} */
53
+ module.exports = {
54
+ meta: {
55
+ type: 'problem',
56
+ schema: [],
57
+ fixable: true,
58
+ messages: {
59
+ usedOutsideConditional:
60
+ '`globalThis.{{ name }}` should only be used as the condition in an if statement or ternary expression.',
61
+ usedWithoutGlobalThis:
62
+ '`{{ name }}` should not be used directly. Use `globalThis.{{ name }}`.',
63
+ },
64
+ },
65
+ create( context ) {
66
+ return {
67
+ Identifier( node ) {
68
+ // Bypass any identifiers with a node name different to `IS_GUTENBERG_PLUGIN`.
69
+ if ( ! NAMES.has( node.name ) ) {
70
+ return;
71
+ }
72
+
73
+ if ( node.parent.type === 'Property' ) {
74
+ return;
75
+ }
76
+
77
+ if ( node.parent.type !== 'MemberExpression' ) {
78
+ context.report( {
79
+ node,
80
+ messageId: 'usedWithoutGlobalThis',
81
+ data: { name: node.name },
82
+ fix( fixer ) {
83
+ return fixer.replaceText(
84
+ node,
85
+ `globalThis.${ node.name }`
86
+ );
87
+ },
88
+ } );
89
+
90
+ if ( ! isUsedInConditional( node ) ) {
91
+ context.report( {
92
+ node,
93
+ messageId: 'usedOutsideConditional',
94
+ data: {
95
+ name: node.name,
96
+ },
97
+ } );
98
+ }
99
+ return;
100
+ }
101
+
102
+ if (
103
+ node.parent.object.type === 'Identifier' &&
104
+ node.parent.object.name !== 'globalThis'
105
+ ) {
106
+ context.report( {
107
+ node,
108
+ messageId: 'usedWithoutGlobalThis',
109
+ data: { name: node.name },
110
+ fix( fixer ) {
111
+ if ( node.parent.object.name === 'window' ) {
112
+ return fixer.replaceText(
113
+ node.parent,
114
+ `globalThis.${ node.name }`
115
+ );
116
+ }
117
+ },
118
+ } );
119
+ } else if ( ! isUsedInConditional( node.parent ) ) {
120
+ context.report( {
121
+ node,
122
+ messageId: 'usedOutsideConditional',
123
+ data: {
124
+ name: node.name,
125
+ },
126
+ } );
127
+ }
128
+ },
129
+
130
+ // Check for literals, e.g. when 'IS_GUTENBERG_PLUGIN' is used as a string via something like 'window[ 'IS_GUTENBERG_PLUGIN' ]'.
131
+ Literal( node ) {
132
+ // Bypass any identifiers with a node value different to `IS_GUTENBERG_PLUGIN`.
133
+ if ( ! NAMES.has( node.value ) ) {
134
+ return;
135
+ }
136
+
137
+ if ( node.parent.type !== 'MemberExpression' ) {
138
+ return;
139
+ }
140
+
141
+ if (
142
+ node.parent.object.type === 'Identifier' &&
143
+ node.parent.object.name !== 'globalThis'
144
+ ) {
145
+ context.report( {
146
+ node,
147
+ messageId: 'usedWithoutGlobalThis',
148
+ data: { name: node.value },
149
+ fix( fixer ) {
150
+ if ( node.parent.object.name === 'window' ) {
151
+ return fixer.replaceText(
152
+ node.parent,
153
+ `globalThis.${ node.value }`
154
+ );
155
+ }
156
+ },
157
+ } );
158
+ } else if ( ! isUsedInConditional( node.parent ) ) {
159
+ context.report( {
160
+ node,
161
+ messageId: 'usedOutsideConditional',
162
+ data: {
163
+ name: node.value,
164
+ },
165
+ } );
166
+ }
167
+ },
168
+ };
169
+ },
170
+ };
@@ -1,65 +0,0 @@
1
- /**
2
- * External dependencies
3
- */
4
- import { RuleTester } from 'eslint';
5
-
6
- /**
7
- * Internal dependencies
8
- */
9
- import rule from '../is-gutenberg-plugin';
10
-
11
- const ruleTester = new RuleTester( {
12
- parserOptions: {
13
- ecmaVersion: 6,
14
- },
15
- } );
16
-
17
- const ERROR_MESSAGE =
18
- 'The `process.env.IS_GUTENBERG_PLUGIN` constant should only be used as the condition in an if statement or ternary expression.';
19
-
20
- ruleTester.run( 'is-gutenberg-plugin', rule, {
21
- valid: [
22
- { code: `if ( process.env.IS_GUTENBERG_PLUGIN ) {}` },
23
- { code: `if ( ! process.env.IS_GUTENBERG_PLUGIN ) {}` },
24
- {
25
- // Ensure whitespace is ok.
26
- code: `if (
27
- process.env.
28
- IS_GUTENBERG_PLUGIN
29
- ) {}`,
30
- },
31
- { code: `const test = process.env.IS_GUTENBERG_PLUGIN ? foo : bar` },
32
- { code: `const test = ! process.env.IS_GUTENBERG_PLUGIN ? bar : foo` },
33
- {
34
- // Ensure whitespace is ok.
35
- code: `const test = ! process.env.
36
- IS_GUTENBERG_PLUGIN ? bar : foo`,
37
- },
38
- ],
39
- invalid: [
40
- {
41
- code: `if ( IS_GUTENBERG_PLUGIN ) {}`,
42
- errors: [ { message: ERROR_MESSAGE } ],
43
- },
44
- {
45
- code: `if ( window[ 'IS_GUTENBERG_PLUGIN' ] ) {}`,
46
- errors: [ { message: ERROR_MESSAGE } ],
47
- },
48
- {
49
- code: `if ( true ) { process.env.IS_GUTENBERG_PLUGIN === 2 }`,
50
- errors: [ { message: ERROR_MESSAGE } ],
51
- },
52
- {
53
- code: `if ( process.env.IS_GUTENBERG_PLUGIN === 2 ) {}`,
54
- errors: [ { message: ERROR_MESSAGE } ],
55
- },
56
- {
57
- code: `if ( true || process.env.IS_GUTENBERG_PLUGIN === 2 ) {}`,
58
- errors: [ { message: ERROR_MESSAGE } ],
59
- },
60
- {
61
- code: `const isFeatureActive = process.env.IS_GUTENBERG_PLUGIN;`,
62
- errors: [ { message: ERROR_MESSAGE } ],
63
- },
64
- ],
65
- } );
@@ -1,174 +0,0 @@
1
- /**
2
- * Traverse up through the chain of parent AST nodes returning the first parent
3
- * the predicate returns a truthy value for.
4
- *
5
- * @param {Object} sourceNode The AST node to search from.
6
- * @param {Function} predicate A predicate invoked for each parent.
7
- *
8
- * @return {Object | undefined} The first encountered parent node where the predicate
9
- * returns a truthy value.
10
- */
11
- function findParent( sourceNode, predicate ) {
12
- if ( ! sourceNode.parent ) {
13
- return;
14
- }
15
-
16
- if ( predicate( sourceNode.parent ) ) {
17
- return sourceNode.parent;
18
- }
19
-
20
- return findParent( sourceNode.parent, predicate );
21
- }
22
-
23
- /**
24
- * Tests whether the GUTENBERG_PHASE variable is accessed via
25
- * `process.env.GUTENBERG_PHASE`.
26
- *
27
- * @example
28
- * ```js
29
- * // good
30
- * if ( process.env.GUTENBERG_PHASE === 2 ) {
31
- *
32
- * // bad
33
- * if ( GUTENBERG_PHASE === 2 ) {
34
- * ```
35
- *
36
- * @param {Object} node The GUTENBERG_PHASE identifier node.
37
- * @param {Object} context The eslint context object.
38
- */
39
- function testIsAccessedViaProcessEnv( node, context ) {
40
- const parent = node.parent;
41
-
42
- if (
43
- parent &&
44
- parent.type === 'MemberExpression' &&
45
- context.getSource( parent ) === 'process.env.GUTENBERG_PHASE'
46
- ) {
47
- return;
48
- }
49
-
50
- context.report(
51
- node,
52
- 'The `GUTENBERG_PHASE` constant should be accessed using `process.env.GUTENBERG_PHASE`.'
53
- );
54
- }
55
-
56
- /**
57
- * Tests whether the GUTENBERG_PHASE variable is used in a strict binary
58
- * equality expression in a comparison with a number, triggering a
59
- * violation if not.
60
- *
61
- * @example
62
- * ```js
63
- * // good
64
- * if ( process.env.GUTENBERG_PHASE === 2 ) {
65
- *
66
- * // bad
67
- * if ( process.env.GUTENBERG_PHASE >= '2' ) {
68
- * ```
69
- *
70
- * @param {Object} node The GUTENBERG_PHASE identifier node.
71
- * @param {Object} context The eslint context object.
72
- */
73
- function testIsUsedInStrictBinaryExpression( node, context ) {
74
- const parent = findParent(
75
- node,
76
- ( candidate ) => candidate.type === 'BinaryExpression'
77
- );
78
-
79
- if ( parent ) {
80
- const comparisonNode =
81
- node.parent.type === 'MemberExpression' ? node.parent : node;
82
-
83
- // Test for process.env.GUTENBERG_PHASE === <number> or <number> === process.env.GUTENBERG_PHASE.
84
- const hasCorrectOperator = [ '===', '!==' ].includes( parent.operator );
85
- const hasCorrectOperands =
86
- ( parent.left === comparisonNode &&
87
- typeof parent.right.value === 'number' ) ||
88
- ( parent.right === comparisonNode &&
89
- typeof parent.left.value === 'number' );
90
-
91
- if ( hasCorrectOperator && hasCorrectOperands ) {
92
- return;
93
- }
94
- }
95
-
96
- context.report(
97
- node,
98
- 'The `GUTENBERG_PHASE` constant should only be used in a strict equality comparison with a primitive number.'
99
- );
100
- }
101
-
102
- /**
103
- * Tests whether the GUTENBERG_PHASE variable is used as the condition for an
104
- * if statement, triggering a violation if not.
105
- *
106
- * @example
107
- * ```js
108
- * // good
109
- * if ( process.env.GUTENBERG_PHASE === 2 ) {
110
- *
111
- * // bad
112
- * const isFeatureActive = process.env.GUTENBERG_PHASE === 2;
113
- * ```
114
- *
115
- * @param {Object} node The GUTENBERG_PHASE identifier node.
116
- * @param {Object} context The eslint context object.
117
- */
118
- function testIsUsedInIfOrTernary( node, context ) {
119
- const conditionalParent = findParent( node, ( candidate ) =>
120
- [ 'IfStatement', 'ConditionalExpression' ].includes( candidate.type )
121
- );
122
- const binaryParent = findParent(
123
- node,
124
- ( candidate ) => candidate.type === 'BinaryExpression'
125
- );
126
-
127
- if (
128
- conditionalParent &&
129
- binaryParent &&
130
- conditionalParent.test &&
131
- conditionalParent.test.range[ 0 ] === binaryParent.range[ 0 ] &&
132
- conditionalParent.test.range[ 1 ] === binaryParent.range[ 1 ]
133
- ) {
134
- return;
135
- }
136
-
137
- context.report(
138
- node,
139
- 'The `GUTENBERG_PHASE` constant should only be used as part of the condition in an if statement or ternary expression.'
140
- );
141
- }
142
-
143
- module.exports = {
144
- meta: {
145
- type: 'problem',
146
- schema: [],
147
- deprecated: true,
148
- replacedBy: '@wordpress/is-gutenberg-plugin',
149
- },
150
- create( context ) {
151
- return {
152
- Identifier( node ) {
153
- // Bypass any identifiers with a node name different to `GUTENBERG_PHASE`.
154
- if ( node.name !== 'GUTENBERG_PHASE' ) {
155
- return;
156
- }
157
-
158
- testIsAccessedViaProcessEnv( node, context );
159
- testIsUsedInStrictBinaryExpression( node, context );
160
- testIsUsedInIfOrTernary( node, context );
161
- },
162
- Literal( node ) {
163
- // Bypass any identifiers with a node value different to `GUTENBERG_PHASE`.
164
- if ( node.value !== 'GUTENBERG_PHASE' ) {
165
- return;
166
- }
167
-
168
- if ( node.parent && node.parent.type === 'MemberExpression' ) {
169
- testIsAccessedViaProcessEnv( node, context );
170
- }
171
- },
172
- };
173
- },
174
- };
@@ -1,94 +0,0 @@
1
- /**
2
- * Traverse up through the chain of parent AST nodes returning the first parent
3
- * the predicate returns a truthy value for.
4
- *
5
- * @param {Object} sourceNode The AST node to search from.
6
- * @param {Function} predicate A predicate invoked for each parent.
7
- *
8
- * @return {Object | undefined} The first encountered parent node where the predicate
9
- * returns a truthy value.
10
- */
11
- function findParent( sourceNode, predicate ) {
12
- if ( ! sourceNode.parent ) {
13
- return;
14
- }
15
-
16
- if ( predicate( sourceNode.parent ) ) {
17
- return sourceNode.parent;
18
- }
19
-
20
- return findParent( sourceNode.parent, predicate );
21
- }
22
-
23
- /**
24
- * Tests whether the IS_GUTENBERG_PLUGIN variable is used as the condition for an
25
- * if statement or ternary, triggering a violation if not.
26
- *
27
- * @example
28
- * ```js
29
- * // good
30
- * if ( process.env.IS_GUTENBERG_PLUGIN ) {
31
- *
32
- * // bad
33
- * const isFeatureActive = process.env.IS_GUTENBERG_PLUGIN;
34
- * ```
35
- *
36
- * @param {Object} node The IS_GUTENBERG_PLUGIN identifier node.
37
- * @param {Object} context The eslint context object.
38
- */
39
- function isUsedInConditional( node, context ) {
40
- const conditionalParent = findParent( node, ( candidate ) =>
41
- [ 'IfStatement', 'ConditionalExpression' ].includes( candidate.type )
42
- );
43
-
44
- if ( ! conditionalParent ) {
45
- return false;
46
- }
47
-
48
- // Allow for whitespace as prettier sometimes breaks this on separate lines.
49
- const textRegex = /^\s*!?\s*process\s*\.\s*env\s*\.\s*IS_GUTENBERG_PLUGIN$/;
50
- const testSource = context.getSource( conditionalParent.test );
51
-
52
- if ( ! textRegex.test( testSource ) ) {
53
- return false;
54
- }
55
-
56
- return true;
57
- }
58
-
59
- const ERROR_MESSAGE =
60
- 'The `process.env.IS_GUTENBERG_PLUGIN` constant should only be used as the condition in an if statement or ternary expression.';
61
-
62
- module.exports = {
63
- meta: {
64
- type: 'problem',
65
- schema: [],
66
- },
67
- create( context ) {
68
- return {
69
- Identifier( node ) {
70
- // Bypass any identifiers with a node name different to `IS_GUTENBERG_PLUGIN`.
71
- if ( node.name !== 'IS_GUTENBERG_PLUGIN' ) {
72
- return;
73
- }
74
-
75
- if ( ! isUsedInConditional( node, context ) ) {
76
- context.report( node, ERROR_MESSAGE );
77
- }
78
- },
79
- // Check for literals, e.g. when 'IS_GUTENBERG_PLUGIN' is used as a string via something like 'window[ 'IS_GUTENBERG_PLUGIN' ]'.
80
- Literal( node ) {
81
- // Bypass any identifiers with a node value different to `IS_GUTENBERG_PLUGIN`.
82
- if ( node.value !== 'IS_GUTENBERG_PLUGIN' ) {
83
- return;
84
- }
85
-
86
- if ( node.parent && node.parent.type === 'MemberExpression' ) {
87
- if ( ! isUsedInConditional( node, context ) ) {
88
- context.report( node, ERROR_MESSAGE );
89
- }
90
- }
91
- },
92
- };
93
- },
94
- };