diff --git a/.eslintrc.js b/.eslintrc.js
index 0b0c71c39a2664..177f3cf35b8ccf 100644
--- a/.eslintrc.js
+++ b/.eslintrc.js
@@ -83,6 +83,72 @@ const restrictedImports = [
},
];
+const restrictedSyntax = [
+ // NOTE: We can't include the forward slash in our regex or
+ // we'll get a `SyntaxError` (Invalid regular expression: \ at end of pattern)
+ // here. That's why we use \\u002F in the regexes below.
+ {
+ selector:
+ 'ImportDeclaration[source.value=/^@wordpress\\u002F.+\\u002F/]',
+ message: 'Path access on WordPress dependencies is not allowed.',
+ },
+ {
+ selector:
+ 'CallExpression[callee.name="deprecated"] Property[key.name="version"][value.value=/' +
+ majorMinorRegExp +
+ '/]',
+ message:
+ 'Deprecated functions must be removed before releasing this version.',
+ },
+ {
+ selector:
+ 'CallExpression[callee.object.name="page"][callee.property.name="waitFor"]',
+ message:
+ 'This method is deprecated. You should use the more explicit API methods available.',
+ },
+ {
+ selector:
+ 'CallExpression[callee.object.name="page"][callee.property.name="waitForTimeout"]',
+ message: 'Prefer page.waitForSelector instead.',
+ },
+ {
+ selector: 'JSXAttribute[name.name="id"][value.type="Literal"]',
+ message:
+ 'Do not use string literals for IDs; use withInstanceId instead.',
+ },
+ {
+ // Discourage the usage of `Math.random()` as it's a code smell
+ // for UUID generation, for which we already have a higher-order
+ // component: `withInstanceId`.
+ selector:
+ 'CallExpression[callee.object.name="Math"][callee.property.name="random"]',
+ message:
+ 'Do not use Math.random() to generate unique IDs; use withInstanceId instead. (If you’re not generating unique IDs: ignore this message.)',
+ },
+ {
+ selector:
+ 'CallExpression[callee.name="withDispatch"] > :function > BlockStatement > :not(VariableDeclaration,ReturnStatement)',
+ message:
+ 'withDispatch must return an object with consistent keys. Avoid performing logic in `mapDispatchToProps`.',
+ },
+ {
+ selector:
+ 'LogicalExpression[operator="&&"][left.property.name="length"][right.type="JSXElement"]',
+ message:
+ 'Avoid truthy checks on length property rendering, as zero length is rendered verbatim.',
+ },
+];
+
+/** `no-restricted-syntax` rules for components. */
+const restrictedSyntaxComponents = [
+ {
+ selector:
+ 'JSXOpeningElement[name.name="Button"]:not(:has(JSXAttribute[name.name="__experimentalIsFocusable"])) JSXAttribute[name.name="disabled"]',
+ message:
+ '`disabled` used without the `__experimentalIsFocusable` prop. Disabling a control without maintaining focusability can cause accessibility issues, by hiding their presence from screen reader users, or preventing focus from returning to a trigger element. (Ignore this error if you truly mean to disable.)',
+ },
+];
+
module.exports = {
root: true,
extends: [
@@ -92,6 +158,7 @@ module.exports = {
],
globals: {
wp: 'off',
+ globalThis: 'readonly',
},
settings: {
jsdoc: {
@@ -103,8 +170,12 @@ module.exports = {
rules: {
'jest/expect-expect': 'off',
'react/jsx-boolean-value': 'error',
+ 'react/jsx-curly-brace-presence': [
+ 'error',
+ { props: 'never', children: 'never' },
+ ],
'@wordpress/dependency-group': 'error',
- '@wordpress/is-gutenberg-plugin': 'error',
+ '@wordpress/wp-global-usage': 'error',
'@wordpress/react-no-unsafe-timeout': 'error',
'@wordpress/i18n-text-domain': [
'error',
@@ -142,63 +213,7 @@ module.exports = {
disallowTypeAnnotations: false,
},
],
- 'no-restricted-syntax': [
- 'error',
- // NOTE: We can't include the forward slash in our regex or
- // we'll get a `SyntaxError` (Invalid regular expression: \ at end of pattern)
- // here. That's why we use \\u002F in the regexes below.
- {
- selector:
- 'ImportDeclaration[source.value=/^@wordpress\\u002F.+\\u002F/]',
- message:
- 'Path access on WordPress dependencies is not allowed.',
- },
- {
- selector:
- 'CallExpression[callee.name="deprecated"] Property[key.name="version"][value.value=/' +
- majorMinorRegExp +
- '/]',
- message:
- 'Deprecated functions must be removed before releasing this version.',
- },
- {
- selector:
- 'CallExpression[callee.object.name="page"][callee.property.name="waitFor"]',
- message:
- 'This method is deprecated. You should use the more explicit API methods available.',
- },
- {
- selector:
- 'CallExpression[callee.object.name="page"][callee.property.name="waitForTimeout"]',
- message: 'Prefer page.waitForSelector instead.',
- },
- {
- selector: 'JSXAttribute[name.name="id"][value.type="Literal"]',
- message:
- 'Do not use string literals for IDs; use withInstanceId instead.',
- },
- {
- // Discourage the usage of `Math.random()` as it's a code smell
- // for UUID generation, for which we already have a higher-order
- // component: `withInstanceId`.
- selector:
- 'CallExpression[callee.object.name="Math"][callee.property.name="random"]',
- message:
- 'Do not use Math.random() to generate unique IDs; use withInstanceId instead. (If you’re not generating unique IDs: ignore this message.)',
- },
- {
- selector:
- 'CallExpression[callee.name="withDispatch"] > :function > BlockStatement > :not(VariableDeclaration,ReturnStatement)',
- message:
- 'withDispatch must return an object with consistent keys. Avoid performing logic in `mapDispatchToProps`.',
- },
- {
- selector:
- 'LogicalExpression[operator="&&"][left.property.name="length"][right.type="JSXElement"]',
- message:
- 'Avoid truthy checks on length property rendering, as zero length is rendered verbatim.',
- },
- ],
+ 'no-restricted-syntax': [ 'error', ...restrictedSyntax ],
},
overrides: [
{
@@ -248,6 +263,20 @@ module.exports = {
],
},
},
+ {
+ files: [
+ 'packages/*/src/**/*.[tj]s?(x)',
+ 'storybook/stories/**/*.[tj]s?(x)',
+ ],
+ excludedFiles: [ '**/*.native.js' ],
+ rules: {
+ 'no-restricted-syntax': [
+ 'error',
+ ...restrictedSyntax,
+ ...restrictedSyntaxComponents,
+ ],
+ },
+ },
{
files: [
// Components package.
@@ -367,6 +396,7 @@ module.exports = {
rules: {
'no-restricted-syntax': [
'error',
+ ...restrictedSyntax,
{
selector:
':matches(Literal[value=/--wp-admin-theme-/],TemplateElement[value.cooked=/--wp-admin-theme-/])',
diff --git a/.git-blame-ignore-revs b/.git-blame-ignore-revs
index f6527c3de9d978..477cd4fbacb400 100644
--- a/.git-blame-ignore-revs
+++ b/.git-blame-ignore-revs
@@ -18,3 +18,6 @@ c56e8a1910ed74f405b74bbb12fe81dea974e5c3
# Autofix eslint curly rule.
0221522f253e094b277a1485b7a2d186cb172632
+
+# ESLint: Enable react/jsx-curly-brace-presence
+5d4baa9ab5f57d207cc3a048003216a8574574d9
diff --git a/.github/workflows/build-plugin-zip.yml b/.github/workflows/build-plugin-zip.yml
index 149faee274206e..a572074f72c9c8 100644
--- a/.github/workflows/build-plugin-zip.yml
+++ b/.github/workflows/build-plugin-zip.yml
@@ -69,7 +69,7 @@ jobs:
steps:
- name: Checkout code
- uses: actions/checkout@44c2b7a8a4ea60a981eaca3cf939b5f4305c123b # v4.1.5
+ uses: actions/checkout@a5ac7e51b41094c92402da3b24376905380afc29 # v4.1.6
with:
token: ${{ secrets.GUTENBERG_TOKEN }}
show-progress: ${{ runner.debug == '1' && 'true' || 'false' }}
@@ -165,7 +165,7 @@ jobs:
steps:
- name: Checkout code
- uses: actions/checkout@44c2b7a8a4ea60a981eaca3cf939b5f4305c123b # v4.1.5
+ uses: actions/checkout@a5ac7e51b41094c92402da3b24376905380afc29 # v4.1.6
with:
ref: ${{ needs.bump-version.outputs.release_branch || github.ref }}
show-progress: ${{ runner.debug == '1' && 'true' || 'false' }}
@@ -222,7 +222,7 @@ jobs:
steps:
- name: Checkout code
- uses: actions/checkout@44c2b7a8a4ea60a981eaca3cf939b5f4305c123b # v4.1.5
+ uses: actions/checkout@a5ac7e51b41094c92402da3b24376905380afc29 # v4.1.6
with:
fetch-depth: 2
ref: ${{ needs.bump-version.outputs.release_branch }}
@@ -311,14 +311,14 @@ jobs:
if: ${{ endsWith( needs.bump-version.outputs.new_version, '-rc.1' ) }}
steps:
- name: Checkout (for CLI)
- uses: actions/checkout@44c2b7a8a4ea60a981eaca3cf939b5f4305c123b # v4.1.5
+ uses: actions/checkout@a5ac7e51b41094c92402da3b24376905380afc29 # v4.1.6
with:
path: main
ref: trunk
show-progress: ${{ runner.debug == '1' && 'true' || 'false' }}
- name: Checkout (for publishing)
- uses: actions/checkout@44c2b7a8a4ea60a981eaca3cf939b5f4305c123b # v4.1.5
+ uses: actions/checkout@a5ac7e51b41094c92402da3b24376905380afc29 # v4.1.6
with:
path: publish
# Later, we switch this branch in the script that publishes packages.
diff --git a/.github/workflows/bundle-size.yml b/.github/workflows/bundle-size.yml
index 1065421044373b..2e7f2c98305fb2 100644
--- a/.github/workflows/bundle-size.yml
+++ b/.github/workflows/bundle-size.yml
@@ -37,7 +37,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- - uses: actions/checkout@44c2b7a8a4ea60a981eaca3cf939b5f4305c123b # v4.1.5
+ - uses: actions/checkout@a5ac7e51b41094c92402da3b24376905380afc29 # v4.1.6
with:
fetch-depth: 1
show-progress: ${{ runner.debug == '1' && 'true' || 'false' }}
diff --git a/.github/workflows/check-backport-changelog.yml b/.github/workflows/check-backport-changelog.yml
new file mode 100644
index 00000000000000..99d7e1ca5b53a8
--- /dev/null
+++ b/.github/workflows/check-backport-changelog.yml
@@ -0,0 +1,59 @@
+name: Verify Core Backport Changlog
+
+on:
+ pull_request:
+ types: [opened, synchronize, labeled, unlabeled]
+ paths:
+ - 'lib/**'
+ - '!lib/load.php'
+ - '!lib/experiments-page.php'
+ - '!lib/experimental/**'
+ - 'phpunit/**'
+ - '!phpunit/experimental/**'
+ - '!phpunit/blocks/**'
+ - 'packages/**/*.php'
+ - '!packages/block-library/**'
+ - '!packages/e2e-tests/**'
+jobs:
+ check:
+ name: Check CHANGELOG diff
+ runs-on: ubuntu-latest
+ steps:
+ - name: 'Get PR commit count'
+ run: echo "PR_COMMIT_COUNT=$(( ${{ github.event.pull_request.commits }} + 1 ))" >> "${GITHUB_ENV}"
+ - name: Checkout code
+ uses: actions/checkout@a5ac7e51b41094c92402da3b24376905380afc29 # v4.1.6
+ with:
+ ref: ${{ github.event.pull_request.head.ref }}
+ repository: ${{ github.event.pull_request.head.repo.full_name }}
+ fetch-depth: ${{ env.PR_COMMIT_COUNT }}
+ show-progress: ${{ runner.debug == '1' && 'true' || 'false' }}
+ - name: 'Fetch relevant history from origin'
+ run: git fetch origin ${{ github.event.pull_request.base.ref }}
+ - name: Check CHANGELOG status
+ if: ${{ !contains(github.event.pull_request.labels.*.name, 'No Core Sync Required') && !contains(github.event.pull_request.labels.*.name, 'Backport from WordPress Core') }}
+ env:
+ PR_NUMBER: ${{ github.event.number }}
+ run: |
+ changelog_folder="backport-changelog"
+
+ # Find any changelog file that contains the Gutenberg PR link
+ gutenberg_pr_url="https://github\.com/WordPress/gutenberg/pull/${PR_NUMBER}"
+ changelog_file=$(grep -rl "[-*] ${gutenberg_pr_url}" "${changelog_folder}" | head -n 1)
+
+ # Confirm that there is an entry containing the Gutenberg PR link
+ if [[ -z "${changelog_file}" ]]; then
+ echo "Please create a core backport PR and add a file with the path webpack.config.js
when using wp-scripts
to modify the build process to suit your needs.
wp-edit-post
. Additionally, using wp.domReady()
ensures the unregister code runs once the dom is loaded.
+
### Using an allow list
@@ -362,37 +372,37 @@ wp.blocks.getBlockTypes().forEach( function ( blockType ) {
### `allowed_block_types_all`
-_**Note:** Before WordPress 5.8 known as `allowed_block_types`. In the case when you want to support older versions of WordPress you might need a way to detect which filter should be used – the deprecated one vs the new one. The recommended way to proceed is to check if the `WP_Block_Editor_Context` class exists._
+allowed_block_types
, which is now deprecated. If you need to support older versions of WordPress, you might need a way to detect which filter should be used. You can check if allowed_block_types
is safe to use by seeing if the WP_Block_Editor_Context
class exists, which was introduced in 5.8.
+block_categories
, which is now deprecated. If you need to support older versions of WordPress, you might need a way to detect which filter should be used. You can check if block_categories
is safe to use by seeing if the WP_Block_Editor_Context
class exists, which was introduced in 5.8.
+