lion/packages-node/providence-analytics/src/program/services/AstService.js
2021-11-16 15:37:50 +01:00

130 lines
4.1 KiB
JavaScript

// @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 };