EsprimaFacade.ts 2.3 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970
  1. import * as esprima from 'esprima';
  2. import * as ESTree from 'estree';
  3. import chalk, { Chalk } from 'chalk';
  4. /**
  5. * Facade over `esprima` to handle parsing errors and provide more detailed error messages
  6. */
  7. export class EsprimaFacade {
  8. /**
  9. * @type {Chalk}
  10. */
  11. private static readonly colorError: Chalk = chalk.red;
  12. /**
  13. * @type {number}
  14. */
  15. private static readonly nearestSymbolsCount: number = 10;
  16. /**
  17. * @param {string} input
  18. * @param {ParseOptions} config
  19. * @returns {Program}
  20. */
  21. public static parseScript (input: string, config: esprima.ParseOptions): ESTree.Program {
  22. let lastMeta: esprima.NodeMeta | null = null;
  23. try {
  24. return esprima.parseScript(input, config, (node: ESTree.Node, meta: any) => lastMeta = meta);
  25. } catch (error) {
  26. return EsprimaFacade.processParsingError(input, error.message, lastMeta);
  27. }
  28. }
  29. /**
  30. * @param {string} sourceCode
  31. * @param {string} errorMessage
  32. * @param {"esprima".NodeMeta | null} meta
  33. * @returns {never}
  34. */
  35. private static processParsingError (sourceCode: string, errorMessage: string, meta: esprima.NodeMeta | null): never {
  36. if (!meta || !meta.start || !meta.end || !meta.start.column || !meta.end.column) {
  37. throw new Error(errorMessage);
  38. }
  39. const lineNumberMatch: RegExpMatchArray | null = errorMessage.match(/Line *(\d*)/);
  40. if (!lineNumberMatch) {
  41. throw new Error(errorMessage);
  42. }
  43. const lineNumber: number = parseInt(lineNumberMatch[1], 10);
  44. const sourceCodeLines: string[] = sourceCode.split(/\r?\n/);
  45. const errorLine: string | undefined = sourceCodeLines[lineNumber - 1];
  46. if (!errorLine) {
  47. throw new Error(errorMessage);
  48. }
  49. const startErrorIndex: number = Math.max(0, meta.start.column - EsprimaFacade.nearestSymbolsCount);
  50. const endErrorIndex: number = Math.min(errorLine.length, meta.end.column + EsprimaFacade.nearestSymbolsCount);
  51. const formattedPointer: string = EsprimaFacade.colorError('>');
  52. const formattedCodeSlice: string = `...${
  53. errorLine.substring(startErrorIndex, endErrorIndex).replace(/^\s+/, '')
  54. }...`;
  55. throw new Error(`${errorMessage}\n${formattedPointer} ${formattedCodeSlice}`);
  56. }
  57. }