import {readFile} from 'fs/promises'; import {join as pathJoin} from 'path'; import {fileURLToPath} from 'url'; import assert from 'assert'; import {typeAndWrappers} from './utils.mjs'; const FILENAMES = ['js.rs', 'jsx.rs', 'literal.rs', 'ts.rs']; /** * Parse type defs from Rust files. */ export default async function getTypesFromCode() { const codeDirPath = pathJoin(fileURLToPath(import.meta.url), '../../../../oxc_ast/src/ast/'); const types = Object.create(null); for (const filename of FILENAMES) { const code = await readFile(`${codeDirPath}${filename}`, 'utf8'); parseFile(code, filename, types); } return types; } function parseFile(code, filename, types) { const lines = code.split(/\r?\n/).map( line => line.replace(/\s+/g, ' ').replace(/ ?\/\/.*$/, '') ); for (let lineIndex = 0; lineIndex < lines.length; lineIndex++) { const lineMatch = lines[lineIndex].match(/^#\[visited_node ?([\]\(])/); if (!lineMatch) continue; let scopeArgs = null; if (lineMatch[1] === '(') { let line = lines[lineIndex].slice(lineMatch[0].length), scopeArgsStr = ''; while (!line.endsWith(')]')) { scopeArgsStr += ` ${line}`; line = lines[++lineIndex]; } scopeArgsStr += ` ${line.slice(0, -2)}`; scopeArgsStr = scopeArgsStr.trim().replace(/ +/g, ' '); scopeArgs = parseScopeArgs(scopeArgsStr, filename, lineIndex); } let match; while (true) { match = lines[++lineIndex].match(/^pub (enum|struct) ((.+?)(?:<'a>)?) \{/); if (match) break; } const [, kind, rawName, name] = match, startLineIndex = lineIndex; const itemLines = []; while (true) { const line = lines[++lineIndex].trim(); if (line === '}') break; if (line !== '') itemLines.push(line); } if (kind === 'struct') { types[name] = parseStruct(name, rawName, itemLines, scopeArgs, filename, startLineIndex); } else { types[name] = parseEnum(name, rawName, itemLines, filename, startLineIndex); } } } function parseStruct(name, rawName, lines, scopeArgs, filename, startLineIndex) { const fields = []; for (let i = 0; i < lines.length; i++) { const line = lines[i]; if (line.startsWith('#[')) { while (!lines[i].endsWith(']')) { i++; } continue; } const match = line.match(/^pub ((?:r#)?([a-z_]+)): (.+),$/); assert( match, `Cannot parse line ${startLineIndex + i} in '${filename}' as struct field: '${line}'` ); const [, rawName, name, rawTypeName] = match, typeName = rawTypeName.replace(/<'a>/g, '').replace(/<'a, ?/g, '<'), {name: innerTypeName, wrappers} = typeAndWrappers(typeName); fields.push({name, typeName, rawName, rawTypeName, innerTypeName, wrappers}); } return {kind: 'struct', name, rawName, fields, scopeArgs}; } function parseEnum(name, rawName, lines, filename, startLineIndex) { const variants = [], inherits = []; for (const [lineIndex, line] of lines.entries()) { const match = line.match(/^(.+?)\((.+?)\)(?: ?= ?(\d+))?,$/); if (match) { const [, name, rawTypeName, discriminantStr] = match, typeName = rawTypeName.replace(/<'a>/g, '').replace(/<'a, ?/g, '<'), {name: innerTypeName, wrappers} = typeAndWrappers(typeName), discriminant = discriminantStr ? +discriminantStr : null; variants.push({name, typeName, rawTypeName, innerTypeName, wrappers, discriminant}); } else { const match2 = line.match(/^@inherit ([A-Za-z]+)$/); assert( match2, `Cannot parse line ${startLineIndex + lineIndex} in '${filename}' as enum variant: '${line}'` ); inherits.push(match2[1]); } } return {kind: 'enum', name, rawName, variants, inherits}; } function parseScopeArgs(argsStr, filename, lineIndex) { if (!argsStr) return null; const matchAndConsume = (regex) => { const match = argsStr.match(regex); assert(match); argsStr = argsStr.slice(match[0].length); return match.slice(1); }; const args = {}; try { while (true) { const [key] = matchAndConsume(/^([a-z_]+)\(/); assert( ['scope', 'scope_if', 'strict_if', 'enter_scope_before'].includes(key), `Unexpected visited_node macro arg: ${key}` ); let bracketCount = 1, index = 0; for (; index < argsStr.length; index++) { const char = argsStr[index]; if (char === '(') { bracketCount++; } else if (char === ')') { bracketCount--; if (bracketCount === 0) break; } } assert(bracketCount === 0); args[key] = argsStr.slice(0, index).trim(); argsStr = argsStr.slice(index + 1); if (argsStr === '') break; matchAndConsume(/^ ?, ?/); } assert(args.scope, 'Missing key `scope`'); } catch (err) { throw new Error( `Cannot parse visited_node args: ${argsStr} in ${filename}:${lineIndex}\n${err?.message}` ); } return args; }