Switch from Esprima to Espree for JavaScript linting in CodeMirror.#10806
Switch from Esprima to Espree for JavaScript linting in CodeMirror.#10806westonruter wants to merge 24 commits intoWordPress:trunkfrom
Conversation
Test using WordPress PlaygroundThe changes in this pull request can previewed and tested using a WordPress Playground instance. WordPress Playground is an experimental project that creates a full WordPress instance entirely within the browser. Some things to be aware of
For more details about these limitations and more, check out the Limitations page in the WordPress Playground documentation. |
5c7da3d to
cfd3baf
Compare
Replaces the `esprima`-based validation in the code editor with `espree` to provide support for modern JavaScript (ES6+).
Key Changes:
1. **New Linter Integration:**
* Introduces `src/js/_enqueues/vendor/codemirror/javascript-lint.js` which uses `espree` (v9.6.1) for parsing and error reporting.
* This replaces the previous dependency on the `jshint` and `esprima` scripts. The `espree` module is now loaded via a dynamic import on demand by the new javascript lint integration.
* This custom linter is bundled into the CodeMirror build via `tools/vendors/codemirror-entry.js`.
2. **Script Modules:**
* Registers `espree` as a script module in `src/wp-includes/script-modules.php`.
* Adds a workaround in the `wp-codemirror` registration to ensure `espree` is included in the importmap.
3. **Editor Settings:**
* Updates `wp_get_code_editor_settings()` in `src/wp-includes/general-template.php` to use ES11 defaults.
* Synchronizes JSHint settings from .jshintrc, even though these are not supported by Espree.
4. **Deprecations:**
* Marks `esprima` and `jshint` script handles as deprecated in `src/wp-includes/script-loader.php`.
5. **Build Tools:**
* Updates Webpack configuration (`tools/webpack/codemirror.config.js`) to bundle `espree` as a module.
* Updates `codemirror-entry.js` to use the new local `javascript-lint.js`.
Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com>
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
cfd3baf to
bd2376c
Compare
…into replace-esprima-with-espree
… at 9acd7f0 Co-authored-by: Jon Surrell <jonsurrell@git.wordpress.org> Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com>
…resolution. By adding /* webpackIgnore: true */ to the dynamic import in javascript-lint.js, we prevent Webpack from bundling Espree into a separate chunk. This allows the browser to resolve Espree at runtime using the Import Map. Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com>
The IIFE in javascript-lint.js is removed as it is redundant when bundled by Webpack. Both javascript-lint.js and codemirror-entry.js are updated to use 'const' instead of 'var' for the CodeMirror require, aligning with modern JavaScript practices in the project. Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com>
942ce9e to
99a6994
Compare
Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com>
…vascript-lint.js. Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com>
… at 9acd7f0...3a67800 Co-authored-by: Jon Surrell <jonsurrell@git.wordpress.org> Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com>
…into replace-esprima-with-espree
|
The following accounts have interacted with this PR and/or linked issues. I will continue to update these lists as activity occurs. You can also manually ask me to refresh this list by adding the Core Committers: Use this line as a base for the props when committing in SVN: To understand the WordPress project's expectations around crediting contributors, please review the Contributor Attribution page in the Core Handbook. |
Update src/js/_enqueues/vendor/codemirror/javascript-lint.js and tools/vendors/codemirror-entry.js to use ESM import statements instead of CommonJS require(). This aligns with modern JavaScript standards and ensures compatibility with the project's build process. Co-authored-by: Jon Surrell <jonsurrell@git.wordpress.org> Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com>
tools/vendors/espree-entry.js
Outdated
| @@ -0,0 +1 @@ | |||
| export * from 'espree'; | |||
There was a problem hiding this comment.
Unfortunately, Epsree does not have an ES module included in its distribution.
There was a problem hiding this comment.
Can webpack still use espree as an entry without requiring this file?
const espreeConfig = {
// …snip
entry: {
'espree.min': 'espree',
},
}There was a problem hiding this comment.
Pull request overview
This PR replaces Esprima/JSHint with Espree for JavaScript linting in CodeMirror to address GPL compatibility issues. The change introduces a new custom JavaScript linter that uses the Espree parser (v9.6.1) for syntax validation, registers Espree as a script module, and deprecates the legacy esprima and jshint script handles.
Changes:
- Introduces a custom JavaScript linter using Espree for syntax-only validation (no linting rules)
- Registers Espree as a script module with import map integration via module dependencies
- Updates build tooling to generate separate espree.min.js module and integrate the new linter into codemirror.min.js
Reviewed changes
Copilot reviewed 9 out of 11 changed files in this pull request and generated 2 comments.
Show a summary per file
| File | Description |
|---|---|
| src/js/_enqueues/vendor/codemirror/javascript-lint.js | New custom JavaScript linter using Espree for syntax validation |
| tools/webpack/codemirror.config.js | Adds espree build configuration and restructures codemirror build |
| tools/vendors/espree-entry.js | Entry point for bundling espree as a module |
| tools/vendors/codemirror-entry.js | Updates to use new javascript-lint and ES module imports |
| src/wp-includes/script-modules.php | Registers espree as a script module |
| src/wp-includes/script-loader.php | Adds module dependency for wp-codemirror and deprecates jshint/esprima |
| src/wp-includes/general-template.php | Updates JSHint settings to ES11 and adds clarifying comments |
| tests/phpunit/tests/dependencies/scripts.php | Updates tests to reflect jshint key changes and adds espree to exclusions |
| tests/phpunit/tests/widgets/wpWidgetCustomHtml.php | Removes jshint enqueue test expectation |
| package.json | Adds espree 9.6.1 and @types/codemirror dependencies |
| package-lock.json | Lock file updates for new dependencies |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
…into replace-esprima-with-espree
By using 'espree' directly as the entry point, the proxy entry file 'tools/vendors/espree-entry.js' is no longer needed and can be removed. Co-authored-by: Jon Surrell <jonsurrell@git.wordpress.org> Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com>
|
I noticed that the javascript-lint file isn't correctly watched when testing with This isn't a blocker, but it's always nice when these tasks all work. Grunt goes into watch mode, and changes to That copy/uglify seems redundant and maybe a codemirror watch task isn't set up? |
|
I was testing this out and wanted to work with some script modules but that errors on import/export syntax. I created westonruter#5 to add module support based on |
| const espree = await import( /* webpackIgnore: true */ 'espree' ); | ||
| espree.parse( text, { | ||
| ...getEspreeOptions( options ), | ||
| loc: true, | ||
| } ); |
There was a problem hiding this comment.
I added some logging here (console.log( 'Parsing with: %o', getEspreeOptions( options ) )) to understand the options and I noticed something (note this is with westonruter#5 so adds module config).
The plugin linting seems to be triggered twice, once with what appear to be defaults and again with the expected config. This creates a race, where sometimes on load this is printed:
Parsing with: {ecmaVersion: 'latest', sourceType: 'script', ecmaFeatures: {…}}
Parsing with: {ecmaVersion: 11, sourceType: 'module', ecmaFeatures: {…}}
And the lint is performed as expected. However, sometimes this is the order and the default linting is applied:
Parsing with: {ecmaVersion: 11, sourceType: 'module', ecmaFeatures: {…}}
Parsing with: {ecmaVersion: 'latest', sourceType: 'script', ecmaFeatures: {…}}
This does seem to be happening before this PR, but it seems very consistent. I'm always seeing it run lint with the desired options second.
There was a problem hiding this comment.
I'm seeing that too.
I tried turning off minification and I captured the stack traces for the first and second invocation:
First
codemirror_entry_validator (codemirror.min.js?ver=5.65.20:33014)
startLinting (codemirror.min.js?ver=5.65.20:7501)
(anonymous) (codemirror.min.js?ver=5.65.20:7599)
CodeMirror (codemirror.min.js?ver=5.65.20:19874)
CodeMirror (codemirror.min.js?ver=5.65.20:19818)
fromTextArea (codemirror.min.js?ver=5.65.20:21688)
initialize (code-editor.js?ver=7.0-alpha-61215-src:298)
initCodeEditor (theme-plugin-editor.js?ver=7.0-alpha-61215-src:417)
(anonymous) (theme-plugin-editor.js?ver=7.0-alpha-61215-src:65)
(anonymous) (underscore.js?ver=1.13.7:1091)
setTimeout
(anonymous) (underscore.js?ver=1.13.7:1090)
(anonymous) (underscore.js?ver=1.13.7:76)
executeBound (underscore.js?ver=1.13.7:991)
bound (underscore.js?ver=1.13.7:1011)
init (theme-plugin-editor.js?ver=7.0-alpha-61215-src:64)
(anonymous) (wp-theme-plugin-editor-js-after:2)
mightThrow (jquery.js?ver=3.7.1:3489)
process (jquery.js?ver=3.7.1:3557)
setTimeout
(anonymous) (jquery.js?ver=3.7.1:3602)
fire (jquery.js?ver=3.7.1:3223)
fireWith (jquery.js?ver=3.7.1:3353)
fire (jquery.js?ver=3.7.1:3361)
fire (jquery.js?ver=3.7.1:3223)
fireWith (jquery.js?ver=3.7.1:3353)
ready (jquery.js?ver=3.7.1:3844)
completed (jquery.js?ver=3.7.1:3854)
Second
codemirror_entry_validator (codemirror.min.js?ver=5.65.20:33014)
startLinting (codemirror.min.js?ver=5.65.20:7501)
(anonymous) (codemirror.min.js?ver=5.65.20:7599)
(anonymous) (codemirror.min.js?ver=5.65.20:15878)
setOption (codemirror.min.js?ver=5.65.20:20213)
configureLinting (code-editor.js?ver=7.0-alpha-61215-src:152)
initialize (code-editor.js?ver=7.0-alpha-61215-src:300)
initCodeEditor (theme-plugin-editor.js?ver=7.0-alpha-61215-src:417)
(anonymous) (theme-plugin-editor.js?ver=7.0-alpha-61215-src:65)
(anonymous) (underscore.js?ver=1.13.7:1091)
setTimeout
(anonymous) (underscore.js?ver=1.13.7:1090)
(anonymous) (underscore.js?ver=1.13.7:76)
executeBound (underscore.js?ver=1.13.7:991)
bound (underscore.js?ver=1.13.7:1011)
init (theme-plugin-editor.js?ver=7.0-alpha-61215-src:64)
(anonymous) (wp-theme-plugin-editor-js-after:2)
mightThrow (jquery.js?ver=3.7.1:3489)
process (jquery.js?ver=3.7.1:3557)
setTimeout
(anonymous) (jquery.js?ver=3.7.1:3602)
fire (jquery.js?ver=3.7.1:3223)
fireWith (jquery.js?ver=3.7.1:3353)
fire (jquery.js?ver=3.7.1:3361)
fire (jquery.js?ver=3.7.1:3223)
fireWith (jquery.js?ver=3.7.1:3353)
ready (jquery.js?ver=3.7.1:3844)
completed (jquery.js?ver=3.7.1:3854)
Diff:
@@ -1,10 +1,10 @@
codemirror_entry_validator (codemirror.min.js?ver=5.65.20:33014)
startLinting (codemirror.min.js?ver=5.65.20:7501)
(anonymous) (codemirror.min.js?ver=5.65.20:7599)
-CodeMirror (codemirror.min.js?ver=5.65.20:19874)
-CodeMirror (codemirror.min.js?ver=5.65.20:19818)
-fromTextArea (codemirror.min.js?ver=5.65.20:21688)
-initialize (code-editor.js?ver=7.0-alpha-61215-src:298)
+(anonymous) (codemirror.min.js?ver=5.65.20:15878)
+setOption (codemirror.min.js?ver=5.65.20:20213)
+configureLinting (code-editor.js?ver=7.0-alpha-61215-src:152)
+initialize (code-editor.js?ver=7.0-alpha-61215-src:300)
initCodeEditor (theme-plugin-editor.js?ver=7.0-alpha-61215-src:417)
(anonymous) (theme-plugin-editor.js?ver=7.0-alpha-61215-src:65)
(anonymous) (underscore.js?ver=1.13.7:1091)The second call is happening when the lint option gets updated here:
The two calls are happening here:
wordpress-develop/src/js/_enqueues/wp/code-editor.js
Lines 298 to 300 in 163bc04
So it makes sense that it would be called twice, once with the default config and again with the custom config. The initial implementation is clearly not ideal, as this configureLinting() function should be refactored to construct the lint option earlier to be passed in initially in the call to fromTextArea().
I'll include this work in the follow-up PR to improve the typing for code-editor.js.
sirreal
left a comment
There was a problem hiding this comment.
I've left several comments. I think this is an improvement over trunk and works well in my testing. The things I've flagged seem to already be issues on trunk.
Add MJS and sourceType: module support
…into replace-esprima-with-espree
@sirreal I actually have trouble with This seems like a larger issue to be investigated. |
…CodeMirror. Esprima is no longer maintained, and it does not support the latest JavaScript features in ES11, as Espree does. - **New Linter Integration:** Introduces `src/js/_enqueues/vendor/codemirror/javascript-lint.js` using `espree` for parsing and error reporting, replacing the dependency on `jshint` and `esprima` scripts. - **Script Modules:** Registers `espree` as a script module and leverages the `module_dependencies` argument in `wp_register_script()` to ensure `espree` is available as a dynamic import. - **Editor Settings:** Updates `wp_get_code_editor_settings()` to use ES11 (ECMAScript 2020) defaults and synchronizes JSHint settings from `.jshintrc` for compatibility. - **Editable Extensions:** Adds `.mjs` to the list of editable file extensions for plugins and themes. - **Deprecations:** Marks `esprima` and `jshint` script handles as deprecated. - **Build Tools:** Updates Webpack configuration to bundle `espree` as a module and use the new local `javascript-lint.js`. Developed in #10806 Follow-up to [61587], [61544], [61539], [42547]. Props westonruter, jonsurrell. See #64562, #61500, #48456, #42850. Fixes #64558. git-svn-id: https://develop.svn.wordpress.org/trunk@61611 602fd350-edb4-49c9-b593-d223f7449a82
Previously developed in westonruter#4 as a sub-PR off of #10778
Depends on:
New Linter Integration:
src/js/_enqueues/vendor/codemirror/javascript-lint.jswhich usesespree(v9.6.1) for parsing and error reporting.jshintandesprimascripts. Theespreemodule is now loaded via a dynamic import on demand by the new javascript lint integration.tools/vendors/codemirror-entry.js.Script Modules:
espreeas a script module insrc/wp-includes/script-modules.php.wp-codemirrorregistration to ensureespreeis included in the importmap.Editor Settings:
wp_get_code_editor_settings()insrc/wp-includes/general-template.phpto use ES11 defaults.Deprecations:
esprimaandjshintscript handles as deprecated insrc/wp-includes/script-loader.php.Build Tools:
tools/webpack/codemirror.config.js) to bundleespreeas a module.codemirror-entry.jsto use the new localjavascript-lint.js.Trac ticket: https://core.trac.wordpress.org/ticket/64558
This Pull Request is for code review only. Please keep all other discussion in the Trac ticket. Do not merge this Pull Request. See GitHub Pull Requests for Code Review in the Core Handbook for more details.