Make WordPress Core

Changeset 61611

Timestamp:
02/10/2026 10:29:49 PM (3 weeks ago)
Author:
westonruter
Message:

Code Editor: Switch from Esprima to Espree for JavaScript linting in 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 https://github.com/WordPress/wordpress-develop/pull/10806

Follow-up to [61587], [61544], [61539], [42547].

Props westonruter, jonsurrell.
See #64562, #61500, #48456, #42850.
Fixes #64558.

Location:
trunk
Files:
1 added
10 edited

Legend:

Unmodified
Added
Removed
  • trunk/package-lock.json

    r61539 r61611  
    1717                "csslint": "1.0.5",
    1818                "element-closest": "3.0.2",
     19
    1920                "esprima": "4.0.1",
    2021                "formdata-polyfill": "4.0.10",
     
    4546                "@playwright/test": "1.56.1",
    4647                "@pmmmwh/react-refresh-webpack-plugin": "0.6.1",
     48
    4749                "@wordpress/e2e-test-utils-playwright": "1.33.2",
    4850                "@wordpress/prettier-config": "4.33.1",
     
    51325134            }
    51335135        },
     5136
     5137
     5138
     5139
     5140
     5141
     5142
     5143
     5144
     5145
    51345146        "node_modules/@types/connect": {
    51355147            "version": "3.4.38",
     
    54205432            "integrity": "sha512-Hl219/BT5fLAaz6NDkSuhzasy49dwQS/DSdu4MdggFB8zcXv7vflBI3xp7FEmkmdDkBUI2bPUNeMttp2knYdxw==",
    54215433            "dev": true
     5434
     5435
     5436
     5437
     5438
     5439
     5440
     5441
     5442
     5443
    54225444        },
    54235445        "node_modules/@types/tough-cookie": {
     
    74467468            "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.15.0.tgz",
    74477469            "integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==",
    7448             "dev": true,
    74497470            "license": "MIT",
    74507471            "bin": {
     
    74697490            "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz",
    74707491            "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==",
    7471             "dev": true,
    74727492            "peerDependencies": {
    74737493                "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0"
     
    1328613306            "resolved": "https://registry.npmjs.org/espree/-/espree-9.6.1.tgz",
    1328713307            "integrity": "sha512-oruZaFkjorTpF32kDSI5/75ViwGeZginGGy2NoOSg3Q9bnwlnmDm4HLnkl0RE3n+njDXR037aY1+x58Z/zFdwQ==",
    13288             "dev": true,
    1328913308            "dependencies": {
    1329013309                "acorn": "^8.9.0",
     
    1330313322            "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz",
    1330413323            "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==",
    13305             "dev": true,
    1330613324            "license": "Apache-2.0",
    1330713325            "engines": {
  • trunk/package.json

    r61605 r61611  
    3131        "@playwright/test": "1.56.1",
    3232        "@pmmmwh/react-refresh-webpack-plugin": "0.6.1",
     33
    3334        "@wordpress/e2e-test-utils-playwright": "1.33.2",
    3435        "@wordpress/prettier-config": "4.33.1",
     
    8081        "csslint": "1.0.5",
    8182        "element-closest": "3.0.2",
     83
    8284        "esprima": "4.0.1",
    8385        "formdata-polyfill": "4.0.10",
  • trunk/src/wp-admin/includes/file.php

    r61470 r61611  
    203203        'include',
    204204        'js',
     205
    205206        'json',
    206207        'jsx',
     
    262263        'include',
    263264        'js',
     265
    264266        'json',
    265267        'jsx',
  • trunk/src/wp-includes/general-template.php

    r61497 r61611  
    40704070                    wp_enqueue_script( 'htmlhint' );
    40714071                    wp_enqueue_script( 'csslint' );
    4072                     wp_enqueue_script( 'jshint' );
    40734072                    if ( ! current_user_can( 'unfiltered_html' ) ) {
    40744073                        wp_enqueue_script( 'htmlhint-kses' );
     
    40824081                case 'text/typescript':
    40834082                case 'application/typescript':
    4084                     wp_enqueue_script( 'jshint' );
    40854083                    wp_enqueue_script( 'jsonlint' );
    40864084                    break;
     
    41544152        ),
    41554153        'jshint'     => array(
    4156             // The following are copied from <https://github.com/WordPress/wordpress-develop/blob/4.8.1/.jshintrc>.
    4157             'boss'     => true,
    4158             'curly'    => true,
    4159             'eqeqeq'   => true,
    4160             'eqnull'   => true,
    4161             'es3'      => true,
    4162             'expr'     => true,
    4163             'immed'    => true,
    4164             'noarg'    => true,
    4165             'nonbsp'   => true,
    4166             'onevar'   => true,
    4167             'quotmark' => 'single',
    4168             'trailing' => true,
    4169             'undef'    => true,
    4170             'unused'   => true,
    4171 
    4172             'browser'  => true,
    4173 
    4174             'globals'  => array(
    4175                 '_'        => false,
    4176                 'Backbone' => false,
    4177                 'jQuery'   => false,
    4178                 'JSON'     => false,
    4179                 'wp'       => false,
     4154            'esversion' => 11,
     4155            'module'    => str_ends_with( $args['file'] ?? '', '.mjs' ),
     4156
     4157            // The following JSHint *linting rule* options are copied from
     4158            // <https://github.com/WordPress/wordpress-develop/blob/6.9.0/.jshintrc>.
     4159            // Parsing-related options such as `esversion` (and, in other contexts, `es5`, `es3`, `module`, `strict`)
     4160            // are honored by the Espree-based integration, but these linting-rule options are not interpreted by Espree
     4161            // and are kept only for compatibility/documentation with the original JSHint configuration.
     4162            'boss'      => true,
     4163            'curly'     => true,
     4164            'eqeqeq'    => true,
     4165            'eqnull'    => true,
     4166            'expr'      => true,
     4167            'immed'     => true,
     4168            'noarg'     => true,
     4169            'nonbsp'    => true,
     4170            'quotmark'  => 'single',
     4171            'undef'     => true,
     4172            'unused'    => true,
     4173            'browser'   => true,
     4174            'globals'   => array(
     4175                '_'                 => false,
     4176                'Backbone'          => false,
     4177                'jQuery'            => false,
     4178                'JSON'              => false,
     4179                'wp'                => false,
     4180                'export'            => false,
     4181                'module'            => false,
     4182                'require'           => false,
     4183                'WorkerGlobalScope' => false,
     4184                'self'              => false,
     4185                'OffscreenCanvas'   => false,
     4186                'Promise'           => false,
    41804187            ),
    41814188        ),
     
    42344241                    break;
    42354242                case 'js':
     4243
    42364244                    $type = 'text/javascript';
    42374245                    break;
  • trunk/src/wp-includes/script-loader.php

    r61554 r61611  
    11971197
    11981198    $scripts->add( 'wp-codemirror', '/wp-includes/js/codemirror/codemirror.min.js', array(), '5.65.20' );
     1199
    11991200    $scripts->add( 'csslint', '/wp-includes/js/codemirror/csslint.js', array(), '1.0.5' );
    1200     $scripts->add( 'esprima', '/wp-includes/js/codemirror/esprima.js', array(), '4.0.1' );
    1201     $scripts->add( 'jshint', '/wp-includes/js/codemirror/fakejshint.js', array( 'esprima' ), '2.9.5' );
     1201    $scripts->add( 'esprima', '/wp-includes/js/codemirror/esprima.js', array(), '4.0.1' );
     1202    $scripts->add( 'jshint', '/wp-includes/js/codemirror/fakejshint.js', array( 'esprima' ), '2.9.5' );
    12021203    $scripts->add( 'jsonlint', '/wp-includes/js/codemirror/jsonlint.js', array(), '1.6.3' );
    12031204    $scripts->add( 'htmlhint', '/wp-includes/js/codemirror/htmlhint.js', array(), '1.8.0' );
  • trunk/src/wp-includes/script-modules.php

    r61587 r61611  
    195195        wp_register_script_module( $script_module_id, $path, $module_deps, $script_module_data['version'], $args );
    196196    }
     197
     198
     199
     200
     201
     202
     203
    197204}
    198205
  • trunk/tests/phpunit/tests/dependencies/scripts.php

    r61587 r61611  
    31593159                'eqeqeq',
    31603160                'eqnull',
    3161                 'es3',
     3161                'es',
    31623162                'expr',
    31633163                'immed',
     3164
    31643165                'noarg',
    31653166                'nonbsp',
    3166                 'onevar',
    31673167                'quotmark',
    3168                 'trailing',
    31693168                'undef',
    31703169                'unused',
     
    32433242                'eqeqeq',
    32443243                'eqnull',
    3245                 'es3',
     3244                'es',
    32463245                'expr',
    32473246                'immed',
     3247
    32483248                'noarg',
    32493249                'nonbsp',
    3250                 'onevar',
    32513250                'quotmark',
    3252                 'trailing',
    32533251                'undef',
    32543252                'unused',
     
    33413339                'eqeqeq',
    33423340                'eqnull',
    3343                 'es3',
     3341                'es',
    33443342                'expr',
    33453343                'immed',
     3344
    33463345                'noarg',
    33473346                'nonbsp',
    3348                 'onevar',
    33493347                'quotmark',
    3350                 'trailing',
    33513348                'undef',
    33523349                'unused',
     
    34363433                'eqeqeq',
    34373434                'eqnull',
    3438                 'es3',
     3435                'es',
    34393436                'expr',
    34403437                'immed',
     3438
    34413439                'noarg',
    34423440                'nonbsp',
    3443                 'onevar',
    34443441                'quotmark',
    3445                 'trailing',
    34463442                'undef',
    34473443                'unused',
     
    40214017
    40224018        // Exclude packages that are not registered in WordPress.
    4023         $exclude                   = array( 'react-is', 'json2php' );
     4019        $exclude                   = array( 'react-is', 'json2php' );
    40244020        $package_json_dependencies = array_diff( $package_json_dependencies, $exclude );
    40254021
  • trunk/tests/phpunit/tests/widgets/wpWidgetCustomHtml.php

    r59120 r61611  
    252252        $this->assertTrue( wp_script_is( 'wp-codemirror', 'enqueued' ) );
    253253        $this->assertTrue( wp_script_is( 'csslint', 'enqueued' ) );
    254         $this->assertTrue( wp_script_is( 'jshint', 'enqueued' ) );
    255254        $this->assertTrue( wp_script_is( 'htmlhint', 'enqueued' ) );
    256255    }
  • trunk/tools/vendors/codemirror-entry.js

    r61539 r61611  
    11// Import CodeMirror core to be exposed as window.wp.CodeMirror.
    2 var CodeMirror = require( 'codemirror/lib/codemirror' );
     2;
    33
    44// Keymaps
    5 require( 'codemirror/keymap/emacs' );
    6 require( 'codemirror/keymap/sublime' );
    7 require( 'codemirror/keymap/vim' );
     5;
     6;
     7;
    88
    99// Addons (Hinting)
    10 require( 'codemirror/addon/hint/show-hint' );
    11 require( 'codemirror/addon/hint/anyword-hint' );
    12 require( 'codemirror/addon/hint/css-hint' );
    13 require( 'codemirror/addon/hint/html-hint' );
    14 require( 'codemirror/addon/hint/javascript-hint' );
    15 require( 'codemirror/addon/hint/sql-hint' );
    16 require( 'codemirror/addon/hint/xml-hint' );
     10;
     11;
     12;
     13;
     14;
     15;
     16;
    1717
    1818// Addons (Linting)
    19 require( 'codemirror/addon/lint/lint' );
    20 require( 'codemirror/addon/lint/css-lint' );
    21 require( 'codemirror/addon/lint/html-lint' );
    22 require( 'codemirror/addon/lint/javascript-lint' );
    23 require( 'codemirror/addon/lint/json-lint' );
     19import 'codemirror/addon/lint/lint';
     20import 'codemirror/addon/lint/css-lint';
     21import 'codemirror/addon/lint/html-lint';
     22
     23import '../../src/js/_enqueues/vendor/codemirror/javascript-lint';
     24import 'codemirror/addon/lint/json-lint';
    2425
    2526// Addons (Other)
    26 require( 'codemirror/addon/comment/comment' );
    27 require( 'codemirror/addon/comment/continuecomment' );
    28 require( 'codemirror/addon/fold/xml-fold' );
    29 require( 'codemirror/addon/mode/overlay' );
    30 require( 'codemirror/addon/edit/closebrackets' );
    31 require( 'codemirror/addon/edit/closetag' );
    32 require( 'codemirror/addon/edit/continuelist' );
    33 require( 'codemirror/addon/edit/matchbrackets' );
    34 require( 'codemirror/addon/edit/matchtags' );
    35 require( 'codemirror/addon/edit/trailingspace' );
    36 require( 'codemirror/addon/dialog/dialog' );
    37 require( 'codemirror/addon/display/autorefresh' );
    38 require( 'codemirror/addon/display/fullscreen' );
    39 require( 'codemirror/addon/display/panel' );
    40 require( 'codemirror/addon/display/placeholder' );
    41 require( 'codemirror/addon/display/rulers' );
    42 require( 'codemirror/addon/fold/brace-fold' );
    43 require( 'codemirror/addon/fold/comment-fold' );
    44 require( 'codemirror/addon/fold/foldcode' );
    45 require( 'codemirror/addon/fold/foldgutter' );
    46 require( 'codemirror/addon/fold/indent-fold' );
    47 require( 'codemirror/addon/fold/markdown-fold' );
    48 require( 'codemirror/addon/merge/merge' );
    49 require( 'codemirror/addon/mode/loadmode' );
    50 require( 'codemirror/addon/mode/multiplex' );
    51 require( 'codemirror/addon/mode/simple' );
    52 require( 'codemirror/addon/runmode/runmode' );
    53 require( 'codemirror/addon/runmode/colorize' );
    54 require( 'codemirror/addon/runmode/runmode-standalone' );
    55 require( 'codemirror/addon/scroll/annotatescrollbar' );
    56 require( 'codemirror/addon/scroll/scrollpastend' );
    57 require( 'codemirror/addon/scroll/simplescrollbars' );
    58 require( 'codemirror/addon/search/search' );
    59 require( 'codemirror/addon/search/jump-to-line' );
    60 require( 'codemirror/addon/search/match-highlighter' );
    61 require( 'codemirror/addon/search/matchesonscrollbar' );
    62 require( 'codemirror/addon/search/searchcursor' );
    63 require( 'codemirror/addon/tern/tern' );
    64 require( 'codemirror/addon/tern/worker' );
    65 require( 'codemirror/addon/wrap/hardwrap' );
    66 require( 'codemirror/addon/selection/active-line' );
    67 require( 'codemirror/addon/selection/mark-selection' );
    68 require( 'codemirror/addon/selection/selection-pointer' );
     27;
     28;
     29;
     30;
     31;
     32;
     33;
     34;
     35;
     36;
     37;
     38;
     39;
     40;
     41;
     42;
     43;
     44;
     45;
     46;
     47;
     48;
     49;
     50;
     51;
     52;
     53;
     54;
     55;
     56;
     57;
     58;
     59;
     60;
     61;
     62;
     63;
     64;
     65;
     66;
     67;
     68;
     69;
    6970
    7071// Modes
    71 require( 'codemirror/mode/meta' );
    72 require( 'codemirror/mode/clike/clike' );
    73 require( 'codemirror/mode/css/css' );
    74 require( 'codemirror/mode/diff/diff' );
    75 require( 'codemirror/mode/htmlmixed/htmlmixed' );
    76 require( 'codemirror/mode/http/http' );
    77 require( 'codemirror/mode/javascript/javascript' );
    78 require( 'codemirror/mode/jsx/jsx' );
    79 require( 'codemirror/mode/markdown/markdown' );
    80 require( 'codemirror/mode/gfm/gfm' );
    81 require( 'codemirror/mode/nginx/nginx' );
    82 require( 'codemirror/mode/php/php' );
    83 require( 'codemirror/mode/sass/sass' );
    84 require( 'codemirror/mode/shell/shell' );
    85 require( 'codemirror/mode/sql/sql' );
    86 require( 'codemirror/mode/xml/xml' );
    87 require( 'codemirror/mode/yaml/yaml' );
     72;
     73;
     74;
     75;
     76;
     77;
     78;
     79;
     80;
     81;
     82;
     83;
     84;
     85;
     86;
     87;
     88;
    8889
    8990/**
  • trunk/tools/webpack/codemirror.config.js

    r61544 r61611  
    77module.exports = ( env = { buildTarget: 'src/' } ) => {
    88    const buildTarget = env.buildTarget || 'src/';
     9
    910
    10     return {
     11    const optimization = {
     12        minimize: true,
     13        minimizer: [
     14            new TerserPlugin( {
     15                terserOptions: {
     16                    format: {
     17                        comments: /^!/,
     18                    },
     19                },
     20                extractComments: false,
     21            } ),
     22        ],
     23    };
     24
     25    const codemirrorConfig = {
    1126        target: 'browserslist',
    1227        mode: 'production',
    13         entry: './tools/vendors/codemirror-entry.js',
     28        entry: {
     29            'codemirror.min': './tools/vendors/codemirror-entry.js',
     30        },
    1431        output: {
    15             path: path.resolve( __dirname, '../../', buildTarget, 'wp-includes/js/codemirror' ),
    16             filename: 'codemirror.min.js',
     32            path: ,
     33            filename: '.js',
    1734        },
    18         optimization: {
    19             minimize: true,
    20             minimizer: [
    21                 new TerserPlugin( {
    22                     terserOptions: {
    23                         format: {
    24                             comments: /^!/,
    25                         },
    26                     },
    27                     extractComments: false,
    28                 } ),
    29             ],
    30         },
     35        optimization,
    3136        externals: {
    3237            'csslint': 'window.CSSLint',
    3338            'htmlhint': 'window.HTMLHint',
    34             'jshint': 'window.JSHINT',
    3539            'jsonlint': 'window.jsonlint',
    3640        },
     
    4347        ],
    4448    };
     49
     50
     51
     52
     53
     54
     55
     56
     57
     58
     59
     60
     61
     62
     63
     64
     65
     66
     67
     68
     69
    4570};
Note: See TracChangeset for help on using the changeset viewer.