Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
18 commits
Select commit Hold shift + click to select a range
a722b50
Switch from Esprima to Espree for JavaScript linting in CodeMirror.
westonruter Jan 27, 2026
bd2376c
Simplify javascript-lint module definition and ensure proper types
westonruter Jan 28, 2026
9d5d79a
Merge branch 'trunk' of https://github.com/WordPress/wordpress-develo…
westonruter Jan 31, 2026
daa5e2e
Squash sirreal:scripts/allow-script-module-dependency (https://github…
westonruter Jan 31, 2026
fe846ac
CodeMirror: Use native dynamic import for Espree to allow Import Map …
westonruter Jan 31, 2026
34f5e4c
Leverage the module_dependencies arg to add espree to importmap
westonruter Jan 31, 2026
57cd3b0
Fix minification of espree after debugging
westonruter Jan 31, 2026
99a6994
CodeMirror: Unwrap javascript-lint.js IIFE and modernize with const.
westonruter Jan 31, 2026
0f8878d
CodeMirror: Add since 7.0.0 JSDoc tags to javascript-lint.js.
westonruter Jan 31, 2026
89c8db8
Change method for suppressing JSHint warning for console.warn() in ja…
westonruter Jan 31, 2026
0edaefa
Squash sirreal:scripts/allow-script-module-dependency (https://github…
westonruter Feb 4, 2026
7a8d756
Merge branch 'trunk' of https://github.com/WordPress/wordpress-develo…
westonruter Feb 4, 2026
9e3038d
Convert vendor source files to ESM imports.
westonruter Feb 4, 2026
87cd175
Clarify comment for JSHint rules
westonruter Feb 4, 2026
8ce6f62
Merge branch 'trunk' into replace-esprima-with-espree
westonruter Feb 4, 2026
ca2ff24
Merge branch 'trunk' of https://github.com/WordPress/wordpress-develo…
westonruter Feb 7, 2026
2661c91
Simplify Webpack configuration for Espree.
westonruter Feb 7, 2026
64f01f2
Merge branch 'trunk' into replace-esprima-with-espree
westonruter Feb 9, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
26 changes: 22 additions & 4 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 2 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@
"@lodder/grunt-postcss": "^3.1.1",
"@playwright/test": "1.56.1",
"@pmmmwh/react-refresh-webpack-plugin": "0.6.1",
"@types/codemirror": "5.60.17",
"@wordpress/e2e-test-utils-playwright": "1.33.2",
"@wordpress/prettier-config": "4.33.1",
"@wordpress/scripts": "30.26.2",
Expand Down Expand Up @@ -79,6 +80,7 @@
"core-js-url-browser": "3.6.4",
"csslint": "1.0.5",
"element-closest": "3.0.2",
"espree": "9.6.1",
"esprima": "4.0.1",
"formdata-polyfill": "4.0.10",
"hoverintent": "2.2.1",
Expand Down
121 changes: 121 additions & 0 deletions src/js/_enqueues/vendor/codemirror/javascript-lint.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,121 @@
/**
* CodeMirror JavaScript linter.
*
* @since 7.0.0
*/

import CodeMirror from 'codemirror';

/**
* CodeMirror Lint Error.
*
* @see https://codemirror.net/5/doc/manual.html#addon_lint
*
* @typedef {Object} CodeMirrorLintError
* @property {string} message - Error message.
* @property {'error'} severity - Severity.
* @property {CodeMirror.Position} from - From position.
* @property {CodeMirror.Position} to - To position.
*/

/**
* JSHint options supported by Espree.
*
* @see https://jshint.com/docs/options/
* @see https://www.npmjs.com/package/espree#options
*
* @typedef {Object} SupportedJSHintOptions
* @property {number} [esversion] - "This option is used to specify the ECMAScript version to which the code must adhere."
* @property {boolean} [es5] - "This option enables syntax first defined in the ECMAScript 5.1 specification. This includes allowing reserved keywords as object properties."
* @property {boolean} [es3] - "This option tells JSHint that your code needs to adhere to ECMAScript 3 specification. Use this option if you need your program to be executable in older browsers—such as Internet Explorer 6/7/8/9—and other legacy JavaScript environments."
* @property {boolean} [module] - "This option informs JSHint that the input code describes an ECMAScript 6 module. All module code is interpreted as strict mode code."
* @property {'implied'} [strict] - "This option requires the code to run in ECMAScript 5's strict mode."
*/

/**
* Validates JavaScript.
*
* @since 7.0.0
*
* @param {string} text - Source.
* @param {SupportedJSHintOptions} options - Linting options.
* @returns {Promise<CodeMirrorLintError[]>}
*/
async function validator( text, options ) {
const errors = /** @type {CodeMirrorLintError[]} */ [];
try {
const espree = await import( /* webpackIgnore: true */ 'espree' );
espree.parse( text, {
...getEspreeOptions( options ),
loc: true,
} );
} catch ( error ) {
if (
// This is an `EnhancedSyntaxError` in Espree: <https://github.com/brettz9/espree/blob/3c1120280b24f4a5e4c3125305b072fa0dfca22b/packages/espree/lib/espree.js#L48-L54>.
error instanceof SyntaxError &&
typeof error.lineNumber === 'number' &&
typeof error.column === 'number'
) {
const line = error.lineNumber - 1;
errors.push( {
message: error.message,
severity: 'error',
from: CodeMirror.Pos( line, error.column - 1 ),
to: CodeMirror.Pos( line, error.column ),
} );
} else {
console.warn( '[CodeMirror] Unable to lint JavaScript:', error ); // jshint ignore:line
}
}

return errors;
}

CodeMirror.registerHelper( 'lint', 'javascript', validator );

/**
* Gets the options for Espree from the supported JSHint options.
*
* @since 7.0.0
*
* @param {SupportedJSHintOptions} options - Linting options for JSHint.
* @return {{
* ecmaVersion?: number|'latest',
* ecmaFeatures?: {
* impliedStrict?: true
* }
* }}
*/
function getEspreeOptions( options ) {
const ecmaFeatures = {};
if ( options.strict === 'implied' ) {
ecmaFeatures.impliedStrict = true;
}

return {
ecmaVersion: getEcmaVersion( options ),
sourceType: options.module ? 'module' : 'script',
ecmaFeatures,
};
}

/**
* Gets the ECMAScript version.
*
* @since 7.0.0
*
* @param {SupportedJSHintOptions} options - Options.
* @return {number|'latest'} ECMAScript version.
*/
function getEcmaVersion( options ) {
if ( typeof options.esversion === 'number' ) {
return options.esversion;
}
if ( options.es5 ) {
return 5;
}
if ( options.es3 ) {
return 3;
}
return 'latest';
}
58 changes: 32 additions & 26 deletions src/wp-includes/general-template.php
Original file line number Diff line number Diff line change
Expand Up @@ -4069,7 +4069,6 @@ function wp_enqueue_code_editor( $args ) {
case 'text/x-php':
wp_enqueue_script( 'htmlhint' );
wp_enqueue_script( 'csslint' );
wp_enqueue_script( 'jshint' );
if ( ! current_user_can( 'unfiltered_html' ) ) {
wp_enqueue_script( 'htmlhint-kses' );
}
Expand All @@ -4081,7 +4080,6 @@ function wp_enqueue_code_editor( $args ) {
case 'application/ld+json':
case 'text/typescript':
case 'application/typescript':
wp_enqueue_script( 'jshint' );
wp_enqueue_script( 'jsonlint' );
break;
}
Expand Down Expand Up @@ -4153,30 +4151,38 @@ function wp_get_code_editor_settings( $args ) {
'outline-none' => true,
),
'jshint' => array(
// The following are copied from <https://github.com/WordPress/wordpress-develop/blob/4.8.1/.jshintrc>.
'boss' => true,
'curly' => true,
'eqeqeq' => true,
'eqnull' => true,
'es3' => true,
'expr' => true,
'immed' => true,
'noarg' => true,
'nonbsp' => true,
'onevar' => true,
'quotmark' => 'single',
'trailing' => true,
'undef' => true,
'unused' => true,

'browser' => true,

'globals' => array(
'_' => false,
'Backbone' => false,
'jQuery' => false,
'JSON' => false,
'wp' => false,
'esversion' => 11,

// The following JSHint *linting rule* options are copied from
// <https://github.com/WordPress/wordpress-develop/blob/6.9.0/.jshintrc>.
// Parsing-related options such as `esversion` (and, in other contexts, `es5`, `es3`, `module`, `strict`)
// are honored by the Espree-based integration, but these linting-rule options are not interpreted by Espree
// and are kept only for compatibility/documentation with the original JSHint configuration.
'boss' => true,
'curly' => true,
'eqeqeq' => true,
'eqnull' => true,
'expr' => true,
'immed' => true,
'noarg' => true,
'nonbsp' => true,
'quotmark' => 'single',
'undef' => true,
'unused' => true,
'browser' => true,
'globals' => array(
'_' => false,
'Backbone' => false,
'jQuery' => false,
'JSON' => false,
'wp' => false,
'export' => false,
'module' => false,
'require' => false,
'WorkerGlobalScope' => false,
'self' => false,
'OffscreenCanvas' => false,
'Promise' => false,
),
),
'htmlhint' => array(
Expand Down
5 changes: 3 additions & 2 deletions src/wp-includes/script-loader.php
Original file line number Diff line number Diff line change
Expand Up @@ -1196,9 +1196,10 @@ function wp_default_scripts( $scripts ) {
);

$scripts->add( 'wp-codemirror', '/wp-includes/js/codemirror/codemirror.min.js', array(), '5.65.20' );
did_action( 'init' ) && $scripts->add_data( 'wp-codemirror', 'module_dependencies', array( 'espree' ) );
$scripts->add( 'csslint', '/wp-includes/js/codemirror/csslint.js', array(), '1.0.5' );
$scripts->add( 'esprima', '/wp-includes/js/codemirror/esprima.js', array(), '4.0.1' );
$scripts->add( 'jshint', '/wp-includes/js/codemirror/fakejshint.js', array( 'esprima' ), '2.9.5' );
$scripts->add( 'esprima', '/wp-includes/js/codemirror/esprima.js', array(), '4.0.1' ); // Deprecated. Use 'espree' script module.
$scripts->add( 'jshint', '/wp-includes/js/codemirror/fakejshint.js', array( 'esprima' ), '2.9.5' ); // Deprecated.
$scripts->add( 'jsonlint', '/wp-includes/js/codemirror/jsonlint.js', array(), '1.6.3' );
$scripts->add( 'htmlhint', '/wp-includes/js/codemirror/htmlhint.js', array(), '1.8.0' );
$scripts->add( 'htmlhint-kses', '/wp-includes/js/codemirror/htmlhint-kses.js', array( 'htmlhint' ) );
Expand Down
7 changes: 7 additions & 0 deletions src/wp-includes/script-modules.php
Original file line number Diff line number Diff line change
Expand Up @@ -194,6 +194,13 @@ function wp_default_script_modules() {
$module_deps = $script_module_data['module_dependencies'] ?? array();
wp_register_script_module( $script_module_id, $path, $module_deps, $script_module_data['version'], $args );
}

wp_register_script_module(
'espree',
includes_url( 'js/codemirror/espree.min.js' ),
array(),
'9.6.1'
);
}

/**
Expand Down
18 changes: 5 additions & 13 deletions tests/phpunit/tests/dependencies/scripts.php
Original file line number Diff line number Diff line change
Expand Up @@ -3158,14 +3158,12 @@ public function test_wp_enqueue_code_editor_when_php_file_will_be_passed() {
'curly',
'eqeqeq',
'eqnull',
'es3',
'esversion',
'expr',
'immed',
'noarg',
'nonbsp',
'onevar',
'quotmark',
'trailing',
'undef',
'unused',
'browser',
Expand Down Expand Up @@ -3242,14 +3240,12 @@ public function test_wp_enqueue_code_editor_when_generated_array_by_compact_will
'curly',
'eqeqeq',
'eqnull',
'es3',
'esversion',
'expr',
'immed',
'noarg',
'nonbsp',
'onevar',
'quotmark',
'trailing',
'undef',
'unused',
'browser',
Expand Down Expand Up @@ -3340,14 +3336,12 @@ public function test_wp_enqueue_code_editor_when_generated_array_by_array_merge_
'curly',
'eqeqeq',
'eqnull',
'es3',
'esversion',
'expr',
'immed',
'noarg',
'nonbsp',
'onevar',
'quotmark',
'trailing',
'undef',
'unused',
'browser',
Expand Down Expand Up @@ -3435,14 +3429,12 @@ public function test_wp_enqueue_code_editor_when_simple_array_will_be_passed() {
'curly',
'eqeqeq',
'eqnull',
'es3',
'esversion',
'expr',
'immed',
'noarg',
'nonbsp',
'onevar',
'quotmark',
'trailing',
'undef',
'unused',
'browser',
Expand Down Expand Up @@ -4020,7 +4012,7 @@ static function ( $dependency ) {
);

// Exclude packages that are not registered in WordPress.
$exclude = array( 'react-is', 'json2php' );
$exclude = array( 'react-is', 'json2php', 'espree' );
$package_json_dependencies = array_diff( $package_json_dependencies, $exclude );

/*
Expand Down
Loading
Loading