// @ts-nocheck const { createProgram, getPreEmitDiagnostics, ModuleKind, ModuleResolutionKind, ScriptTarget, } = require('typescript'); const babelParser = require('@babel/parser'); // @ts-expect-error const esModuleLexer = require('es-module-lexer'); const parse5 = require('parse5'); const traverseHtml = require('../utils/traverse-html.js'); const { LogService } = require('./LogService.js'); /** * @typedef {import('../types/core').PathFromSystemRoot} PathFromSystemRoot */ class AstService { /** * @deprecated for simplicity/maintainability, only allow Babel for js * Compiles an array of file paths using Typescript. * @param {string[]} filePaths * @param {CompilerOptions} options */ static _getTypescriptAst(filePaths, options) { // eslint-disable-next-line no-param-reassign filePaths = Array.isArray(filePaths) ? filePaths : [filePaths]; const defaultOptions = { noEmitOnError: false, allowJs: true, experimentalDecorators: true, target: ScriptTarget.Latest, downlevelIteration: true, module: ModuleKind.ESNext, // module: ModuleKind.CommonJS, // lib: ["esnext", "dom"], strictNullChecks: true, moduleResolution: ModuleResolutionKind.NodeJs, esModuleInterop: true, noEmit: true, allowSyntheticDefaultImports: true, allowUnreachableCode: true, allowUnusedLabels: true, skipLibCheck: true, isolatedModules: true, }; const program = createProgram(filePaths, options || defaultOptions); const diagnostics = getPreEmitDiagnostics(program); const files = program.getSourceFiles().filter(sf => filePaths.includes(sf.fileName)); return { diagnostics, program, files }; } /** * Compiles an array of file paths using Babel. * @param {string} code */ static _getBabelAst(code) { const ast = babelParser.parse(code, { sourceType: 'module', plugins: ['importMeta', 'dynamicImport', 'classProperties'], }); return ast; } /** * Combines all script tags as if it were one js file. * @param {string} htmlCode */ static getScriptsFromHtml(htmlCode) { const ast = parse5.parseFragment(htmlCode); const scripts = []; traverseHtml(ast, { script(path) { const code = path.node.childNodes[0] ? path.node.childNodes[0].value : ''; scripts.push(code); }, }); return scripts; } /** * @deprecated for simplicity/maintainability, only allow Babel for js * @param {string} code */ static async _getEsModuleLexerOutput(code) { return esModuleLexer.parse(code); } /** * Returns the desired AST * Why would we support multiple ASTs/parsers? * - 'babel' is our default tool for analysis. It's the most versatile and popular tool, it's * close to the EStree standard (other than Typescript) and a lot of plugins and resources can * be found online. It also allows to parse Typescript and spec proposals. * - 'typescript' (deprecated) is needed for some valuable third party tooling, like web-component-analyzer * - 'es-module-lexer' (deprecated) is needed for the dedicated task of finding module imports; it is way * quicker than a full fledged AST parser * @param { 'babel' } astType * @param { {filePath: PathFromSystemRoot} } [options] */ // eslint-disable-next-line consistent-return static getAst(code, astType, { filePath } = {}) { // eslint-disable-next-line default-case try { // eslint-disable-next-line default-case switch (astType) { case 'babel': return this._getBabelAst(code); case 'typescript': LogService.warn(` Please notice "typescript" support is deprecated. For parsing javascript, "babel" is recommended.`); return this._getTypescriptAst(code); case 'es-module-lexer': LogService.warn(` Please notice "es-module-lexer" support is deprecated. For parsing javascript, "babel" is recommended.`); return this._getEsModuleLexerOutput(code); } } catch (e) { LogService.error(`Error when parsing "${filePath}":/n${e}`); } } } module.exports = { AstService };