Skip to content

Commit 79e3ac0

Browse files
committed
Implement espree for javascript-lint
1 parent 923fb1f commit 79e3ac0

4 files changed

Lines changed: 95 additions & 61 deletions

File tree

Lines changed: 75 additions & 57 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
1-
/* globals define, CodeMirror, JSHINT */
1+
/* globals define, CodeMirror */
2+
3+
/* jshint esversion: 11 */
24

35
// CodeMirror, copyright (c) by Marijn Haverbeke and others
46
// Distributed under an MIT license: https://codemirror.net/5/LICENSE
@@ -18,74 +20,90 @@
1820
}
1921
} )( function ( CodeMirror ) {
2022
'use strict';
21-
// declare global: JSHINT
2223

2324
async function validator( text, options ) {
24-
// TODO: Await import espree.
25+
const espree = await import( 'espree' );
2526

26-
if ( ! window.JSHINT ) {
27-
if ( window.console ) {
28-
window.console.error(
29-
'Error: window.JSHINT not defined, CodeMirror JavaScript linting cannot run.'
30-
);
31-
}
32-
return [];
33-
}
34-
if ( ! options.indent ) {
35-
// JSHint error.character actually is a column index, this fixes underlining on lines using tabs for indentation
36-
options.indent = 1; // JSHint default value is 4
37-
}
38-
JSHINT( text, options, options.globals );
27+
const errors = [];
28+
try {
29+
espree.parse( text, {
30+
...getEspreeOptions( options ),
31+
loc: true,
32+
} );
33+
} catch ( error ) {
34+
// Note: A lineNumber of 0 causes CodeMirror to log out a warning in the console. This is as desired for a generic Espree error.
3935

40-
var errors = JSHINT.data().errors,
41-
result = [];
42-
if ( errors ) {
43-
parseErrors( errors, result );
36+
const line = error.lineNumber - 1;
37+
const start = error.column - 1;
38+
const end = error.column;
39+
40+
errors.push( {
41+
message: error.message,
42+
severity: 'error',
43+
from: CodeMirror.Pos( line, start ),
44+
to: CodeMirror.Pos( line, end ),
45+
} );
4446
}
45-
return result;
47+
48+
return errors;
4649
}
4750

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

50-
function parseErrors( errors, output ) {
51-
for ( var i = 0; i < errors.length; i++ ) {
52-
var error = errors[ i ];
53-
if ( error ) {
54-
if ( error.line <= 0 ) {
55-
if ( window.console ) {
56-
window.console.warn(
57-
'Cannot display JSHint error (invalid line ' +
58-
error.line +
59-
')',
60-
error
61-
);
62-
}
63-
continue;
64-
}
53+
/**
54+
* JSHint options supported by Espree.
55+
*
56+
* @see https://jshint.com/docs/options/
57+
* @see https://www.npmjs.com/package/espree#options
58+
*
59+
* @typedef {Object} SupportedJSHintOptions
60+
* @property {number} [esversion] - "This option is used to specify the ECMAScript version to which the code must adhere."
61+
* @property {boolean} [es5] - "This option enables syntax first defined in the ECMAScript 5.1 specification. This includes allowing reserved keywords as object properties."
62+
* @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."
63+
* @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."
64+
* @property {'implied'} [strict] - "This option requires the code to run in ECMAScript 5's strict mode."
65+
*/
6566

66-
let start = error.character - 1,
67-
end = start + 1;
68-
if ( error.evidence ) {
69-
const index = error.evidence
70-
.substring( start )
71-
.search( /.\b/ );
72-
if ( index > -1 ) {
73-
end += index;
74-
}
75-
}
67+
/**
68+
* Gets the options for Espree from the supported JSHint options.
69+
*
70+
* @param {SupportedJSHintOptions} options - Linting options for JSHint.
71+
* @return {{
72+
* ecmaVersion?: number|'latest',
73+
* ecmaFeatures?: {
74+
* impliedStrict?: true
75+
* }
76+
* }}
77+
*/
78+
function getEspreeOptions( options ) {
79+
const ecmaFeatures = {};
80+
if ( options.strict === 'implied' ) {
81+
ecmaFeatures.impliedStrict = true;
82+
}
7683

77-
// Convert to format expected by validation service
78-
const hint = {
79-
message: error.reason,
80-
severity: error.code ?
81-
( error.code.startsWith( 'W' ) ? 'warning' : 'error' )
82-
: 'error',
83-
from: CodeMirror.Pos( error.line - 1, start ),
84-
to: CodeMirror.Pos( error.line - 1, end ),
85-
};
84+
return {
85+
ecmaVersion: getEcmaVersion( options ),
86+
sourceType: options.module ? 'module' : 'script',
87+
ecmaFeatures,
88+
};
89+
}
8690

87-
output.push( hint );
88-
}
91+
/**
92+
* Gets the ECMAScript version.
93+
*
94+
* @param {SupportedJSHintOptions} options - Options.
95+
* @return {number|'latest'} ECMAScript version.
96+
*/
97+
function getEcmaVersion( options ) {
98+
if ( typeof options.esversion === 'number' ) {
99+
return options.esversion;
100+
}
101+
if ( options.es5 ) {
102+
return 5;
103+
}
104+
if ( options.es3 ) {
105+
return 3;
89106
}
107+
return 'latest';
90108
}
91109
} );

src/wp-includes/general-template.php

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4045,6 +4045,7 @@ function wp_enqueue_code_editor( $args ) {
40454045

40464046
wp_enqueue_script( 'code-editor' );
40474047
wp_enqueue_style( 'code-editor' );
4048+
wp_enqueue_script_module( 'wp-codemirror' ); // Hack to get importmap printed with espree.
40484049

40494050
if ( isset( $settings['codemirror']['mode'] ) ) {
40504051
$mode = $settings['codemirror']['mode'];
@@ -4069,7 +4070,6 @@ function wp_enqueue_code_editor( $args ) {
40694070
case 'text/x-php':
40704071
wp_enqueue_script( 'htmlhint' );
40714072
wp_enqueue_script( 'csslint' );
4072-
wp_enqueue_script( 'jshint' );
40734073
if ( ! current_user_can( 'unfiltered_html' ) ) {
40744074
wp_enqueue_script( 'htmlhint-kses' );
40754075
}
@@ -4081,7 +4081,6 @@ function wp_enqueue_code_editor( $args ) {
40814081
case 'application/ld+json':
40824082
case 'text/typescript':
40834083
case 'application/typescript':
4084-
wp_enqueue_script( 'jshint' );
40854084
wp_enqueue_script( 'jsonlint' );
40864085
break;
40874086
}

src/wp-includes/script-modules.php

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -195,7 +195,24 @@ function wp_default_script_modules() {
195195
wp_register_script_module( $script_module_id, $path, $module_deps, $script_module_data['version'], $args );
196196
}
197197

198-
// TODO: Register espree.
198+
wp_register_script_module(
199+
'espree',
200+
includes_url( 'js/codemirror/espree.min.js' ),
201+
array(),
202+
'9.6.1'
203+
);
204+
205+
// The following is a workaround for classic scripts not yet being able to depend on modules. See <https://core.trac.wordpress.org/ticket/61500>.
206+
wp_register_script_module(
207+
'wp-codemirror',
208+
'', // An empty string is a hack to cause the dependencies to be printed in the importmap without a dependent script being printed.
209+
array(
210+
array(
211+
'id' => 'espree',
212+
'import' => 'dynamic',
213+
),
214+
)
215+
);
199216
}
200217

201218
/**

tools/vendors/codemirror-entry.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ require( 'codemirror/addon/lint/lint' );
2020
require( 'codemirror/addon/lint/css-lint' );
2121
require( 'codemirror/addon/lint/html-lint' );
2222

23-
require( '../../src/js/_enqueues/vendor/codemirror/javascript-lint' ); // TODO: Change to our own version which uses Espree. No need for fakejshint.js at all then.
23+
require( '../../src/js/_enqueues/vendor/codemirror/javascript-lint' );
2424
require( 'codemirror/addon/lint/json-lint' );
2525

2626
// Addons (Other)

0 commit comments

Comments
 (0)