|
| 1 | +/* globals define, CodeMirror */ |
| 2 | +/* jshint devel: true */ |
| 3 | +/* jshint esversion: 11 */ |
| 4 | + |
| 5 | +// CodeMirror, copyright (c) by Marijn Haverbeke and others |
| 6 | +// Distributed under an MIT license: https://codemirror.net/5/LICENSE |
| 7 | + |
| 8 | +( function ( mod ) { |
| 9 | + if ( typeof exports === 'object' && typeof module === 'object' ) { |
| 10 | + // CommonJS |
| 11 | + mod( require( 'codemirror' ) ); |
| 12 | + } else if ( typeof define === 'function' && define.amd ) { |
| 13 | + // AMD |
| 14 | + define( [ 'codemirror' ], mod ); |
| 15 | + // Plain browser env |
| 16 | + } else { |
| 17 | + mod( CodeMirror ); |
| 18 | + } |
| 19 | +} )( function ( CodeMirror ) { |
| 20 | + 'use strict'; |
| 21 | + |
| 22 | + /** |
| 23 | + * CodeMirror Lint Error. |
| 24 | + * |
| 25 | + * @see https://codemirror.net/5/doc/manual.html#addon_lint |
| 26 | + * |
| 27 | + * @typedef {Object} CodeMirrorLintError |
| 28 | + * @property {string} message - Error message. |
| 29 | + * @property {'error'} severity - Severity. |
| 30 | + * @property {{line: number, ch: number}} from - From position. |
| 31 | + * @property {{line: number, ch: number}} to - To position. |
| 32 | + */ |
| 33 | + |
| 34 | + /** |
| 35 | + * JSHint options supported by Espree. |
| 36 | + * |
| 37 | + * @see https://jshint.com/docs/options/ |
| 38 | + * @see https://www.npmjs.com/package/espree#options |
| 39 | + * |
| 40 | + * @typedef {Object} SupportedJSHintOptions |
| 41 | + * @property {number} [esversion] - "This option is used to specify the ECMAScript version to which the code must adhere." |
| 42 | + * @property {boolean} [es5] - "This option enables syntax first defined in the ECMAScript 5.1 specification. This includes allowing reserved keywords as object properties." |
| 43 | + * @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." |
| 44 | + * @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." |
| 45 | + * @property {'implied'} [strict] - "This option requires the code to run in ECMAScript 5's strict mode." |
| 46 | + */ |
| 47 | + |
| 48 | + /** |
| 49 | + * Validates JavaScript. |
| 50 | + * |
| 51 | + * @param {string} text - Source. |
| 52 | + * @param {SupportedJSHintOptions} options - Linting options. |
| 53 | + * @returns {Promise<CodeMirrorLintError[]>} |
| 54 | + */ |
| 55 | + async function validator( text, options ) { |
| 56 | + const errors = /** @type {CodeMirrorLintError[]} */ []; |
| 57 | + try { |
| 58 | + const espree = await import( 'espree' ); |
| 59 | + espree.parse( text, { |
| 60 | + ...getEspreeOptions( options ), |
| 61 | + loc: true, |
| 62 | + } ); |
| 63 | + } catch ( error ) { |
| 64 | + if ( |
| 65 | + // This is an `EnhancedSyntaxError` in Espree: <https://github.com/brettz9/espree/blob/3c1120280b24f4a5e4c3125305b072fa0dfca22b/packages/espree/lib/espree.js#L48-L54>. |
| 66 | + error instanceof SyntaxError && |
| 67 | + typeof error.lineNumber === 'number' && |
| 68 | + typeof error.column === 'number' |
| 69 | + ) { |
| 70 | + const line = error.lineNumber - 1; |
| 71 | + errors.push( { |
| 72 | + message: error.message, |
| 73 | + severity: 'error', |
| 74 | + from: { line, ch: error.column - 1 }, |
| 75 | + to: { line, ch: error.column }, |
| 76 | + } ); |
| 77 | + } else { |
| 78 | + console.warn( '[CodeMirror] Unable to lint JavaScript:', error ); |
| 79 | + } |
| 80 | + } |
| 81 | + |
| 82 | + return errors; |
| 83 | + } |
| 84 | + |
| 85 | + CodeMirror.registerHelper( 'lint', 'javascript', validator ); |
| 86 | + |
| 87 | + /** |
| 88 | + * Gets the options for Espree from the supported JSHint options. |
| 89 | + * |
| 90 | + * @param {SupportedJSHintOptions} options - Linting options for JSHint. |
| 91 | + * @return {{ |
| 92 | + * ecmaVersion?: number|'latest', |
| 93 | + * ecmaFeatures?: { |
| 94 | + * impliedStrict?: true |
| 95 | + * } |
| 96 | + * }} |
| 97 | + */ |
| 98 | + function getEspreeOptions( options ) { |
| 99 | + const ecmaFeatures = {}; |
| 100 | + if ( options.strict === 'implied' ) { |
| 101 | + ecmaFeatures.impliedStrict = true; |
| 102 | + } |
| 103 | + |
| 104 | + return { |
| 105 | + ecmaVersion: getEcmaVersion( options ), |
| 106 | + sourceType: options.module ? 'module' : 'script', |
| 107 | + ecmaFeatures, |
| 108 | + }; |
| 109 | + } |
| 110 | + |
| 111 | + /** |
| 112 | + * Gets the ECMAScript version. |
| 113 | + * |
| 114 | + * @param {SupportedJSHintOptions} options - Options. |
| 115 | + * @return {number|'latest'} ECMAScript version. |
| 116 | + */ |
| 117 | + function getEcmaVersion( options ) { |
| 118 | + if ( typeof options.esversion === 'number' ) { |
| 119 | + return options.esversion; |
| 120 | + } |
| 121 | + if ( options.es5 ) { |
| 122 | + return 5; |
| 123 | + } |
| 124 | + if ( options.es3 ) { |
| 125 | + return 3; |
| 126 | + } |
| 127 | + return 'latest'; |
| 128 | + } |
| 129 | +} ); |
0 commit comments