esast-util-from-js
Version:
estree (and esast) utility to parse from JavaScript
139 lines (127 loc) • 4.42 kB
JavaScript
/**
* @typedef {typeof import('acorn').Parser} ParserClass
* @typedef {import('acorn').Position} Position
* @typedef {import('estree-jsx').Comment} Comment
* @typedef {import('estree-jsx').Program} Program
*/
/**
* @typedef {Uint8Array | string} Value
* Input value
*
* When a typed array, must be UTF-8.
*
* @typedef AcornErrorFields
* Extra fields in acorn errors.
* @property {number} pos
* Index.
* @property {Position} loc
* Acorn position.
*
* @typedef {Error & AcornErrorFields} AcornError
* Acorn error.
*
* @callback Plugin
* Acorn plugin.
* @param {ParserClass} Parser
* Base parser class.
* @returns {ParserClass}
* Resulting parser class.
*
* @typedef {2015 | 2016 | 2017 | 2018 | 2019 | 2020 | 2021 | 2022 | 2023 | 'latest'} Version
* JavaScript version.
*
* `'latest'` is equivalent to the latest supported year.
*
* @typedef Options
* Configuration.
* @property {Version | null | undefined} [version='latest']
* JavaScript version (year between 2015 and 2023 or `'latest'`,
* default: `'latest'`).
*
* When a number, must be a year in the range `2015` and `2023` (both
* including).
* `'latest'` is the same as passing the latest supported year.
*
* > ☢️ **Danger**: `'latest'` is a sliding thing, you could consider it as
* > breaking semver.
* > Pass an actual year to lock that down.
* @property {boolean | null | undefined} [module=false]
* Whether this is a module (ESM) or a script (default: `false`).
* @property {boolean | null | undefined} [allowReturnOutsideFunction=false]
* Whether a return statement is allowed in the top scope (default: `false`).
* @property {boolean | null | undefined} [allowImportExportEverywhere=false]
* Whether import/export statements are allowed in the every scope (default:
* `false`).
* @property {boolean | null | undefined} [allowAwaitOutsideFunction]
* Whether `await` is allowed in the top scope (default: `version >= 2022`).
* @property {boolean | null | undefined} [allowSuperOutsideMethod=false]
* Whether `super` is allowed outside methods (default: `false`).
* @property {boolean | null | undefined} [allowHashBang=false]
* Whether a shell hasbang is allowed (default: `false`).
* @property {Array<Plugin> | null | undefined} [plugins=[]]
* List of acorn plugins (default: `[]`); examples are `acorn-jsx` and
* `acorn-stage3`.
*/
import {Parser} from 'acorn'
import {fromEstree} from 'esast-util-from-estree'
import {VFileMessage} from 'vfile-message'
/**
* Parse JavaScript to an esast.
*
* @param {Value} value
* Serialized JavaScript to parse.
* @param {Options | null | undefined} [options]
* Configuration (optional).
* @returns {Program}
* Program node (as esast).
*/
export function fromJs(value, options) {
const options_ = options || {}
/** @type {ParserClass} */
let parser = Parser
/** @type {Array<Comment>} */
const comments = []
/** @type {Program} */
let tree
if (options_.plugins) {
parser = parser.extend(...options_.plugins)
}
const text =
typeof value === 'string'
? value.toString()
: new TextDecoder().decode(value)
try {
// @ts-expect-error: Acorn looks enough like estree.
tree = parser.parse(text, {
ecmaVersion: options_.version || 'latest',
sourceType: options_.module ? 'module' : 'script',
allowReturnOutsideFunction:
options_.allowReturnOutsideFunction || undefined,
allowImportExportEverywhere:
options_.allowImportExportEverywhere || undefined,
allowAwaitOutsideFunction:
options_.allowAwaitOutsideFunction || undefined,
allowHashBang: options_.allowHashBang || undefined,
allowSuperOutsideMethod: options_.allowSuperOutsideMethod || undefined,
locations: true,
// @ts-expect-error: Acorn looks enough like estree.
onComment: comments
})
} catch (error) {
const cause = /** @type {AcornError} */ (error)
const message = new VFileMessage('Could not parse JavaScript with Acorn', {
cause,
place: {
line: cause.loc.line,
column: cause.loc.column + 1,
offset: cause.pos
},
ruleId: 'acorn',
source: 'esast-util-from-js'
})
message.url = 'https://github.com/syntax-tree/esast-util-from-js#throws'
throw message
}
tree.comments = comments
return fromEstree(tree)
}