EsprimaFacade.ts 2.5 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374
  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 parse (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: esprima.NodeMeta) => lastMeta = meta);
  25. } catch {}
  26. try {
  27. return esprima.parseModule(input, config, (node: ESTree.Node, meta: esprima.NodeMeta) => lastMeta = meta);
  28. } catch (error) {
  29. return EsprimaFacade.processParsingError(input, error.message, lastMeta);
  30. }
  31. }
  32. /**
  33. * @param {string} sourceCode
  34. * @param {string} errorMessage
  35. * @param {"esprima".NodeMeta | null} meta
  36. * @returns {never}
  37. */
  38. private static processParsingError (sourceCode: string, errorMessage: string, meta: esprima.NodeMeta | null): never {
  39. if (!meta || !meta.start || !meta.end || !meta.start.column || !meta.end.column) {
  40. throw new Error(errorMessage);
  41. }
  42. const lineNumberMatch: RegExpMatchArray | null = errorMessage.match(/Line *(\d*)/);
  43. if (!lineNumberMatch) {
  44. throw new Error(errorMessage);
  45. }
  46. const lineNumber: number = parseInt(lineNumberMatch[1], 10);
  47. const sourceCodeLines: string[] = sourceCode.split(/\r?\n/);
  48. const errorLine: string | undefined = sourceCodeLines[lineNumber - 1];
  49. if (!errorLine) {
  50. throw new Error(errorMessage);
  51. }
  52. const startErrorIndex: number = Math.max(0, meta.start.column - EsprimaFacade.nearestSymbolsCount);
  53. const endErrorIndex: number = Math.min(errorLine.length, meta.end.column + EsprimaFacade.nearestSymbolsCount);
  54. const formattedPointer: string = EsprimaFacade.colorError('>');
  55. const formattedCodeSlice: string = `...${
  56. errorLine.substring(startErrorIndex, endErrorIndex).replace(/^\s+/, '')
  57. }...`;
  58. throw new Error(`${errorMessage}\n${formattedPointer} ${formattedCodeSlice}`);
  59. }
  60. }