Skip to content

Commit b6c1bb7

Browse files
committed
Code Editor: Improve types and fix options handling to avoid double-linting at initialization.
* Refactor how CodeMirror is initialized so that the full settings are provided up-front. This avoids the linting from being applied twice at initialization, the first time with an incorrect configuration. * Add initial TypeScript configuration for core with `npm run typecheck:js`. * Add comprehensive types for code editor files: `code-editor.js`, `javascript-lint.js`, and `htmlhint-kses.js`. * Move code editor scripts from `src/js/_enqueues/vendor/codemirror/` to `src/js/_enqueues/lib/codemirror/`. The CodeMirror library is sourced from the npm package as of r61539. * Remove (deprecated) `esprima.js` from being committed to SVN since in r61539 it was switched to using the npm package as its source. * Move `fakejshint.js` to `src/js/_enqueues/deprecated`. Developed in WordPress#10900 Follow up to r61611, r61539. Props westonruter, jonsurrell, justlevine. See #64662, #48456. Fixes #64661. git-svn-id: https://develop.svn.wordpress.org/trunk@61800 602fd350-edb4-49c9-b593-d223f7449a82
1 parent a58028d commit b6c1bb7

14 files changed

Lines changed: 501 additions & 6894 deletions

File tree

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ wp-tests-config.php
2424
/tests/phpunit/build
2525
/wp-cli.local.yml
2626
/phpstan.neon
27+
/*.tsbuildinfo
2728
/jsdoc
2829
/composer.lock
2930
/vendor

Gruntfile.js

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -339,12 +339,19 @@ module.exports = function(grunt) {
339339
},
340340
{
341341
expand: true,
342-
cwd: SOURCE_DIR + 'js/_enqueues/vendor/codemirror/',
342+
cwd: SOURCE_DIR + 'js/_enqueues/lib/codemirror/',
343343
src: [
344-
'fakejshint.js',
345344
'htmlhint-kses.js',
346345
],
347346
dest: WORKING_DIR + 'wp-includes/js/codemirror/'
347+
},
348+
{
349+
expand: true,
350+
cwd: SOURCE_DIR + 'js/_enqueues/deprecated/',
351+
src: [
352+
'fakejshint.js',
353+
],
354+
dest: WORKING_DIR + 'wp-includes/js/codemirror/'
348355
}
349356
]
350357
},

package-lock.json

Lines changed: 74 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,10 @@
3131
"@playwright/test": "1.56.1",
3232
"@pmmmwh/react-refresh-webpack-plugin": "0.6.1",
3333
"@types/codemirror": "5.60.17",
34+
"@types/espree": "10.1.0",
35+
"@types/htmlhint": "1.1.5",
36+
"@types/jquery": "3.5.33",
37+
"@types/underscore": "1.11.15",
3438
"@wordpress/e2e-test-utils-playwright": "1.33.2",
3539
"@wordpress/prettier-config": "4.33.1",
3640
"@wordpress/scripts": "30.26.2",
@@ -69,6 +73,7 @@
6973
"sinon-test": "~3.1.6",
7074
"source-map-loader": "5.0.0",
7175
"terser-webpack-plugin": "5.3.14",
76+
"typescript": "5.9.3",
7277
"uuid": "13.0.0",
7378
"wait-on": "9.0.3",
7479
"webpack": "5.98.0"
@@ -115,6 +120,7 @@
115120
"grunt": "grunt",
116121
"lint:jsdoc": "wp-scripts lint-js",
117122
"lint:jsdoc:fix": "wp-scripts lint-js --fix",
123+
"typecheck:js": "tsc --build",
118124
"env:start": "node ./tools/local-env/scripts/start.js && node ./tools/local-env/scripts/docker.js run -T --rm php composer update -W",
119125
"env:stop": "node ./tools/local-env/scripts/docker.js down",
120126
"env:restart": "npm run env:stop && npm run env:start",

src/js/_enqueues/vendor/codemirror/fakejshint.js renamed to src/js/_enqueues/deprecated/fakejshint.js

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,16 @@
1-
// JSHINT has some GPL Compatability issues, so we are faking it out and using esprima for validation
2-
// Based on https://github.com/jquery/esprima/blob/gh-pages/demo/validate.js which is MIT licensed
1+
/**
2+
* JSHINT has some GPL Compatability issues, so we are faking it out and using esprima for validation
3+
* Based on https://github.com/jquery/esprima/blob/gh-pages/demo/validate.js which is MIT licensed.
4+
* This is now deprecated in favor of Espree.
5+
*
6+
* @since 4.9.3
7+
* @deprecated 7.0.0
8+
* @output wp-includes/js/codemirror/fakejshint.js
9+
* @see https://core.trac.wordpress.org/ticket/42850
10+
* @see https://core.trac.wordpress.org/ticket/64558
11+
*/
312

13+
/* jshint -W057, -W058 */
414
var fakeJSHINT = new function() {
515
var syntax, errors;
616
var that = this;
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
{
2+
"overrides": [
3+
{
4+
"files": [ "javascript-lint.js" ],
5+
"parserOptions": {
6+
"sourceType": "module",
7+
"ecmaVersion": 2020
8+
}
9+
},
10+
{
11+
"files": [ "htmlhint-kses.js" ],
12+
"parserOptions": {
13+
"ecmaVersion": 2020
14+
}
15+
}
16+
]
17+
}
Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
/* global HTMLHint */
2+
/* eslint no-magic-numbers: ["error", { "ignore": [1] }] */
3+
HTMLHint.addRule( {
4+
id: 'kses',
5+
description: 'Element or attribute cannot be used.',
6+
7+
/**
8+
* Initialize.
9+
*
10+
* @this {import('htmlhint/types').Rule}
11+
* @param {import('htmlhint').HTMLParser} parser - Parser.
12+
* @param {import('htmlhint').Reporter} reporter - Reporter.
13+
* @param {Record<string, Record<string, boolean>>} options - KSES options.
14+
* @return {void}
15+
*/
16+
init: function ( parser, reporter, options ) {
17+
'use strict';
18+
19+
parser.addListener( 'tagstart', ( event ) => {
20+
const tagName = event.tagName.toLowerCase();
21+
if ( ! options[ tagName ] ) {
22+
reporter.error(
23+
`Tag <${ event.tagName }> is not allowed.`,
24+
event.line,
25+
event.col,
26+
this,
27+
event.raw
28+
);
29+
return;
30+
}
31+
32+
const allowedAttributes = options[ tagName ];
33+
const column = event.col + event.tagName.length + 1;
34+
for ( const attribute of event.attrs ) {
35+
if ( ! allowedAttributes[ attribute.name.toLowerCase() ] ) {
36+
reporter.error(
37+
`Tag attribute [${ attribute.raw }] is not allowed.`,
38+
event.line,
39+
column + attribute.index,
40+
this,
41+
attribute.raw
42+
);
43+
}
44+
}
45+
} );
46+
},
47+
} );

src/js/_enqueues/vendor/codemirror/javascript-lint.js renamed to src/js/_enqueues/lib/codemirror/javascript-lint.js

Lines changed: 14 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ import CodeMirror from 'codemirror';
2525
* @see https://www.npmjs.com/package/espree#options
2626
*
2727
* @typedef {Object} SupportedJSHintOptions
28-
* @property {number} [esversion] - "This option is used to specify the ECMAScript version to which the code must adhere."
28+
* @property {import('espree').Options['ecmaVersion']} [esversion] - "This option is used to specify the ECMAScript version to which the code must adhere."
2929
* @property {boolean} [es5] - "This option enables syntax first defined in the ECMAScript 5.1 specification. This includes allowing reserved keywords as object properties."
3030
* @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."
3131
* @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."
@@ -50,19 +50,20 @@ async function validator( text, options ) {
5050
loc: true,
5151
} );
5252
} catch ( error ) {
53+
const enhancedError = /** @type {Error & { lineNumber?: number, column?: number }} */ ( error );
5354
if (
5455
// This is an `EnhancedSyntaxError` in Espree: <https://github.com/brettz9/espree/blob/3c1120280b24f4a5e4c3125305b072fa0dfca22b/packages/espree/lib/espree.js#L48-L54>.
5556
error instanceof SyntaxError &&
56-
typeof error.lineNumber === 'number' &&
57-
typeof error.column === 'number'
57+
typeof enhancedError.lineNumber === 'number' &&
58+
typeof enhancedError.column === 'number'
5859
) {
59-
const line = error.lineNumber - 1;
60-
errors.push( {
60+
const line = enhancedError.lineNumber - 1;
61+
errors.push( /** @type {CodeMirrorLintError} */ ( {
6162
message: error.message,
6263
severity: 'error',
63-
from: CodeMirror.Pos( line, error.column - 1 ),
64-
to: CodeMirror.Pos( line, error.column ),
65-
} );
64+
from: CodeMirror.Pos( line, enhancedError.column - 1 ),
65+
to: CodeMirror.Pos( line, enhancedError.column ),
66+
} ) );
6667
} else {
6768
console.warn( '[CodeMirror] Unable to lint JavaScript:', error ); // jshint ignore:line
6869
}
@@ -80,13 +81,15 @@ CodeMirror.registerHelper( 'lint', 'javascript', validator );
8081
*
8182
* @param {SupportedJSHintOptions} options - Linting options for JSHint.
8283
* @return {{
83-
* ecmaVersion?: number|'latest',
84+
* ecmaVersion?: import('espree').Options['ecmaVersion'],
85+
* sourceType?: 'module'|'script',
8486
* ecmaFeatures?: {
8587
* impliedStrict?: true
8688
* }
8789
* }}
8890
*/
8991
function getEspreeOptions( options ) {
92+
/** @type {{ impliedStrict?: true }} */
9093
const ecmaFeatures = {};
9194
if ( options.strict === 'implied' ) {
9295
ecmaFeatures.impliedStrict = true;
@@ -105,10 +108,10 @@ function getEspreeOptions( options ) {
105108
* @since 7.0.0
106109
*
107110
* @param {SupportedJSHintOptions} options - Options.
108-
* @return {number|'latest'} ECMAScript version.
111+
* @return {import('espree').Options['ecmaVersion']} ECMAScript version.
109112
*/
110113
function getEcmaVersion( options ) {
111-
if ( typeof options.esversion === 'number' ) {
114+
if ( options.esversion ) {
112115
return options.esversion;
113116
}
114117
if ( options.es5 ) {

0 commit comments

Comments
 (0)