Skip to content

Commit a88a606

Browse files
committed
Refactor fake-JSHint to pass esversion and better support Espree options
1 parent 1b15d2c commit a88a606

2 files changed

Lines changed: 153 additions & 57 deletions

File tree

Lines changed: 149 additions & 53 deletions
Original file line numberDiff line numberDiff line change
@@ -1,66 +1,162 @@
1-
// JSHINT has some GPL Compatability issues, so we are faking it out and using espree for validation
2-
// Based on https://github.com/jquery/esprima/blob/gh-pages/demo/validate.js which is MIT licensed
1+
/* globals espree: false */
32

4-
( () => {
3+
/**
4+
* JSHINT has some GPL Compatability issues, so we are faking it out and using espree for validation
5+
* Based on https://github.com/jquery/esprima/blob/gh-pages/demo/validate.js which is MIT licensed.
6+
*
7+
* This emulates the JSHint API <https://jshint.com/docs/api/>.
8+
*
9+
* @since 4.9.3
10+
*/
11+
12+
/**
13+
* JSHint options supported by Espree.
14+
*
15+
* @see https://jshint.com/docs/options/
16+
* @see https://www.npmjs.com/package/espree#options
17+
*
18+
* @typedef {Object} SupportedJSHintOptions
19+
* @property {3|5|6|7|8|9|10|11} [esversion] - "This option is used to specify the ECMAScript version to which the code must adhere."
20+
* @property {boolean} [es5] - "This option enables syntax first defined in the ECMAScript 5.1 specification. This includes allowing reserved keywords as object properties."
21+
* @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."
22+
* @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."
23+
* @property {'implied'} [strict] - "This option requires the code to run in ECMAScript 5's strict mode."
24+
*/
25+
26+
/**
27+
* JSHint Error.
28+
*
29+
* @typedef {Object} JSHintError
30+
* @property {number} line - Line number.
31+
* @property {number} character - Column number.
32+
* @property {string} reason - Error message.
33+
* @property {'E'} code - Error code.
34+
*/
35+
36+
window.JSHINT = ( () => {
537
/**
6-
* @typedef {Object} JSHINTError
7-
* @property {number} line - Line number.
8-
* @property {number} character - Column number.
9-
* @property {string} reason - Error message.
10-
* @property {string} code - Error code.
38+
* Collected error(s) during parsing.
39+
*
40+
* @type {JSHintError[]}
1141
*/
42+
const errors = [];
1243

1344
/**
14-
* Fake JSHINT.
45+
* Current options.
46+
*
47+
* @type {SupportedJSHintOptions}
1548
*/
16-
const fakeJSHINT = {
17-
/**
18-
* Collected error(s) during parsing.
19-
*
20-
* @type {JSHINTError[]}
21-
*/
22-
data: [],
49+
let currentOptions = {};
2350

24-
/**
25-
* Converts a SyntaxError to a JSHINT error.
26-
*
27-
* @param {SyntaxError} error - SyntaxError to convert.
28-
* @returns {JSHINTError}
29-
*/
30-
convertError( error ) {
31-
return {
32-
line: error.lineNumber,
33-
character: error.column,
34-
reason: error.message,
35-
code: 'E',
36-
};
37-
},
51+
/**
52+
* Parses JS code to find errors.
53+
*
54+
* @param {string} source - JavaScript source code.
55+
* @param {SupportedJSHintOptions} [options={}] - Linting options.
56+
*/
57+
function parse( source, options = {} ) {
58+
errors.length = 0;
59+
try {
60+
espree.parse( source, {
61+
...getOptions( options ),
62+
loc: true,
63+
} );
64+
} catch ( error ) {
65+
errors.push( convertError( error ) );
66+
}
67+
}
3868

39-
/**
40-
* Parses JS code to find errors.
41-
*
42-
* @param {string} code - JS code to parse.
43-
*/
44-
parse( code ) {
45-
try {
46-
window.espree.parse( code, {
47-
ecmaVersion: 'latest',
48-
loc: true,
49-
sourceType: 'module',
50-
} );
51-
this.data = [];
52-
} catch ( error ) {
53-
this.data.push( this.convertError( error ) );
54-
}
55-
},
56-
};
69+
/**
70+
* Gets the options for Espree from the supported JSHint options.
71+
*
72+
* @param {SupportedJSHintOptions} [options={}] - Linting options.
73+
* @return {{
74+
* ecmaVersion?: number|'latest',
75+
* ecmaFeatures?: {
76+
* impliedStrict?: true
77+
* }
78+
* }}
79+
*/
80+
function getOptions( options ) {
81+
const ecmaFeatures = {};
82+
if ( options.strict === 'implied' ) {
83+
ecmaFeatures.impliedStrict = true;
84+
}
5785

58-
window.JSHINT = ( text ) => {
59-
fakeJSHINT.parse( text );
60-
};
61-
window.JSHINT.data = () => {
6286
return {
63-
errors: fakeJSHINT.data,
87+
ecmaVersion: getEcmaVersion( options ),
88+
sourceType: options.module ? 'module' : 'script',
89+
ecmaFeatures,
90+
};
91+
}
92+
93+
/**
94+
* Converts a SyntaxError to a JSHINT error.
95+
*
96+
* @param {SyntaxError} error - SyntaxError to convert.
97+
* @returns {JSHintError}
98+
*/
99+
function convertError( error ) {
100+
return {
101+
line: error.lineNumber,
102+
character: error.column,
103+
reason: error.message,
104+
code: 'E',
105+
};
106+
}
107+
108+
/**
109+
* Gets the ECMAScript version.
110+
*
111+
* @param {SupportedJSHintOptions} options
112+
* @returns {number|'latest'}
113+
*/
114+
function getEcmaVersion( options ) {
115+
if ( typeof options.esversion === 'number' ) {
116+
return options.esversion;
117+
} else if ( options.es5 ) {
118+
return 5;
119+
} else if ( options.es3 ) {
120+
return 3;
121+
} else {
122+
return 'latest';
123+
}
124+
}
125+
126+
/**
127+
* Parses JS code to find errors.
128+
*
129+
* @param {string} source - JavaScript source code.
130+
* @param {SupportedJSHintOptions} options - Linting options.
131+
*/
132+
function fakeJSHINT( source, options ) {
133+
parse( source, options );
134+
}
135+
136+
/**
137+
* An array of warnings and errors generated by the most recent invocation of JSHINT.
138+
*
139+
* Note: Since Espree does not support warnings, and only will throw one exception when parsing, this array will
140+
* only ever be empty or contain a single error.
141+
*
142+
* @type {JSHintError[]}
143+
*/
144+
fakeJSHINT.errors = errors;
145+
146+
/**
147+
* Generates a report containing details about the most recent invocation of JSHINT.
148+
*
149+
* @returns {{
150+
* options: SupportedJSHintOptions,
151+
* errors: JSHintError[]
152+
* }}
153+
*/
154+
fakeJSHINT.data = () => {
155+
return {
156+
options: currentOptions,
157+
errors,
64158
};
65159
};
160+
161+
return fakeJSHINT;
66162
} )();

src/wp-includes/general-template.php

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4153,22 +4153,22 @@ function wp_get_code_editor_settings( $args ) {
41534153
'outline-none' => true,
41544154
),
41554155
'jshint' => array(
4156-
// The following are copied from <https://github.com/WordPress/wordpress-develop/blob/6.9.0/.jshintrc>.
4156+
// This version is copied from <https://github.com/WordPress/wordpress-develop/blob/6.9.0/.jshintrc>.
4157+
'esversion' => 10,
4158+
4159+
// The remaining options are not supported by Espree, which is used instead of JSHint for licensing reasons.
41574160
'boss' => true,
41584161
'curly' => true,
41594162
'eqeqeq' => true,
41604163
'eqnull' => true,
4161-
'esversion' => 10,
41624164
'expr' => true,
41634165
'immed' => true,
41644166
'noarg' => true,
41654167
'nonbsp' => true,
41664168
'quotmark' => 'single',
41674169
'undef' => true,
41684170
'unused' => true,
4169-
41704171
'browser' => true,
4171-
41724172
'globals' => array(
41734173
'_' => false,
41744174
'Backbone' => false,

0 commit comments

Comments
 (0)