-
Notifications
You must be signed in to change notification settings - Fork 3.5k
Switch from Esprima to Espree for JavaScript linting in CodeMirror. #10806
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 10 commits
a722b50
bd2376c
9d5d79a
daa5e2e
fe846ac
34f5e4c
57cd3b0
99a6994
0f8878d
89c8db8
0edaefa
7a8d756
9e3038d
87cd175
8ce6f62
ca2ff24
2661c91
64f01f2
f2ee120
5b68233
09a7bab
fbf63ef
2dd7d20
083a8a8
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
| Original file line number | Diff line number | Diff line change | ||||||||
|---|---|---|---|---|---|---|---|---|---|---|
| @@ -0,0 +1,121 @@ | ||||||||||
| /** | ||||||||||
| * CodeMirror JavaScript linter. | ||||||||||
| * | ||||||||||
| * @since 7.0.0 | ||||||||||
| */ | ||||||||||
|
|
||||||||||
| const CodeMirror = require( '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, | ||||||||||
| } ); | ||||||||||
|
Comment on lines
+47
to
+51
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I added some logging here ( The plugin linting seems to be triggered twice, once with what appear to be defaults and again with the expected config. This creates a race, where sometimes on load this is printed: And the lint is performed as expected. However, sometimes this is the order and the default linting is applied: This does seem to be happening before this PR, but it seems very consistent. I'm always seeing it run lint with the desired options second.
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'm seeing that too. I tried turning off minification and I captured the stack traces for the first and second invocation: FirstSecondDiff: @@ -1,10 +1,10 @@
codemirror_entry_validator (codemirror.min.js?ver=5.65.20:33014)
startLinting (codemirror.min.js?ver=5.65.20:7501)
(anonymous) (codemirror.min.js?ver=5.65.20:7599)
-CodeMirror (codemirror.min.js?ver=5.65.20:19874)
-CodeMirror (codemirror.min.js?ver=5.65.20:19818)
-fromTextArea (codemirror.min.js?ver=5.65.20:21688)
-initialize (code-editor.js?ver=7.0-alpha-61215-src:298)
+(anonymous) (codemirror.min.js?ver=5.65.20:15878)
+setOption (codemirror.min.js?ver=5.65.20:20213)
+configureLinting (code-editor.js?ver=7.0-alpha-61215-src:152)
+initialize (code-editor.js?ver=7.0-alpha-61215-src:300)
initCodeEditor (theme-plugin-editor.js?ver=7.0-alpha-61215-src:417)
(anonymous) (theme-plugin-editor.js?ver=7.0-alpha-61215-src:65)
(anonymous) (underscore.js?ver=1.13.7:1091)The second call is happening when the
The two calls are happening here: wordpress-develop/src/js/_enqueues/wp/code-editor.js Lines 298 to 300 in 163bc04
So it makes sense that it would be called twice, once with the default config and again with the custom config. The initial implementation is clearly not ideal, as this I'll include this work in the follow-up PR to improve the typing for
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Issue is fixed in #10900!
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. After implementing TypeScript types for the JS files, I then prompted Gemini to resolve the issue based on what I thought needed to be done, as well as providing our conversation as context. Quite pleased 😄 |
||||||||||
| } 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'; | ||||||||||
| } | ||||||||||
Uh oh!
There was an error while loading. Please reload this page.