penambahan web socket

This commit is contained in:
2025-09-18 19:01:22 +07:00
parent 1d053646a9
commit d7bb2eb5bb
15070 changed files with 2402916 additions and 0 deletions

No files matched your search

+22
View File
@@ -0,0 +1,22 @@
MIT License
Copyright (c) 2024-present VoidZero Inc. & Contributors
Copyright (c) 2023 Boshen
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
+125
View File
@@ -0,0 +1,125 @@
# Oxc Parser
## Features
### Supports WASM
See https://stackblitz.com/edit/oxc-parser for usage example.
### ESTree
When parsing JS or JSX files, the AST returned is fully conformant with the
[ESTree standard](https://github.com/estree/estree), the same as produced by
[Acorn](https://www.npmjs.com/package/acorn).
When parsing TypeScript, the AST conforms to [@typescript-eslint/typescript-estree](https://www.npmjs.com/package/@typescript-eslint/typescript-estree)'s TS-ESTree format.
If you need all ASTs in the same with-TS-properties format, use the `astType: 'ts'` option.
The only differences between Oxc's AST and ESTree / TS-ESTree are:
- Support for Stage 3 [decorators](https://github.com/tc39/proposal-decorators).
- Support for Stage 3 ECMA features [`import defer`](https://github.com/tc39/proposal-defer-import-eval)
and [`import source`](https://github.com/tc39/proposal-source-phase-imports).
- In TS-ESTree AST, `import.defer(...)` and `import.source(...)` are represented as an `ImportExpression`
with `'defer'` or `'source'` in `phase` field (as in ESTree spec), where TS-ESLint represents these
as a `CallExpression` with `MetaProperty` as its `callee`.
- Addition of a non-standard `hashbang` field to `Program`.
That aside, the AST should completely align with Acorn's ESTree AST or TS-ESLint's TS-ESTree.
Any deviation would be considered a bug.
### AST Types
[@oxc-project/types](https://www.npmjs.com/package/@oxc-project/types) can be used. For example:
```typescript
import { Statement } from '@oxc-project/types';
```
### Visitor
[oxc-walker](https://www.npmjs.com/package/oxc-walker) or [estree-walker](https://www.npmjs.com/package/estree-walker) can be used.
### Fast Mode
By default, Oxc parser does not produce semantic errors where symbols and scopes are needed.
To enable semantic errors, apply the option `showSemanticErrors: true`.
For example,
```js
let foo;
let foo;
```
Does not produce any errors when `showSemanticErrors` is `false`, which is the default behavior.
Fast mode is best suited for parser plugins, where other parts of your build pipeline has already checked for errors.
Please note that turning off fast mode incurs a small performance overhead.
### Returns ESM information.
It is likely that you are writing a parser plugin that requires ESM information.
To avoid walking the AST again, Oxc Parser returns ESM information directly.
This information can be used to rewrite import and exports with the help of [`magic-string`](https://www.npmjs.com/package/magic-string),
without any AST manipulations.
```ts
export interface EcmaScriptModule {
/**
* Has ESM syntax.
*
* i.e. `import` and `export` statements, and `import.meta`.
*
* Dynamic imports `import('foo')` are ignored since they can be used in non-ESM files.
*/
hasModuleSyntax: boolean;
/** Import statements. */
staticImports: Array<StaticImport>;
/** Export statements. */
staticExports: Array<StaticExport>;
/** Dynamic import expressions. */
dynamicImports: Array<DynamicImport>;
/** Span positions` of `import.meta` */
importMetas: Array<Span>;
}
```
## API
```javascript
import oxc from 'oxc-parser';
const code = 'const url: String = /* 🤨 */ import.meta.url;';
// File extension is used to determine which dialect to parse source as.
const filename = 'test.tsx';
const result = oxc.parseSync(filename, code);
// or `await oxc.parseAsync(filename, code)`
// An array of errors, if any.
console.log(result.errors);
// AST and comments.
console.log(result.program, result.comments);
// ESM information - imports, exports, `import.meta`s.
console.log(result.module);
```
### Options
All options are optional.
- `lang`: `'js'` | `'jsx'` | `'ts'` | `'tsx'`. Set language of source. If omitted, language is deduced from file extension.
- `sourceType`: `'script'` | `'module'` | `'unambiguous'`. Set source type. Defaults to `'module'`.
- `astType`: `'js'` | `'ts'`. Set to `'ts'` if you want ASTs of plain JS/JSX files to contain TypeScript-specific properties.
- `range`: `true` | `false`. If `true`, AST nodes contain a `range` field. Defaults to `false`.
- `preserveParens`: `true` | `false`. If `true`, parenthesized expressions are represented by (non-standard) `ParenthesizedExpression` and `TSParenthesizedType` AST nodes. Defaults to `true`.
- `showSemanticErrors`: `true` | `false`. If `true`, check file for semantic errors which parser does not otherwise emit e.g. `let x; let x;`. Has a small performance cost. Defaults to `false`.
+412
View File
@@ -0,0 +1,412 @@
// prettier-ignore
/* eslint-disable */
// @ts-nocheck
/* auto-generated by NAPI-RS */
const { createRequire } = require('node:module')
require = createRequire(__filename)
const { readFileSync } = require('node:fs')
let nativeBinding = null
const loadErrors = []
const isMusl = () => {
let musl = false
if (process.platform === 'linux') {
musl = isMuslFromFilesystem()
if (musl === null) {
musl = isMuslFromReport()
}
if (musl === null) {
musl = isMuslFromChildProcess()
}
}
return musl
}
const isFileMusl = (f) => f.includes('libc.musl-') || f.includes('ld-musl-')
const isMuslFromFilesystem = () => {
try {
return readFileSync('/usr/bin/ldd', 'utf-8').includes('musl')
} catch {
return null
}
}
const isMuslFromReport = () => {
let report = null
if (typeof process.report?.getReport === 'function') {
process.report.excludeNetwork = true
report = process.report.getReport()
}
if (!report) {
return null
}
if (report.header && report.header.glibcVersionRuntime) {
return false
}
if (Array.isArray(report.sharedObjects)) {
if (report.sharedObjects.some(isFileMusl)) {
return true
}
}
return false
}
const isMuslFromChildProcess = () => {
try {
return require('child_process').execSync('ldd --version', { encoding: 'utf8' }).includes('musl')
} catch (e) {
// If we reach this case, we don't know if the system is musl or not, so is better to just fallback to false
return false
}
}
function requireNative() {
if (process.env.NAPI_RS_NATIVE_LIBRARY_PATH) {
try {
nativeBinding = require(process.env.NAPI_RS_NATIVE_LIBRARY_PATH);
} catch (err) {
loadErrors.push(err)
}
} else if (process.platform === 'android') {
if (process.arch === 'arm64') {
try {
return require('./parser.android-arm64.node')
} catch (e) {
loadErrors.push(e)
}
try {
return require('@oxc-parser/binding-android-arm64')
} catch (e) {
loadErrors.push(e)
}
} else if (process.arch === 'arm') {
try {
return require('./parser.android-arm-eabi.node')
} catch (e) {
loadErrors.push(e)
}
try {
return require('@oxc-parser/binding-android-arm-eabi')
} catch (e) {
loadErrors.push(e)
}
} else {
loadErrors.push(new Error(`Unsupported architecture on Android ${process.arch}`))
}
} else if (process.platform === 'win32') {
if (process.arch === 'x64') {
try {
return require('./parser.win32-x64-msvc.node')
} catch (e) {
loadErrors.push(e)
}
try {
return require('@oxc-parser/binding-win32-x64-msvc')
} catch (e) {
loadErrors.push(e)
}
} else if (process.arch === 'ia32') {
try {
return require('./parser.win32-ia32-msvc.node')
} catch (e) {
loadErrors.push(e)
}
try {
return require('@oxc-parser/binding-win32-ia32-msvc')
} catch (e) {
loadErrors.push(e)
}
} else if (process.arch === 'arm64') {
try {
return require('./parser.win32-arm64-msvc.node')
} catch (e) {
loadErrors.push(e)
}
try {
return require('@oxc-parser/binding-win32-arm64-msvc')
} catch (e) {
loadErrors.push(e)
}
} else {
loadErrors.push(new Error(`Unsupported architecture on Windows: ${process.arch}`))
}
} else if (process.platform === 'darwin') {
try {
return require('./parser.darwin-universal.node')
} catch (e) {
loadErrors.push(e)
}
try {
return require('@oxc-parser/binding-darwin-universal')
} catch (e) {
loadErrors.push(e)
}
if (process.arch === 'x64') {
try {
return require('./parser.darwin-x64.node')
} catch (e) {
loadErrors.push(e)
}
try {
return require('@oxc-parser/binding-darwin-x64')
} catch (e) {
loadErrors.push(e)
}
} else if (process.arch === 'arm64') {
try {
return require('./parser.darwin-arm64.node')
} catch (e) {
loadErrors.push(e)
}
try {
return require('@oxc-parser/binding-darwin-arm64')
} catch (e) {
loadErrors.push(e)
}
} else {
loadErrors.push(new Error(`Unsupported architecture on macOS: ${process.arch}`))
}
} else if (process.platform === 'freebsd') {
if (process.arch === 'x64') {
try {
return require('./parser.freebsd-x64.node')
} catch (e) {
loadErrors.push(e)
}
try {
return require('@oxc-parser/binding-freebsd-x64')
} catch (e) {
loadErrors.push(e)
}
} else if (process.arch === 'arm64') {
try {
return require('./parser.freebsd-arm64.node')
} catch (e) {
loadErrors.push(e)
}
try {
return require('@oxc-parser/binding-freebsd-arm64')
} catch (e) {
loadErrors.push(e)
}
} else {
loadErrors.push(new Error(`Unsupported architecture on FreeBSD: ${process.arch}`))
}
} else if (process.platform === 'linux') {
if (process.arch === 'x64') {
if (isMusl()) {
try {
return require('./parser.linux-x64-musl.node')
} catch (e) {
loadErrors.push(e)
}
try {
return require('@oxc-parser/binding-linux-x64-musl')
} catch (e) {
loadErrors.push(e)
}
} else {
try {
return require('./parser.linux-x64-gnu.node')
} catch (e) {
loadErrors.push(e)
}
try {
return require('@oxc-parser/binding-linux-x64-gnu')
} catch (e) {
loadErrors.push(e)
}
}
} else if (process.arch === 'arm64') {
if (isMusl()) {
try {
return require('./parser.linux-arm64-musl.node')
} catch (e) {
loadErrors.push(e)
}
try {
return require('@oxc-parser/binding-linux-arm64-musl')
} catch (e) {
loadErrors.push(e)
}
} else {
try {
return require('./parser.linux-arm64-gnu.node')
} catch (e) {
loadErrors.push(e)
}
try {
return require('@oxc-parser/binding-linux-arm64-gnu')
} catch (e) {
loadErrors.push(e)
}
}
} else if (process.arch === 'arm') {
if (isMusl()) {
try {
return require('./parser.linux-arm-musleabihf.node')
} catch (e) {
loadErrors.push(e)
}
try {
return require('@oxc-parser/binding-linux-arm-musleabihf')
} catch (e) {
loadErrors.push(e)
}
} else {
try {
return require('./parser.linux-arm-gnueabihf.node')
} catch (e) {
loadErrors.push(e)
}
try {
return require('@oxc-parser/binding-linux-arm-gnueabihf')
} catch (e) {
loadErrors.push(e)
}
}
} else if (process.arch === 'riscv64') {
if (isMusl()) {
try {
return require('./parser.linux-riscv64-musl.node')
} catch (e) {
loadErrors.push(e)
}
try {
return require('@oxc-parser/binding-linux-riscv64-musl')
} catch (e) {
loadErrors.push(e)
}
} else {
try {
return require('./parser.linux-riscv64-gnu.node')
} catch (e) {
loadErrors.push(e)
}
try {
return require('@oxc-parser/binding-linux-riscv64-gnu')
} catch (e) {
loadErrors.push(e)
}
}
} else if (process.arch === 'ppc64') {
try {
return require('./parser.linux-ppc64-gnu.node')
} catch (e) {
loadErrors.push(e)
}
try {
return require('@oxc-parser/binding-linux-ppc64-gnu')
} catch (e) {
loadErrors.push(e)
}
} else if (process.arch === 's390x') {
try {
return require('./parser.linux-s390x-gnu.node')
} catch (e) {
loadErrors.push(e)
}
try {
return require('@oxc-parser/binding-linux-s390x-gnu')
} catch (e) {
loadErrors.push(e)
}
} else {
loadErrors.push(new Error(`Unsupported architecture on Linux: ${process.arch}`))
}
} else if (process.platform === 'openharmony') {
if (process.arch === 'arm64') {
try {
return require('./parser.linux-arm64-ohos.node')
} catch (e) {
loadErrors.push(e)
}
try {
return require('@oxc-parser/binding-linux-arm64-ohos')
} catch (e) {
loadErrors.push(e)
}
} else if (process.arch === 'x64') {
try {
return require('./parser.linux-x64-ohos.node')
} catch (e) {
loadErrors.push(e)
}
try {
return require('@oxc-parser/binding-linux-x64-ohos')
} catch (e) {
loadErrors.push(e)
}
} else if (process.arch === 'arm') {
try {
return require('./parser.linux-arm-ohos.node')
} catch (e) {
loadErrors.push(e)
}
try {
return require('@oxc-parser/binding-linux-arm-ohos')
} catch (e) {
loadErrors.push(e)
}
} else {
loadErrors.push(new Error(`Unsupported architecture on OpenHarmony: ${process.arch}`))
}
} else {
loadErrors.push(new Error(`Unsupported OS: ${process.platform}, architecture: ${process.arch}`))
}
}
nativeBinding = requireNative()
if (!nativeBinding || process.env.NAPI_RS_FORCE_WASI) {
try {
nativeBinding = require('./parser.wasi.cjs')
} catch (err) {
if (process.env.NAPI_RS_FORCE_WASI) {
loadErrors.push(err)
}
}
if (!nativeBinding) {
try {
nativeBinding = require('@oxc-parser/binding-wasm32-wasi')
} catch (err) {
if (process.env.NAPI_RS_FORCE_WASI) {
loadErrors.push(err)
}
}
}
}
if (!nativeBinding && globalThis.process?.versions?.["webcontainer"]) {
try {
nativeBinding = require('./webcontainer-fallback.js');
} catch (err) {
loadErrors.push(err)
}
}
if (!nativeBinding) {
if (loadErrors.length > 0) {
throw new Error(
`Cannot find native binding. ` +
`npm has a bug related to optional dependencies (https://github.com/npm/cli/issues/4828). ` +
'Please try `npm i` again after removing both package-lock.json and node_modules directory.',
{ cause: loadErrors }
)
}
throw new Error(`Failed to load native binding`)
}
module.exports = nativeBinding
module.exports.Severity = nativeBinding.Severity
module.exports.ParseResult = nativeBinding.ParseResult
module.exports.ExportExportNameKind = nativeBinding.ExportExportNameKind
module.exports.ExportImportNameKind = nativeBinding.ExportImportNameKind
module.exports.ExportLocalNameKind = nativeBinding.ExportLocalNameKind
module.exports.ImportNameKind = nativeBinding.ImportNameKind
module.exports.parseAsync = nativeBinding.parseAsync
module.exports.parseSync = nativeBinding.parseSync
module.exports.rawTransferSupported = nativeBinding.rawTransferSupported
@@ -0,0 +1,18 @@
// Auto-generated code, DO NOT EDIT DIRECTLY!
// To edit this generated file you have to edit `tasks/ast_tools/src/generators/raw_transfer.rs`.
const BUFFER_SIZE = 2147483616,
BUFFER_ALIGN = 4294967296,
DATA_POINTER_POS_32 = 536870902,
IS_TS_FLAG_POS = 2147483612,
PROGRAM_OFFSET = 0,
SOURCE_LEN_OFFSET = 16;
module.exports = {
BUFFER_SIZE,
BUFFER_ALIGN,
DATA_POINTER_POS_32,
IS_TS_FLAG_POS,
PROGRAM_OFFSET,
SOURCE_LEN_OFFSET,
};
File diff suppressed because it is too large Load diff
File diff suppressed because it is too large Load diff
File diff suppressed because it is too large Load diff
@@ -0,0 +1,198 @@
// Auto-generated code, DO NOT EDIT DIRECTLY!
// To edit this generated file you have to edit `tasks/ast_tools/src/generators/raw_transfer_lazy.rs`.
'use strict';
// Mapping from node type name to node type ID
const NODE_TYPE_IDS_MAP = new Map([
// Leaf nodes
['IdentifierName', 0],
['IdentifierReference', 1],
['BindingIdentifier', 2],
['LabelIdentifier', 3],
['ThisExpression', 4],
['Elision', 5],
['TemplateElement', 6],
['Super', 7],
['Hashbang', 8],
['EmptyStatement', 9],
['DebuggerStatement', 10],
['PrivateIdentifier', 11],
['BooleanLiteral', 12],
['NullLiteral', 13],
['NumericLiteral', 14],
['StringLiteral', 15],
['BigIntLiteral', 16],
['RegExpLiteral', 17],
['JSXOpeningFragment', 18],
['JSXClosingFragment', 19],
['JSXEmptyExpression', 20],
['JSXIdentifier', 21],
['JSXText', 22],
['TSAnyKeyword', 23],
['TSStringKeyword', 24],
['TSBooleanKeyword', 25],
['TSNumberKeyword', 26],
['TSNeverKeyword', 27],
['TSIntrinsicKeyword', 28],
['TSUnknownKeyword', 29],
['TSNullKeyword', 30],
['TSUndefinedKeyword', 31],
['TSVoidKeyword', 32],
['TSSymbolKeyword', 33],
['TSThisType', 34],
['TSObjectKeyword', 35],
['TSBigIntKeyword', 36],
['JSDocUnknownType', 37],
// Non-leaf nodes
['Program', 38],
['ArrayExpression', 39],
['ObjectExpression', 40],
['ObjectProperty', 41],
['TemplateLiteral', 42],
['TaggedTemplateExpression', 43],
['ComputedMemberExpression', 44],
['StaticMemberExpression', 45],
['PrivateFieldExpression', 46],
['CallExpression', 47],
['NewExpression', 48],
['MetaProperty', 49],
['SpreadElement', 50],
['UpdateExpression', 51],
['UnaryExpression', 52],
['BinaryExpression', 53],
['PrivateInExpression', 54],
['LogicalExpression', 55],
['ConditionalExpression', 56],
['AssignmentExpression', 57],
['ArrayAssignmentTarget', 58],
['ObjectAssignmentTarget', 59],
['AssignmentTargetWithDefault', 60],
['AssignmentTargetPropertyIdentifier', 61],
['AssignmentTargetPropertyProperty', 62],
['SequenceExpression', 63],
['AwaitExpression', 64],
['ChainExpression', 65],
['ParenthesizedExpression', 66],
['BlockStatement', 67],
['VariableDeclaration', 68],
['VariableDeclarator', 69],
['ExpressionStatement', 70],
['IfStatement', 71],
['DoWhileStatement', 72],
['WhileStatement', 73],
['ForStatement', 74],
['ForInStatement', 75],
['ForOfStatement', 76],
['ContinueStatement', 77],
['BreakStatement', 78],
['ReturnStatement', 79],
['WithStatement', 80],
['SwitchStatement', 81],
['SwitchCase', 82],
['LabeledStatement', 83],
['ThrowStatement', 84],
['TryStatement', 85],
['CatchClause', 86],
['AssignmentPattern', 87],
['ObjectPattern', 88],
['BindingProperty', 89],
['ArrayPattern', 90],
['Function', 91],
['FormalParameters', 92],
['FunctionBody', 93],
['ArrowFunctionExpression', 94],
['YieldExpression', 95],
['Class', 96],
['ClassBody', 97],
['MethodDefinition', 98],
['PropertyDefinition', 99],
['StaticBlock', 100],
['AccessorProperty', 101],
['ImportExpression', 102],
['ImportDeclaration', 103],
['ImportSpecifier', 104],
['ImportDefaultSpecifier', 105],
['ImportNamespaceSpecifier', 106],
['ImportAttribute', 107],
['ExportNamedDeclaration', 108],
['ExportDefaultDeclaration', 109],
['ExportAllDeclaration', 110],
['ExportSpecifier', 111],
['V8IntrinsicExpression', 112],
['JSXElement', 113],
['JSXOpeningElement', 114],
['JSXClosingElement', 115],
['JSXFragment', 116],
['JSXNamespacedName', 117],
['JSXMemberExpression', 118],
['JSXExpressionContainer', 119],
['JSXAttribute', 120],
['JSXSpreadAttribute', 121],
['JSXSpreadChild', 122],
['TSEnumDeclaration', 123],
['TSEnumBody', 124],
['TSEnumMember', 125],
['TSTypeAnnotation', 126],
['TSLiteralType', 127],
['TSConditionalType', 128],
['TSUnionType', 129],
['TSIntersectionType', 130],
['TSParenthesizedType', 131],
['TSTypeOperator', 132],
['TSArrayType', 133],
['TSIndexedAccessType', 134],
['TSTupleType', 135],
['TSNamedTupleMember', 136],
['TSOptionalType', 137],
['TSRestType', 138],
['TSTypeReference', 139],
['TSQualifiedName', 140],
['TSTypeParameterInstantiation', 141],
['TSTypeParameter', 142],
['TSTypeParameterDeclaration', 143],
['TSTypeAliasDeclaration', 144],
['TSClassImplements', 145],
['TSInterfaceDeclaration', 146],
['TSInterfaceBody', 147],
['TSPropertySignature', 148],
['TSIndexSignature', 149],
['TSCallSignatureDeclaration', 150],
['TSMethodSignature', 151],
['TSConstructSignatureDeclaration', 152],
['TSIndexSignatureName', 153],
['TSInterfaceHeritage', 154],
['TSTypePredicate', 155],
['TSModuleDeclaration', 156],
['TSModuleBlock', 157],
['TSTypeLiteral', 158],
['TSInferType', 159],
['TSTypeQuery', 160],
['TSImportType', 161],
['TSImportTypeQualifiedName', 162],
['TSFunctionType', 163],
['TSConstructorType', 164],
['TSMappedType', 165],
['TSTemplateLiteralType', 166],
['TSAsExpression', 167],
['TSSatisfiesExpression', 168],
['TSTypeAssertion', 169],
['TSImportEqualsDeclaration', 170],
['TSExternalModuleReference', 171],
['TSNonNullExpression', 172],
['Decorator', 173],
['TSExportAssignment', 174],
['TSNamespaceExportDeclaration', 175],
['TSInstantiationExpression', 176],
['JSDocNullableType', 177],
['JSDocNonNullableType', 178],
]);
const NODE_TYPES_COUNT = 179,
LEAF_NODE_TYPES_COUNT = 38;
module.exports = {
NODE_TYPE_IDS_MAP,
NODE_TYPES_COUNT,
LEAF_NODE_TYPES_COUNT,
};
File diff suppressed because it is too large Load diff
+283
View File
@@ -0,0 +1,283 @@
/* auto-generated by NAPI-RS */
/* eslint-disable */
export * from '@oxc-project/types';
export interface Comment {
type: 'Line' | 'Block'
value: string
start: number
end: number
}
export interface ErrorLabel {
message?: string
start: number
end: number
}
export interface OxcError {
severity: Severity
message: string
labels: Array<ErrorLabel>
helpMessage?: string
codeframe?: string
}
export declare const enum Severity {
Error = 'Error',
Warning = 'Warning',
Advice = 'Advice'
}
export declare class ParseResult {
get program(): import("@oxc-project/types").Program
get module(): EcmaScriptModule
get comments(): Array<Comment>
get errors(): Array<OxcError>
}
export interface DynamicImport {
start: number
end: number
moduleRequest: Span
}
export interface EcmaScriptModule {
/**
* Has ESM syntax.
*
* i.e. `import` and `export` statements, and `import.meta`.
*
* Dynamic imports `import('foo')` are ignored since they can be used in non-ESM files.
*/
hasModuleSyntax: boolean
/** Import statements. */
staticImports: Array<StaticImport>
/** Export statements. */
staticExports: Array<StaticExport>
/** Dynamic import expressions. */
dynamicImports: Array<DynamicImport>
/** Span positions` of `import.meta` */
importMetas: Array<Span>
}
export interface ExportExportName {
kind: ExportExportNameKind
name?: string
start?: number
end?: number
}
export declare const enum ExportExportNameKind {
/** `export { name } */
Name = 'Name',
/** `export default expression` */
Default = 'Default',
/** `export * from "mod" */
None = 'None'
}
export interface ExportImportName {
kind: ExportImportNameKind
name?: string
start?: number
end?: number
}
export declare const enum ExportImportNameKind {
/** `export { name } */
Name = 'Name',
/** `export * as ns from "mod"` */
All = 'All',
/** `export * from "mod"` */
AllButDefault = 'AllButDefault',
/** Does not have a specifier. */
None = 'None'
}
export interface ExportLocalName {
kind: ExportLocalNameKind
name?: string
start?: number
end?: number
}
export declare const enum ExportLocalNameKind {
/** `export { name } */
Name = 'Name',
/** `export default expression` */
Default = 'Default',
/**
* If the exported value is not locally accessible from within the module.
* `export default function () {}`
*/
None = 'None'
}
export interface ImportName {
kind: ImportNameKind
name?: string
start?: number
end?: number
}
export declare const enum ImportNameKind {
/** `import { x } from "mod"` */
Name = 'Name',
/** `import * as ns from "mod"` */
NamespaceObject = 'NamespaceObject',
/** `import defaultExport from "mod"` */
Default = 'Default'
}
/**
* Parse asynchronously.
*
* Note: This function can be slower than `parseSync` due to the overhead of spawning a thread.
*/
export declare function parseAsync(filename: string, sourceText: string, options?: ParserOptions | undefined | null): Promise<ParseResult>
export interface ParserOptions {
/** Treat the source text as `js`, `jsx`, `ts`, `tsx` or `dts`. */
lang?: 'js' | 'jsx' | 'ts' | 'tsx' | 'dts'
/** Treat the source text as `script` or `module` code. */
sourceType?: 'script' | 'module' | 'unambiguous' | undefined
/**
* Return an AST which includes TypeScript-related properties, or excludes them.
*
* `'js'` is default for JS / JSX files.
* `'ts'` is default for TS / TSX files.
* The type of the file is determined from `lang` option, or extension of provided `filename`.
*/
astType?: 'js' | 'ts'
/**
* Controls whether the `range` property is included on AST nodes.
* The `range` property is a `[number, number]` which indicates the start/end offsets
* of the node in the file contents.
*
* @default false
*/
range?: boolean
/**
* Emit `ParenthesizedExpression` and `TSParenthesizedType` in AST.
*
* If this option is true, parenthesized expressions are represented by
* (non-standard) `ParenthesizedExpression` and `TSParenthesizedType` nodes that
* have a single `expression` property containing the expression inside parentheses.
*
* @default true
*/
preserveParens?: boolean
/**
* Produce semantic errors with an additional AST pass.
* Semantic errors depend on symbols and scopes, where the parser does not construct.
* This adds a small performance overhead.
*
* @default false
*/
showSemanticErrors?: boolean
}
/** Parse synchronously. */
export declare function parseSync(filename: string, sourceText: string, options?: ParserOptions | undefined | null): ParseResult
/** Returns `true` if raw transfer is supported on this platform. */
export declare function rawTransferSupported(): boolean
export interface Span {
start: number
end: number
}
export interface StaticExport {
start: number
end: number
entries: Array<StaticExportEntry>
}
export interface StaticExportEntry {
start: number
end: number
moduleRequest?: ValueSpan
/** The name under which the desired binding is exported by the module`. */
importName: ExportImportName
/** The name used to export this binding by this module. */
exportName: ExportExportName
/** The name that is used to locally access the exported value from within the importing module. */
localName: ExportLocalName
/**
* Whether the export is a TypeScript `export type`.
*
* Examples:
*
* ```ts
* export type * from 'mod';
* export type * as ns from 'mod';
* export type { foo };
* export { type foo }:
* export type { foo } from 'mod';
* ```
*/
isType: boolean
}
export interface StaticImport {
/** Start of import statement. */
start: number
/** End of import statement. */
end: number
/**
* Import source.
*
* ```js
* import { foo } from "mod";
* // ^^^
* ```
*/
moduleRequest: ValueSpan
/**
* Import specifiers.
*
* Empty for `import "mod"`.
*/
entries: Array<StaticImportEntry>
}
export interface StaticImportEntry {
/**
* The name under which the desired binding is exported by the module.
*
* ```js
* import { foo } from "mod";
* // ^^^
* import { foo as bar } from "mod";
* // ^^^
* ```
*/
importName: ImportName
/**
* The name that is used to locally access the imported value from within the importing module.
* ```js
* import { foo } from "mod";
* // ^^^
* import { foo as bar } from "mod";
* // ^^^
* ```
*/
localName: ValueSpan
/**
* Whether this binding is for a TypeScript type-only import.
*
* `true` for the following imports:
* ```ts
* import type { foo } from "mod";
* import { type foo } from "mod";
* ```
*/
isType: boolean
}
export interface ValueSpan {
value: string
start: number
end: number
}
+109
View File
@@ -0,0 +1,109 @@
'use strict';
const bindings = require('./bindings.js');
const { wrap } = require('./wrap.cjs');
const rawTransferSupported = require('./raw-transfer/supported.js');
const { parseSync: parseSyncBinding, parseAsync: parseAsyncBinding } = bindings;
module.exports.ParseResult = bindings.ParseResult;
module.exports.ExportExportNameKind = bindings.ExportExportNameKind;
module.exports.ExportImportNameKind = bindings.ExportImportNameKind;
module.exports.ExportLocalNameKind = bindings.ExportLocalNameKind;
module.exports.ImportNameKind = bindings.ImportNameKind;
module.exports.Severity = bindings.Severity;
module.exports.parseSync = parseSync;
module.exports.parseAsync = parseAsync;
module.exports.experimentalGetLazyVisitor = experimentalGetLazyVisitor;
module.exports.rawTransferSupported = rawTransferSupported;
// Lazily loaded as needed
let parseSyncRaw = null,
parseAsyncRaw,
parseSyncLazy = null,
parseAsyncLazy,
Visitor;
/**
* Lazy-load code related to raw transfer.
* @returns {undefined}
*/
function loadRawTransfer() {
if (parseSyncRaw === null) {
({ parseSyncRaw, parseAsyncRaw } = require('./raw-transfer/eager.js'));
}
}
/**
* Lazy-load code related to raw transfer lazy deserialization.
* @returns {undefined}
*/
function loadRawTransferLazy() {
if (parseSyncLazy === null) {
({ parseSyncLazy, parseAsyncLazy, Visitor } = require('./raw-transfer/lazy.js'));
}
}
/**
* Parse JS/TS source synchronously on current thread.
*
* @param {string} filename - Filename
* @param {string} sourceText - Source text of file
* @param {Object|undefined} options - Parsing options
* @returns {Object} - Object with property getters for `program`, `module`, `comments`, and `errors`
* @throws {Error} - If `experimentalRawTransfer` or `experimentalLazy` option is enabled,
* and raw transfer is not supported on this platform
*/
function parseSync(filename, sourceText, options) {
if (options?.experimentalRawTransfer) {
loadRawTransfer();
return parseSyncRaw(filename, sourceText, options);
}
if (options?.experimentalLazy) {
loadRawTransferLazy();
return parseSyncLazy(filename, sourceText, options);
}
return wrap(parseSyncBinding(filename, sourceText, options));
}
/**
* Parse JS/TS source asynchronously on a separate thread.
*
* Note that not all of the workload can happen on a separate thread.
* Parsing on Rust side does happen in a separate thread, but deserialization of the AST to JS objects
* has to happen on current thread. This synchronous deserialization work typically outweighs
* the asynchronous parsing by a factor of between 3 and 20.
*
* i.e. the majority of the workload cannot be parallelized by using this method.
*
* Generally `parseSync` is preferable to use as it does not have the overhead of spawning a thread.
* If you need to parallelize parsing multiple files, it is recommended to use worker threads.
*
* @param {string} filename - Filename
* @param {string} sourceText - Source text of file
* @param {Object|undefined} options - Parsing options
* @returns {Object} - Object with property getters for `program`, `module`, `comments`, and `errors`
* @throws {Error} - If `experimentalRawTransfer` or `experimentalLazy` option is enabled,
* and raw transfer is not supported on this platform
*/
async function parseAsync(filename, sourceText, options) {
if (options?.experimentalRawTransfer) {
loadRawTransfer();
return await parseAsyncRaw(filename, sourceText, options);
}
if (options?.experimentalLazy) {
loadRawTransferLazy();
return await parseAsyncLazy(filename, sourceText, options);
}
return wrap(await parseAsyncBinding(filename, sourceText, options));
}
/**
* Get `Visitor` class to construct visitors with.
* @returns {function} - `Visitor` class
*/
function experimentalGetLazyVisitor() {
loadRawTransferLazy();
return Visitor;
}
+123
View File
@@ -0,0 +1,123 @@
{
"name": "oxc-parser",
"version": "0.87.0",
"type": "commonjs",
"main": "index.js",
"browser": "wasm.mjs",
"engines": {
"node": ">=20.0.0"
},
"description": "Oxc Parser Node API",
"keywords": [
"oxc",
"parser"
],
"author": "Boshen and oxc contributors",
"license": "MIT",
"homepage": "https://oxc.rs",
"bugs": "https://github.com/oxc-project/oxc/issues",
"repository": {
"type": "git",
"url": "git+https://github.com/oxc-project/oxc.git",
"directory": "napi/parser"
},
"funding": {
"url": "https://github.com/sponsors/Boshen"
},
"files": [
"index.d.ts",
"index.js",
"wrap.cjs",
"wrap.mjs",
"wasm.mjs",
"bindings.js",
"webcontainer-fallback.js",
"generated/constants.js",
"generated/deserialize/js.js",
"generated/deserialize/ts.js",
"generated/lazy/constructors.js",
"generated/lazy/types.js",
"generated/lazy/walk.js",
"raw-transfer/common.js",
"raw-transfer/eager.js",
"raw-transfer/lazy.js",
"raw-transfer/lazy-common.js",
"raw-transfer/node-array.js",
"raw-transfer/supported.js",
"raw-transfer/visitor.js"
],
"publishConfig": {
"registry": "https://registry.npmjs.org/",
"access": "public"
},
"dependencies": {
"@oxc-project/types": "^0.87.0"
},
"devDependencies": {
"@codspeed/vitest-plugin": "^4.0.0",
"@napi-rs/wasm-runtime": "1.0.3",
"@vitest/browser": "3.2.4",
"esbuild": "^0.25.0",
"playwright": "^1.51.0",
"tinypool": "^2.0.0",
"typescript": "5.9.2",
"vitest": "3.2.4"
},
"napi": {
"binaryName": "parser",
"packageName": "@oxc-parser/binding",
"targets": [
"x86_64-pc-windows-msvc",
"aarch64-pc-windows-msvc",
"x86_64-unknown-linux-gnu",
"x86_64-unknown-linux-musl",
"x86_64-unknown-freebsd",
"aarch64-unknown-linux-gnu",
"aarch64-unknown-linux-musl",
"armv7-unknown-linux-gnueabihf",
"armv7-unknown-linux-musleabihf",
"s390x-unknown-linux-gnu",
"riscv64gc-unknown-linux-gnu",
"x86_64-apple-darwin",
"aarch64-apple-darwin",
"aarch64-linux-android",
"wasm32-wasip1-threads"
],
"wasm": {
"browser": {
"fs": false
}
},
"dtsHeaderFile": "header.js"
},
"optionalDependencies": {
"@oxc-parser/binding-win32-x64-msvc": "0.87.0",
"@oxc-parser/binding-win32-arm64-msvc": "0.87.0",
"@oxc-parser/binding-linux-x64-gnu": "0.87.0",
"@oxc-parser/binding-linux-x64-musl": "0.87.0",
"@oxc-parser/binding-freebsd-x64": "0.87.0",
"@oxc-parser/binding-linux-arm64-gnu": "0.87.0",
"@oxc-parser/binding-linux-arm64-musl": "0.87.0",
"@oxc-parser/binding-linux-arm-gnueabihf": "0.87.0",
"@oxc-parser/binding-linux-arm-musleabihf": "0.87.0",
"@oxc-parser/binding-linux-s390x-gnu": "0.87.0",
"@oxc-parser/binding-linux-riscv64-gnu": "0.87.0",
"@oxc-parser/binding-darwin-x64": "0.87.0",
"@oxc-parser/binding-darwin-arm64": "0.87.0",
"@oxc-parser/binding-android-arm64": "0.87.0",
"@oxc-parser/binding-wasm32-wasi": "0.87.0"
},
"scripts": {
"build-dev": "napi build --platform --js bindings.js",
"build-test": "pnpm run build-dev --profile coverage",
"build": "pnpm run build-dev --features allocator --release",
"postbuild-dev": "node patch.mjs",
"build-wasi": "pnpm run build-dev --release --target wasm32-wasip1-threads",
"build-npm-dir": "rm -rf npm-dir && napi create-npm-dirs --npm-dir npm-dir && pnpm napi artifacts --npm-dir npm-dir --output-dir .",
"build-browser-bundle": "node build-browser-bundle.mjs",
"test": "tsc && pnpm run test-node run",
"test-node": "vitest --dir ./test",
"test-browser": "vitest -c vitest.config.browser.mts",
"bench": "vitest bench --run ./bench.bench.mjs"
}
}
@@ -0,0 +1,285 @@
'use strict';
const os = require('node:os');
const rawTransferSupported = require('./supported.js');
const {
parseSyncRaw: parseSyncRawBinding,
parseAsyncRaw: parseAsyncRawBinding,
getBufferOffset,
} = require('../bindings.js');
const { BUFFER_SIZE, BUFFER_ALIGN, IS_TS_FLAG_POS } = require('../generated/constants.js');
module.exports = {
parseSyncRawImpl,
parseAsyncRawImpl,
prepareRaw,
isJsAst,
returnBufferToCache,
};
// Throw an error if running on a platform which raw transfer doesn't support.
//
// Note: This module is lazy-loaded only when user calls `parseSync` or `parseAsync` with
// `experimentalRawTransfer` or `experimentalLazy` options, or calls `experimentalGetLazyVisitor`.
if (!rawTransferSupported()) {
throw new Error(
'`experimentalRawTransfer` and `experimentalLazy` options are not supported ' +
'on 32-bit or big-endian systems, versions of NodeJS prior to v22.0.0, ' +
'versions of Deno prior to v2.0.0, or other runtimes',
);
}
/**
* Parse JS/TS source synchronously on current thread using raw transfer.
*
* Convert the buffer returned by Rust to a JS object with provided `convert` function.
*
* This function contains logic shared by both `parseSyncRaw` and `parseSyncLazy`.
*
* @param {string} filename - Filename
* @param {string} sourceText - Source text of file
* @param {Object|undefined} options - Parsing options
* @param {function} convert - Function to convert the buffer returned from Rust into a JS object
* @returns {Object} - The return value of `convert`
*/
function parseSyncRawImpl(filename, sourceText, options, convert) {
const { buffer, sourceByteLen } = prepareRaw(sourceText);
parseSyncRawBinding(filename, buffer, sourceByteLen, options);
return convert(buffer, sourceText, sourceByteLen);
}
// User should not schedule more async tasks than there are available CPUs, as it hurts performance,
// but it's a common mistake in async JS code to do exactly that.
//
// That anti-pattern looks like this when applied to Oxc:
//
// ```js
// const asts = await Promise.all(
// files.map(
// async (filename) => {
// const sourceText = await fs.readFile(filename, 'utf8');
// const ast = await oxc.parseAsync(filename, sourceText);
// return ast;
// }
// )
// );
// ```
//
// In most cases, that'd just result in a bit of degraded performance, and higher memory use because
// of loading sources into memory prematurely.
//
// However, raw transfer uses a 6 GiB buffer for each parsing operation.
// Most of the memory pages in those buffers are never touched, so this does not consume a huge amount
// of physical memory, but it does still consume virtual memory.
//
// If we allowed creating a large number of 6 GiB buffers simultaneously, it would quickly consume
// virtual memory space and risk memory exhaustion. The code above would exhaust all of bottom half
// (heap) of 48-bit virtual memory space if `files.length >= 21_845`. This is not a number which
// is unrealistic in real world code.
//
// To guard against this possibility, we implement a simple queue.
// No more than `os.availableParallelism()` files can be parsed simultaneously, and any further calls to
// `parseAsyncRaw` will be put in a queue, to execute once other tasks complete.
//
// Fallback to `os.cpus().length` on versions of NodeJS prior to v18.14.0, which do not support
// `os.availableParallelism`.
let availableCores = os.availableParallelism ? os.availableParallelism() : os.cpus().length;
const queue = [];
/**
* Parse JS/TS source asynchronously using raw transfer.
*
* Convert the buffer returned by Rust to a JS object with provided `convert` function.
*
* Queues up parsing operations if more calls than number of CPU cores (see above).
*
* This function contains logic shared by both `parseAsyncRaw` and `parseAsyncLazy`.
*
* @param {string} filename - Filename
* @param {string} sourceText - Source text of file
* @param {Object|undefined} options - Parsing options
* @param {function} convert - Function to convert the buffer returned from Rust into a JS object
* @returns {Object} - The return value of `convert`
*/
async function parseAsyncRawImpl(filename, sourceText, options, convert) {
// Wait for a free CPU core if all CPUs are currently busy.
//
// Note: `availableCores` is NOT decremented if have to wait in the queue first,
// and NOT incremented when parsing completes and it runs next task in the queue.
//
// This is to avoid a race condition if `parseAsyncRaw` is called during the microtick in between
// `resolve` being called below, and the promise resolving here. In that case the new task could
// start running, and then the promise resolves, and the queued task also starts running.
// We'd then have `availableParallelism() + 1` tasks running simultaneously. Potentially, this could
// happen repeatedly, with the number of tasks running simultaneously ever-increasing.
if (availableCores === 0) {
// All CPU cores are busy. Put this task in queue and wait for capacity to become available.
await new Promise((resolve, _) => {
queue.push(resolve);
});
} else {
// A CPU core is available. Mark core as busy, and run parsing now.
availableCores--;
}
// Parse
const { buffer, sourceByteLen } = prepareRaw(sourceText);
await parseAsyncRawBinding(filename, buffer, sourceByteLen, options);
const data = convert(buffer, sourceText, sourceByteLen);
// Free the CPU core
if (queue.length > 0) {
// Some further tasks waiting in queue. Run the next one.
// Do not increment `availableCores` (see above).
const resolve = queue.shift();
resolve();
} else {
// No tasks waiting in queue. This CPU is now free.
availableCores++;
}
return data;
}
const ARRAY_BUFFER_SIZE = BUFFER_SIZE + BUFFER_ALIGN;
const ONE_GIB = 1 << 30;
// We keep a cache of buffers for raw transfer, so we can reuse them as much as possible.
//
// When processing multiple files, it's ideal if can reuse an existing buffer, as it's more likely to
// be warm in CPU cache, it avoids allocations, and it saves work for the garbage collector.
//
// However, we also don't want to keep a load of large buffers around indefinitely using up memory,
// if they're not going to be used again.
//
// We have no knowledge of what pattern over time user may process files in (could be lots in quick
// succession, or more occasionally in a long-running process). So we try to use flexible caching
// strategy which is adaptable to many usage patterns.
//
// We use a 2-tier cache.
// Tier 1 uses strong references, tier 2 uses weak references.
//
// When parsing is complete and the buffer is no longer in use, push it to `buffers` (tier 1 cache).
// Set a timer to clear the cache when no activity for 10 seconds.
//
// When the timer expires, move all the buffers from tier 1 cache into `oldBuffers` (tier 2).
// They are stored there as `WeakRef`s, so the garbage collector is free to reclaim them.
//
// On the next call to `parseSync` or `parseAsync`, promote any buffers in tier 2 cache which were not
// already garbage collected back into tier 1 cache. This is on assumption that parsing one file
// indicates parsing as a whole is an ongoing process, and there will likely be further calls to
// `parseSync` / `parseAsync` in future.
//
// The weak tier 2 cache is because V8 does not necessarily free memory as soon as it's able to be
// freed. We don't want to block it from freeing memory, but if it's not done that yet, there's no
// point creating a new buffer, when one already exists.
const CLEAR_BUFFERS_TIMEOUT = 10_000; // 10 seconds
const buffers = [], oldBuffers = [];
let clearBuffersTimeout = null;
const textEncoder = new TextEncoder();
/**
* Get a buffer (from cache if possible), and copy source text into it.
*
* @param {string} sourceText - Source text of file
* @returns {Object} - Object of form `{ buffer, sourceByteLen }`.
* - `buffer`: `Uint8Array` containing the AST in raw form.
* - `sourceByteLen`: Length of source text in UTF-8 bytes
* (which may not be equal to `sourceText.length` if source contains non-ASCII characters).
*/
function prepareRaw(sourceText) {
// Cancel timeout for clearing buffers
if (clearBuffersTimeout !== null) {
clearTimeout(clearBuffersTimeout);
clearBuffersTimeout = null;
}
// Revive any discarded buffers which have not yet been garbage collected
if (oldBuffers.length > 0) {
const revivedBuffers = [];
for (let oldBuffer of oldBuffers) {
oldBuffer = oldBuffer.deref();
if (oldBuffer !== undefined) revivedBuffers.push(oldBuffer);
}
oldBuffers.length = 0;
if (revivedBuffers.length > 0) buffers.unshift(...revivedBuffers);
}
// Reuse existing buffer, or create a new one
const buffer = buffers.length > 0 ? buffers.pop() : createBuffer();
// Write source into start of buffer.
// `TextEncoder` cannot write into a `Uint8Array` larger than 1 GiB,
// so create a view into buffer of this size to write into.
const sourceBuffer = new Uint8Array(buffer.buffer, buffer.byteOffset, ONE_GIB);
const { read, written: sourceByteLen } = textEncoder.encodeInto(sourceText, sourceBuffer);
if (read !== sourceText.length) throw new Error('Failed to write source text into buffer');
return { buffer, sourceByteLen };
}
/**
* Get if AST should be parsed as JS or TS.
* Rust side sets a `bool` in this position in buffer which is `true` if TS.
*
* @param {Uint8Array} buffer - Buffer containing AST in raw form
* @returns {boolean} - `true` if AST is JS, `false` if TS
*/
function isJsAst(buffer) {
return buffer[IS_TS_FLAG_POS] === 0;
}
/**
* Return buffer to cache, to be reused.
* Set a timer to clear buffers.
*
* @param {Uint8Array} buffer - Buffer
* @returns {undefined}
*/
function returnBufferToCache(buffer) {
buffers.push(buffer);
if (clearBuffersTimeout !== null) clearTimeout(clearBuffersTimeout);
clearBuffersTimeout = setTimeout(clearBuffersCache, CLEAR_BUFFERS_TIMEOUT);
clearBuffersTimeout.unref();
}
/**
* Downgrade buffers in tier 1 cache (`buffers`) to tier 2 (`oldBuffers`)
* so they can be garbage collected.
*
* @returns {undefined}
*/
function clearBuffersCache() {
clearBuffersTimeout = null;
for (const buffer of buffers) {
oldBuffers.push(new WeakRef(buffer));
}
buffers.length = 0;
}
/**
* Create a `Uint8Array` which is 2 GiB in size, with its start aligned on 4 GiB.
*
* Achieve this by creating a 6 GiB `ArrayBuffer`, getting the offset within it that's aligned to 4 GiB,
* chopping off that number of bytes from the start, and shortening to 2 GiB.
*
* It's always possible to obtain a 2 GiB slice aligned on 4 GiB within a 6 GiB buffer,
* no matter how the 6 GiB buffer is aligned.
*
* Note: On systems with virtual memory, this only consumes 6 GiB of *virtual* memory.
* It does not consume physical memory until data is actually written to the `Uint8Array`.
* Physical memory consumed corresponds to the quantity of data actually written.
*
* @returns {Uint8Array} - Buffer
*/
function createBuffer() {
const arrayBuffer = new ArrayBuffer(ARRAY_BUFFER_SIZE);
const offset = getBufferOffset(new Uint8Array(arrayBuffer));
const buffer = new Uint8Array(arrayBuffer, offset, BUFFER_SIZE);
buffer.uint32 = new Uint32Array(arrayBuffer, offset, BUFFER_SIZE / 4);
buffer.float64 = new Float64Array(arrayBuffer, offset, BUFFER_SIZE / 8);
return buffer;
}
@@ -0,0 +1,93 @@
'use strict';
const { parseSyncRawImpl, parseAsyncRawImpl, isJsAst, returnBufferToCache } = require('./common.js');
module.exports = { parseSyncRaw, parseAsyncRaw };
/**
* Parse JS/TS source synchronously on current thread, using raw transfer to speed up deserialization.
*
* @param {string} filename - Filename
* @param {string} sourceText - Source text of file
* @param {Object} options - Parsing options
* @returns {Object} - Object with property getters for `program`, `module`, `comments`, and `errors`
*/
function parseSyncRaw(filename, sourceText, options) {
let _;
({ experimentalRawTransfer: _, ...options } = options);
return parseSyncRawImpl(filename, sourceText, options, deserialize);
}
/**
* Parse JS/TS source asynchronously, using raw transfer to speed up deserialization.
*
* Note that not all of the workload can happen on a separate thread.
* Parsing on Rust side does happen in a separate thread, but deserialization of the AST to JS objects
* has to happen on current thread. This synchronous deserialization work typically outweighs
* the asynchronous parsing by a factor of around 3.
*
* i.e. the majority of the workload cannot be parallelized by using this method.
*
* Generally `parseSyncRaw` is preferable to use as it does not have the overhead of spawning a thread.
* If you need to parallelize parsing multiple files, it is recommended to use worker threads.
*
* @param {string} filename - Filename
* @param {string} sourceText - Source text of file
* @param {Object} options - Parsing options
* @returns {Object} - Object with property getters for `program`, `module`, `comments`, and `errors`
*/
function parseAsyncRaw(filename, sourceText, options) {
let _;
({ experimentalRawTransfer: _, ...options } = options);
return parseAsyncRawImpl(filename, sourceText, options, deserialize);
}
let deserializeJS = null, deserializeTS = null;
/**
* Deserialize whole AST from buffer.
*
* @param {Uint8Array} buffer - Buffer containing AST in raw form
* @param {string} sourceText - Source for the file
* @param {number} sourceByteLen - Length of source text in UTF-8 bytes
* @returns {Object} - Object with property getters for `program`, `module`, `comments`, and `errors`
*/
function deserialize(buffer, sourceText, sourceByteLen) {
// Lazy load deserializer, and deserialize buffer to JS objects
let data;
if (isJsAst(buffer)) {
if (deserializeJS === null) deserializeJS = require('../generated/deserialize/js.js');
data = deserializeJS(buffer, sourceText, sourceByteLen);
// Add a line comment for hashbang
const { hashbang } = data.program;
if (hashbang !== null) {
data.comments.unshift({ type: 'Line', value: hashbang.value, start: hashbang.start, end: hashbang.end });
}
} else {
if (deserializeTS === null) deserializeTS = require('../generated/deserialize/ts.js');
data = deserializeTS(buffer, sourceText, sourceByteLen);
// Note: Do not add line comment for hashbang, to match `@typescript-eslint/parser`.
// See https://github.com/oxc-project/oxc/blob/ea784f5f082e4c53c98afde9bf983afd0b95e44e/napi/parser/src/lib.rs#L106-L130
}
// Return buffer to cache, to be reused
returnBufferToCache(buffer);
// We cannot lazily deserialize in the getters, because the buffer might be re-used to parse
// another file before the getter is called
return {
get program() {
return data.program;
},
get module() {
return data.module;
},
get comments() {
return data.comments;
},
get errors() {
return data.errors;
},
};
}
@@ -0,0 +1,15 @@
'use strict';
// Unique token which is not exposed publicly.
// Used to prevent user calling class constructors.
const TOKEN = {};
/**
* Throw error when restricted class constructor is called by user code.
* @throws {Error}
*/
function constructorError() {
throw new Error('Constructor is for internal use only');
}
module.exports = { TOKEN, constructorError };
@@ -0,0 +1,158 @@
'use strict';
const { parseSyncRawImpl, parseAsyncRawImpl, returnBufferToCache } = require('./common.js'),
{ TOKEN } = require('./lazy-common.js'),
{ DATA_POINTER_POS_32, PROGRAM_OFFSET } = require('../generated/constants.js'),
{ RawTransferData } = require('../generated/lazy/constructors.js'),
walkProgram = require('../generated/lazy/walk.js'),
{ Visitor, getVisitorsArr } = require('./visitor.js');
module.exports = { parseSyncLazy, parseAsyncLazy, Visitor };
/**
* Parse JS/TS source synchronously on current thread.
*
* The data in buffer is not deserialized. Is deserialized to JS objects lazily, when accessing the
* properties of objects.
*
* e.g. `program` in returned object is an instance of `Program` class, with getters for `start`, `end`,
* `body` etc.
*
* Returned object contains a `visit` function which can be used to visit the AST with a `Visitor`
* (`Visitor` class can be obtained by calling `experimentalGetLazyVisitor()`).
*
* Returned object contains a `dispose` method. When finished with this AST, it's advisable to call
* `dispose`, to return the buffer to the cache, so it can be reused.
* Garbage collector should do this anyway at some point, but on an unpredictable schedule,
* so it's preferable to call `dispose` manually, to ensure the buffer can be reused immediately.
*
* @param {string} filename - Filename
* @param {string} sourceText - Source text of file
* @param {Object} options - Parsing options
* @returns {Object} - Object with property getters for `program`, `module`, `comments`, and `errors`,
* and `dispose` and `visit` methods
*/
function parseSyncLazy(filename, sourceText, options) {
let _;
({ experimentalLazy: _, ...options } = options);
return parseSyncRawImpl(filename, sourceText, options, construct);
}
/**
* Parse JS/TS source asynchronously on a separate thread.
*
* The data in buffer is not deserialized. Is deserialized to JS objects lazily, when accessing the
* properties of objects.
*
* e.g. `program` in returned object is an instance of `Program` class, with getters for `start`, `end`,
* `body` etc.
*
* Because this function does not deserialize the AST, unlike `parseAsyncRaw`, very little work happens
* on current thread in this function. Deserialization work only occurs when properties of the objects
* are accessed.
*
* Returned object contains a `visit` function which can be used to visit the AST with a `Visitor`
* (`Visitor` class can be obtained by calling `experimentalGetLazyVisitor()`).
*
* Returned object contains a `dispose` method. When finished with this AST, it's advisable to call
* `dispose`, to return the buffer to the cache, so it can be reused.
* Garbage collector should do this anyway at some point, but on an unpredictable schedule,
* so it's preferable to call `dispose` manually, to ensure the buffer can be reused immediately.
*
* @param {string} filename - Filename
* @param {string} sourceText - Source text of file
* @param {Object} options - Parsing options
* @returns {Object} - Object with property getters for `program`, `module`, `comments`, and `errors`,
* and `dispose` and `visit` methods
*/
function parseAsyncLazy(filename, sourceText, options) {
let _;
({ experimentalLazy: _, ...options } = options);
return parseAsyncRawImpl(filename, sourceText, options, construct);
}
// Registry for buffers which are held by lazily-deserialized ASTs.
// Returns buffer to cache when the `ast` wrapper is garbage collected.
//
// Check for existence of `FinalizationRegistry`, to avoid errors on old versions of NodeJS
// which don't support it. e.g. Prettier supports NodeJS v14.
// Raw transfer is disabled on NodeJS before v22, so it doesn't matter if this is `null` on old NodeJS
// - it'll never be accessed in that case.
const bufferRecycleRegistry = typeof FinalizationRegistry === 'undefined'
? null
: new FinalizationRegistry(returnBufferToCache);
/**
* Get an object with getters which lazy deserialize AST and other data from buffer.
*
* Object also includes `dispose` and `visit` functions.
*
* @param {Uint8Array} buffer - Buffer containing AST in raw form
* @param {string} sourceText - Source for the file
* @param {number} sourceByteLen - Length of source text in UTF-8 bytes
* @returns {Object} - Object with property getters for `program`, `module`, `comments`, and `errors`,
* and `dispose` and `visit` methods
*/
function construct(buffer, sourceText, sourceByteLen) {
// Create AST object
const sourceIsAscii = sourceText.length === sourceByteLen;
const ast = { buffer, sourceText, sourceByteLen, sourceIsAscii, nodes: new Map(), token: TOKEN };
// Register `ast` with the recycle registry so buffer is returned to cache
// when `ast` is garbage collected
bufferRecycleRegistry.register(ast, buffer, ast);
// Get root data class instance
const rawDataPos = buffer.uint32[DATA_POINTER_POS_32];
const data = new RawTransferData(rawDataPos, ast);
return {
get program() {
return data.program;
},
get module() {
return data.module;
},
get comments() {
return data.comments;
},
get errors() {
return data.errors;
},
dispose: dispose.bind(null, ast),
visit(visitor) {
walkProgram(rawDataPos + PROGRAM_OFFSET, ast, getVisitorsArr(visitor));
},
};
}
/**
* Dispose of this AST.
*
* After calling this method, trying to read any nodes from this AST may cause an error.
*
* Buffer is returned to the cache to be reused.
*
* The buffer would be returned to the cache anyway, once all nodes of the AST are garbage collected,
* but calling `dispose` is preferable, as it will happen immediately.
* Otherwise, garbage collector may take time to collect the `ast` object, and new buffers may be created
* in the meantime, when we could have reused this one.
*
* @param {Object} ast - AST object containing buffer etc
* @returns {undefined}
*/
function dispose(ast) {
// Return buffer to cache, to be reused
returnBufferToCache(ast.buffer);
// Remove connection between `ast` and the buffer
ast.buffer = null;
// Clear other contents of `ast`, so they can be garbage collected
ast.sourceText = null;
ast.nodes = null;
// Remove `ast` from recycling register.
// When `ast` is garbage collected, there's no longer any action to be taken.
bufferRecycleRegistry.unregister(ast);
}
@@ -0,0 +1,362 @@
'use strict';
const { TOKEN, constructorError } = require('./lazy-common.js');
// Internal symbol to get `NodeArray` from a proxy wrapping a `NodeArray`.
//
// Methods of `NodeArray` are called with `this` being the proxy, rather than the `NodeArray` itself.
// They can "unwrap" the proxy by getting `this[ARRAY]`, and the `get` proxy trap will return
// the actual `NodeArray`.
//
// This symbol is not exported, and it is not actually defined on `NodeArray`s, so user cannot obtain it
// via `Object.getOwnPropertySymbols` or `Reflect.ownKeys`. Therefore user code cannot unwrap the proxy.
const ARRAY = Symbol();
// Functions to get internal properties of a `NodeArray`. Initialized in class static block below.
let getInternalFromProxy, getLength, getElement;
/**
* An array of AST nodes where elements are deserialized lazily upon access.
*
* Extends `Array` to make `Array.isArray` return `true` for a `NodeArray`.
*
* TODO: Other methods could maybe be more optimal, avoiding going via proxy multiple times
* e.g. `some`, `indexOf`.
*/
class NodeArray extends Array {
#internal;
/**
* Create a `NodeArray`.
*
* Constructor does not actually return a `NodeArray`, but one wrapped in a `Proxy`.
* The proxy intercepts accesses to elements and lazily deserializes them,
* and blocks mutation of elements or `length` property.
*
* @class
* @param {number} pos - Buffer position of first element
* @param {number} length - Number of elements
* @param {number} stride - Element size in bytes
* @param {Function} construct - Function to deserialize element
* @param {Object} ast - AST object
* @returns {Proxy<NodeArray>} - `NodeArray` wrapped in a `Proxy`
*/
constructor(pos, length, stride, construct, ast) {
if (ast?.token !== TOKEN) constructorError();
super();
this.#internal = { pos, length, ast, stride, construct };
return new Proxy(this, PROXY_HANDLERS);
}
// Allow `arr.filter`, `arr.map` etc.
static [Symbol.species] = Array;
// Override `values` method with a more efficient one that avoids going via proxy for every iteration.
// TODO: Benchmark to check that this is actually faster.
values() {
return new NodeArrayValuesIterator(this);
}
// Override `keys` method with a more efficient one that avoids going via proxy for every iteration.
// TODO: Benchmark to check that this is actually faster.
keys() {
return new NodeArrayKeysIterator(this);
}
// Override `entries` method with a more efficient one that avoids going via proxy for every iteration.
// TODO: Benchmark to check that this is actually faster.
entries() {
return new NodeArrayEntriesIterator(this);
}
// This method is overwritten with reference to `values` method below.
// Defining dummy method here to prevent the later assignment altering the shape of class prototype.
[Symbol.iterator]() {}
/**
* Override `slice` method to return a `NodeArray`.
*
* @this {NodeArray}
* @param {*} start - Start of slice
* @param {*} end - End of slice
* @returns {NodeArray} - `NodeArray` containing slice of this one
*/
slice(start, end) {
const internal = this[ARRAY].#internal,
{ length } = internal;
start = toInt(start);
if (start < 0) {
start = length + start;
if (start < 0) start = 0;
}
if (end === void 0) {
end = length;
} else {
end = toInt(end);
if (end < 0) {
end += length;
if (end < 0) end = 0;
} else if (end > length) {
end = length;
}
}
let sliceLength = end - start;
if (sliceLength <= 0 || start >= length) {
start = 0;
sliceLength = 0;
}
const { stride } = internal;
return new NodeArray(internal.pos + start * stride, sliceLength, stride, internal.construct, internal.ast);
}
// Make `console.log` deserialize all elements.
[Symbol.for('nodejs.util.inspect.custom')]() {
const values = [...this.values()];
Object.setPrototypeOf(values, DebugNodeArray.prototype);
return values;
}
static {
/**
* Get internal properties of `NodeArray`, given a proxy wrapping a `NodeArray`.
* @param {Proxy} proxy - Proxy wrapping `NodeArray` object
* @returns {Object} - Internal properties object
*/
getInternalFromProxy = proxy => proxy[ARRAY].#internal;
/**
* Get length of `NodeArray`.
* @param {NodeArray} arr - `NodeArray` object
* @returns {number} - Array length
*/
getLength = arr => arr.#internal.length;
/**
* Get element of `NodeArray` at index `index`.
*
* @param {NodeArray} arr - `NodeArray` object
* @param {number} index - Index of element to get
* @returns {*|undefined} - Element at index `index`, or `undefined` if out of bounds
*/
getElement = (arr, index) => {
const internal = arr.#internal;
if (index >= internal.length) return void 0;
return (0, internal.construct)(internal.pos + index * internal.stride, internal.ast);
};
}
}
NodeArray.prototype[Symbol.iterator] = NodeArray.prototype.values;
module.exports = NodeArray;
/**
* Iterator over values of a `NodeArray`.
* Returned by `values` method, and also used as iterator for `for (const node of nodeArray) {}`.
*/
class NodeArrayValuesIterator {
#internal;
constructor(proxy) {
const internal = getInternalFromProxy(proxy),
{ pos, stride } = internal;
this.#internal = {
pos,
endPos: pos + internal.length * stride,
ast: internal.ast,
construct: internal.construct,
stride,
};
}
next() {
const internal = this.#internal,
{ pos } = internal;
if (pos === internal.endPos) return { done: true, value: null };
internal.pos = pos + internal.stride;
return { done: false, value: (0, internal.construct)(pos, internal.ast) };
}
[Symbol.iterator]() {
return this;
}
}
/**
* Iterator over keys of a `NodeArray`. Returned by `keys` method.
*/
class NodeArrayKeysIterator {
#internal;
constructor(proxy) {
const internal = getInternalFromProxy(proxy);
this.#internal = { index: 0, length: internal.length };
}
next() {
const internal = this.#internal,
{ index } = internal;
if (index === internal.length) return { done: true, value: null };
internal.index = index + 1;
return { done: false, value: index };
}
[Symbol.iterator]() {
return this;
}
}
/**
* Iterator over values of a `NodeArray`. Returned by `entries` method.
*/
class NodeArrayEntriesIterator {
#internal;
constructor(proxy) {
const internal = getInternalFromProxy(proxy);
this.#internal = {
index: 0,
length: internal.length,
pos: internal.pos,
ast: internal.ast,
construct: internal.construct,
stride: internal.stride,
};
}
next() {
const internal = this.#internal,
{ index } = internal;
if (index === internal.length) return { done: true, value: null };
internal.index = index + 1;
return {
done: false,
value: [index, (0, internal.construct)(internal.pos + index * internal.stride, internal.ast)],
};
}
[Symbol.iterator]() {
return this;
}
}
// Class used for `[Symbol.for('nodejs.util.inspect.custom')]` method (for `console.log`).
const DebugNodeArray = class NodeArray extends Array {};
// Proxy handlers.
//
// Every `NodeArray` returned to user is wrapped in a `Proxy`, using these handlers.
// They lazily deserialize array elements upon access, and block mutation of array elements / `length`.
const PROXY_HANDLERS = {
// Return `true` for indexes which are in bounds.
// e.g. `'0' in arr`.
has(arr, key) {
const index = toIndex(key);
if (index !== null) return index < getLength(arr);
return Reflect.has(arr, key);
},
// Get elements and length.
get(arr, key) {
// Methods of `NodeArray` are called with `this` being the proxy, rather than the `NodeArray` itself.
// They can "unwrap" the proxy by getting `this[ARRAY]`.
if (key === ARRAY) return arr;
if (key === 'length') return getLength(arr);
const index = toIndex(key);
if (index !== null) return getElement(arr, index);
return Reflect.get(arr, key);
},
// Get descriptors for elements and length.
getOwnPropertyDescriptor(arr, key) {
if (key === 'length') {
// Cannot return `writable: false` unfortunately
return { value: getLength(arr), writable: true, enumerable: false, configurable: false };
}
const index = toIndex(key);
if (index !== null) {
const value = getElement(arr, index);
if (value === void 0) return void 0;
// Cannot return `configurable: false` unfortunately
return { value, writable: false, enumerable: true, configurable: true };
}
return Reflect.getOwnPropertyDescriptor(arr, key);
},
// Prevent setting `length` or entries.
// Catches:
// * `Object.defineProperty(arr, 0, {value: null})`.
// * `arr[1] = null`.
// * `arr.length = 0`.
// * `Object.defineProperty(arr, 'length', {value: 0})`.
// * Other operations which mutate entries e.g. `arr.push(123)`.
defineProperty(arr, key, descriptor) {
if (key === 'length' || toIndex(key) !== null) return false;
return Reflect.defineProperty(arr, key, descriptor);
},
// Prevent deleting entries.
deleteProperty(arr, key) {
// Note: `Reflect.deleteProperty(arr, 'length')` already returns `false`
if (toIndex(key) !== null) return false;
return Reflect.deleteProperty(arr, key);
},
// Get keys, including element indexes.
ownKeys(arr) {
const keys = [],
length = getLength(arr);
for (let i = 0; i < length; i++) {
keys.push(i + '');
}
keys.push(...Reflect.ownKeys(arr));
return keys;
},
};
/**
* Convert key to array index, if it is a valid array index.
*
* Only strings comprising a plain integer are valid indexes.
* e.g. `"-1"`, `"01"`, `"0xFF"`, `"1e1"`, `"1 "` are not valid indexes.
* Integers >= 4294967295 are not valid indexes.
*
* @param {string|Symbol} - Key used for property lookup.
* @returns {number|null} - `key` converted to integer, if it's a valid array index, otherwise `null`.
*/
function toIndex(key) {
if (typeof key === 'string') {
if (key === '0') return 0;
if (INDEX_REGEX.test(key)) {
const index = +key;
if (index < 4294967295) return index;
}
}
return null;
}
const INDEX_REGEX = /^[1-9]\d*$/;
/**
* Convert value to integer.
* https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number#integer_conversion
*
* @param {*} value - Value to convert to integer.
* @returns {number} - Integer
*/
function toInt(value) {
value = Math.trunc(+value);
// `value === 0` check is to convert -0 to 0
if (value === 0 || Number.isNaN(value)) return 0;
return value;
}
@@ -0,0 +1,56 @@
'use strict';
const rawTransferSupportedBinding = require('../bindings.js').rawTransferSupported;
module.exports = rawTransferSupported;
let rawTransferIsSupported = null;
/**
* Returns `true` if `experimentalRawTransfer` is option is supported.
*
* Raw transfer is only supported on 64-bit little-endian systems,
* and NodeJS >= v22.0.0 or Deno >= v2.0.0.
*
* Versions of NodeJS prior to v22.0.0 do not support creating an `ArrayBuffer` larger than 4 GiB.
* Bun (as at v1.2.4) also does not support creating an `ArrayBuffer` larger than 4 GiB.
* Support on Deno v1 is unknown and it's EOL, so treating Deno before v2.0.0 as unsupported.
*
* No easy way to determining pointer width (64 bit or 32 bit) in JS,
* so call a function on Rust side to find out.
*
* @returns {boolean} - `true` if raw transfer is supported on this platform
*/
function rawTransferSupported() {
if (rawTransferIsSupported === null) {
rawTransferIsSupported = rawTransferRuntimeSupported() && rawTransferSupportedBinding();
}
return rawTransferIsSupported;
}
// Checks copied from:
// https://github.com/unjs/std-env/blob/ab15595debec9e9115a9c1d31bc7597a8e71dbfd/src/runtimes.ts
// MIT license: https://github.com/unjs/std-env/blob/ab15595debec9e9115a9c1d31bc7597a8e71dbfd/LICENCE
function rawTransferRuntimeSupported() {
let global;
try {
global = globalThis;
} catch (_err) { // oxlint-disable-line no-unused-vars
return false;
}
const isBun = !!global.Bun || !!global.process?.versions?.bun;
if (isBun) return false;
const isDeno = !!global.Deno;
if (isDeno) {
const match = Deno.version?.deno?.match(/^(\d+)\./);
return !!match && match[1] * 1 >= 2;
}
const isNode = global.process?.release?.name === 'node';
if (!isNode) return false;
const match = process.version?.match(/^v(\d+)\./);
return !!match && match[1] * 1 >= 22;
}
@@ -0,0 +1,129 @@
'use strict';
const {
NODE_TYPE_IDS_MAP,
NODE_TYPES_COUNT,
LEAF_NODE_TYPES_COUNT,
} = require('../generated/lazy/types.js');
// Getter for private `#visitorsArr` property of `Visitor` class. Initialized in class body below.
let getVisitorsArr;
/**
* Visitor class, used to visit an AST.
*/
class Visitor {
#visitorsArr;
/**
* Create `Visitor`.
*
* Provide an object where keys are names of AST nodes you want to visit,
* and values are visitor functions which receive AST node objects of that type.
*
* Keys can also be postfixed with `:exit` to visit when exiting the node, rather than entering.
*
* ```js
* const visitor = new Visitor({
* BinaryExpression(binExpr) {
* // Do stuff when entering a `BinaryExpression`
* },
* 'BinaryExpression:exit'(binExpr) {
* // Do stuff when exiting a `BinaryExpression`
* },
* });
* ```
*
* @class
* @param {Object} visitor - Object defining visit functions for AST nodes
* @returns {Visitor}
*/
constructor(visitor) {
this.#visitorsArr = createVisitorsArr(visitor);
}
static {
getVisitorsArr = visitor => visitor.#visitorsArr;
}
}
module.exports = { Visitor, getVisitorsArr };
/**
* Create array of visitors, keyed by node type ID.
*
* Each element of array is one of:
*
* * No visitor for this type = `null`.
* * Visitor for leaf node = visit function.
* * Visitor for non-leaf node = object of form `{ enter, exit }`,
* where each property is either a visitor function or `null`.
*
* @param {Object} visitor - Visitors object from user
* @returns {Array<Object|Function|null>} - Array of visitors
*/
function createVisitorsArr(visitor) {
if (visitor === null || typeof visitor !== 'object') {
throw new Error('`visitor` must be an object');
}
// Create empty visitors array
const visitorsArr = [];
for (let i = NODE_TYPES_COUNT; i !== 0; i--) {
visitorsArr.push(null);
}
// Populate visitors array from provided object
for (let name of Object.keys(visitor)) {
const visitFn = visitor[name];
if (typeof visitFn !== 'function') {
throw new Error(`'${name}' property of \`visitor\` object is not a function`);
}
const isExit = name.endsWith(':exit');
if (isExit) name = name.slice(0, -5);
const typeId = NODE_TYPE_IDS_MAP.get(name);
if (typeId === void 0) throw new Error(`Unknown node type '${name}' in \`visitor\` object`);
if (typeId < LEAF_NODE_TYPES_COUNT) {
// Leaf node. Store just 1 function.
const existingVisitFn = visitorsArr[typeId];
if (existingVisitFn === null) {
visitorsArr[typeId] = visitFn;
} else if (isExit) {
visitorsArr[typeId] = combineVisitFunctions(existingVisitFn, visitFn);
} else {
visitorsArr[typeId] = combineVisitFunctions(visitFn, existingVisitFn);
}
continue;
}
let enterExit = visitorsArr[typeId];
if (enterExit === null) {
enterExit = visitorsArr[typeId] = { enter: null, exit: null };
}
if (isExit) {
enterExit.exit = visitFn;
} else {
enterExit.enter = visitFn;
}
}
return visitorsArr;
}
/**
* Combine 2 visitor functions into 1.
*
* @param {function} visit1 - 1st visitor function
* @param {function} visit2 - 2nd visitor function
* @returns {function} - Combined visitor function
*/
function combineVisitFunctions(visit1, visit2) {
return function(node) {
visit1(node);
visit2(node);
};
}
+11
View File
@@ -0,0 +1,11 @@
export * from '@oxc-parser/binding-wasm32-wasi';
import * as bindings from '@oxc-parser/binding-wasm32-wasi';
import { wrap } from './wrap.mjs';
export async function parseAsync(...args) {
return wrap(await bindings.parseAsync(...args));
}
export function parseSync(filename, sourceText, options) {
return wrap(bindings.parseSync(filename, sourceText, options));
}
@@ -0,0 +1,23 @@
const fs = require('node:fs');
const childProcess = require('node:child_process');
const pkg = JSON.parse(
fs.readFileSync(require.resolve('oxc-parser/package.json'), 'utf-8'),
);
const version = pkg.version;
const baseDir = `/tmp/oxc-parser-${version}`;
const bindingEntry = `${baseDir}/node_modules/@oxc-parser/binding-wasm32-wasi/parser.wasi.cjs`;
if (!fs.existsSync(bindingEntry)) {
fs.rmSync(baseDir, { recursive: true, force: true });
fs.mkdirSync(baseDir, { recursive: true });
const bindingPkg = `@oxc-parser/binding-wasm32-wasi@${version}`;
// eslint-disable-next-line: no-console
console.log(`[oxc-parser] Downloading ${bindingPkg} on WebContainer...`);
childProcess.execFileSync('pnpm', ['i', bindingPkg], {
cwd: baseDir,
stdio: 'inherit',
});
}
module.exports = require(bindingEntry);
+58
View File
@@ -0,0 +1,58 @@
// Note: This code is repeated in `wrap.mjs`.
// Any changes should be applied in that file too.
module.exports.wrap = function wrap(result) {
let program, module, comments, errors;
return {
get program() {
if (!program) program = jsonParseAst(result.program);
return program;
},
get module() {
if (!module) module = result.module;
return module;
},
get comments() {
if (!comments) comments = result.comments;
return comments;
},
get errors() {
if (!errors) errors = result.errors;
return errors;
},
};
};
// Set `value` field of `Literal`s which are `BigInt`s or `RegExp`s.
//
// Returned JSON contains an array `fixes` with paths to these nodes
// e.g. for `123n; foo(/xyz/)`, `fixes` will be
// `[["body", 0, "expression"], ["body", 1, "expression", "arguments", 2]]`.
//
// Walk down the AST to these nodes and alter them.
// Compiling the list of fixes on Rust side avoids having to do a full AST traversal on JS side
// to locate the likely very few `Literal`s which need fixing.
function jsonParseAst(programJson) {
const { node: program, fixes } = JSON.parse(programJson);
for (const fixPath of fixes) {
applyFix(program, fixPath);
}
return program;
}
function applyFix(program, fixPath) {
let node = program;
for (const key of fixPath) {
node = node[key];
}
if (node.bigint) {
node.value = BigInt(node.bigint);
} else {
try {
node.value = RegExp(node.regex.pattern, node.regex.flags);
} catch (_err) { // oxlint-disable-line no-unused-vars
// Invalid regexp, or valid regexp using syntax not supported by this version of NodeJS
}
}
}
+60
View File
@@ -0,0 +1,60 @@
// Note: This code is repeated in `wrap.cjs`.
// Any changes should be applied in that file too.
export function wrap(result) {
let program, module, comments, errors;
return {
get program() {
if (!program) program = jsonParseAst(result.program);
return program;
},
get module() {
if (!module) module = result.module;
return module;
},
get comments() {
if (!comments) comments = result.comments;
return comments;
},
get errors() {
if (!errors) errors = result.errors;
return errors;
},
};
}
// Used by `napi/playground/patch.mjs`.
//
// Set `value` field of `Literal`s which are `BigInt`s or `RegExp`s.
//
// Returned JSON contains an array `fixes` with paths to these nodes
// e.g. for `123n; foo(/xyz/)`, `fixes` will be
// `[["body", 0, "expression"], ["body", 1, "expression", "arguments", 2]]`.
//
// Walk down the AST to these nodes and alter them.
// Compiling the list of fixes on Rust side avoids having to do a full AST traversal on JS side
// to locate the likely very few `Literal`s which need fixing.
export function jsonParseAst(programJson) {
const { node: program, fixes } = JSON.parse(programJson);
for (const fixPath of fixes) {
applyFix(program, fixPath);
}
return program;
}
function applyFix(program, fixPath) {
let node = program;
for (const key of fixPath) {
node = node[key];
}
if (node.bigint) {
node.value = BigInt(node.bigint);
} else {
try {
node.value = RegExp(node.regex.pattern, node.regex.flags);
} catch (_err) { // oxlint-disable-line no-unused-vars
// Invalid regexp, or valid regexp using syntax not supported by this version of NodeJS
}
}
}