Ohm-Management - Projektarbeit B-ME
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

espree.js 10KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314
  1. "use strict";
  2. const acorn = require("acorn");
  3. const jsx = require("acorn-jsx");
  4. const commentAttachment = require("./comment-attachment");
  5. const TokenTranslator = require("./token-translator");
  6. const DEFAULT_ECMA_VERSION = 5;
  7. const STATE = Symbol("espree's internal state");
  8. const ESPRIMA_FINISH_NODE = Symbol("espree's esprimaFinishNode");
  9. const tokTypes = Object.assign({}, acorn.tokTypes, jsx.tokTypes);
  10. /**
  11. * Normalize ECMAScript version from the initial config
  12. * @param {number} ecmaVersion ECMAScript version from the initial config
  13. * @returns {number} normalized ECMAScript version
  14. */
  15. function normalizeEcmaVersion(ecmaVersion) {
  16. if (typeof ecmaVersion === "number") {
  17. let version = ecmaVersion;
  18. // Calculate ECMAScript edition number from official year version starting with
  19. // ES2015, which corresponds with ES6 (or a difference of 2009).
  20. if (version >= 2015) {
  21. version -= 2009;
  22. }
  23. switch (version) {
  24. case 3:
  25. case 5:
  26. case 6:
  27. case 7:
  28. case 8:
  29. case 9:
  30. case 10:
  31. return version;
  32. default:
  33. throw new Error("Invalid ecmaVersion.");
  34. }
  35. } else {
  36. return DEFAULT_ECMA_VERSION;
  37. }
  38. }
  39. /**
  40. * Converts an Acorn comment to a Esprima comment.
  41. * @param {boolean} block True if it's a block comment, false if not.
  42. * @param {string} text The text of the comment.
  43. * @param {int} start The index at which the comment starts.
  44. * @param {int} end The index at which the comment ends.
  45. * @param {Location} startLoc The location at which the comment starts.
  46. * @param {Location} endLoc The location at which the comment ends.
  47. * @returns {Object} The comment object.
  48. * @private
  49. */
  50. function convertAcornCommentToEsprimaComment(block, text, start, end, startLoc, endLoc) {
  51. const comment = {
  52. type: block ? "Block" : "Line",
  53. value: text
  54. };
  55. if (typeof start === "number") {
  56. comment.start = start;
  57. comment.end = end;
  58. comment.range = [start, end];
  59. }
  60. if (typeof startLoc === "object") {
  61. comment.loc = {
  62. start: startLoc,
  63. end: endLoc
  64. };
  65. }
  66. return comment;
  67. }
  68. module.exports = () => Parser => class Espree extends Parser {
  69. constructor(options, code) {
  70. if (typeof options !== "object" || options === null) {
  71. options = {};
  72. }
  73. if (typeof code !== "string" && !(code instanceof String)) {
  74. code = String(code);
  75. }
  76. const ecmaFeatures = options.ecmaFeatures || {};
  77. const ecmaVersion = normalizeEcmaVersion(options.ecmaVersion);
  78. const isModule = options.sourceType === "module";
  79. const tokenTranslator =
  80. options.tokens === true ?
  81. new TokenTranslator(tokTypes, code) :
  82. null;
  83. // Initialize acorn parser.
  84. super({
  85. ecmaVersion: isModule ? Math.max(6, ecmaVersion) : ecmaVersion,
  86. sourceType: isModule ? "module" : "script",
  87. ranges: options.range === true || options.attachComment === true,
  88. locations: options.loc === true,
  89. // Truthy value is true for backward compatibility.
  90. allowReturnOutsideFunction: Boolean(ecmaFeatures.globalReturn),
  91. // Collect tokens
  92. onToken: (token) => {
  93. if (tokenTranslator) {
  94. // Use `tokens`, `ecmaVersion`, and `jsxAttrValueToken` in the state.
  95. tokenTranslator.onToken(token, this[STATE]);
  96. }
  97. if (token.type !== tokTypes.eof) {
  98. this[STATE].lastToken = token;
  99. }
  100. },
  101. // Collect comments
  102. onComment: (block, text, start, end, startLoc, endLoc) => {
  103. if (this[STATE].comments) {
  104. const comment = convertAcornCommentToEsprimaComment(block, text, start, end, startLoc, endLoc);
  105. this[STATE].comments.push(comment);
  106. if (options.attachComment === true) {
  107. commentAttachment.addComment(comment);
  108. }
  109. }
  110. }
  111. }, code);
  112. // TODO: remove global state.
  113. commentAttachment.reset();
  114. // Initialize internal state.
  115. this[STATE] = {
  116. tokens: tokenTranslator ? [] : null,
  117. comments: options.comment === true || options.attachComment === true ? [] : null,
  118. attachComment: options.attachComment === true,
  119. impliedStrict: ecmaFeatures.impliedStrict === true && this.options.ecmaVersion >= 5,
  120. ecmaVersion: this.options.ecmaVersion,
  121. jsxAttrValueToken: false,
  122. lastToken: null
  123. };
  124. }
  125. tokenize() {
  126. do {
  127. this.next();
  128. } while (this.type !== tokTypes.eof);
  129. const extra = this[STATE];
  130. const tokens = extra.tokens;
  131. if (extra.comments) {
  132. tokens.comments = extra.comments;
  133. }
  134. return tokens;
  135. }
  136. finishNode(...args) {
  137. const result = super.finishNode(...args);
  138. return this[ESPRIMA_FINISH_NODE](result);
  139. }
  140. finishNodeAt(...args) {
  141. const result = super.finishNodeAt(...args);
  142. return this[ESPRIMA_FINISH_NODE](result);
  143. }
  144. parse() {
  145. const extra = this[STATE];
  146. const program = super.parse();
  147. program.sourceType = this.options.sourceType;
  148. if (extra.comments) {
  149. program.comments = extra.comments;
  150. }
  151. if (extra.tokens) {
  152. program.tokens = extra.tokens;
  153. }
  154. /*
  155. * Adjust opening and closing position of program to match Esprima.
  156. * Acorn always starts programs at range 0 whereas Esprima starts at the
  157. * first AST node's start (the only real difference is when there's leading
  158. * whitespace or leading comments). Acorn also counts trailing whitespace
  159. * as part of the program whereas Esprima only counts up to the last token.
  160. */
  161. if (program.range) {
  162. program.range[0] = program.body.length ? program.body[0].range[0] : program.range[0];
  163. program.range[1] = extra.lastToken ? extra.lastToken.range[1] : program.range[1];
  164. }
  165. if (program.loc) {
  166. program.loc.start = program.body.length ? program.body[0].loc.start : program.loc.start;
  167. program.loc.end = extra.lastToken ? extra.lastToken.loc.end : program.loc.end;
  168. }
  169. return program;
  170. }
  171. parseTopLevel(node) {
  172. if (this[STATE].impliedStrict) {
  173. this.strict = true;
  174. }
  175. return super.parseTopLevel(node);
  176. }
  177. /**
  178. * Overwrites the default raise method to throw Esprima-style errors.
  179. * @param {int} pos The position of the error.
  180. * @param {string} message The error message.
  181. * @throws {SyntaxError} A syntax error.
  182. * @returns {void}
  183. */
  184. raise(pos, message) {
  185. const loc = acorn.getLineInfo(this.input, pos);
  186. const err = new SyntaxError(message);
  187. err.index = pos;
  188. err.lineNumber = loc.line;
  189. err.column = loc.column + 1; // acorn uses 0-based columns
  190. throw err;
  191. }
  192. /**
  193. * Overwrites the default raise method to throw Esprima-style errors.
  194. * @param {int} pos The position of the error.
  195. * @param {string} message The error message.
  196. * @throws {SyntaxError} A syntax error.
  197. * @returns {void}
  198. */
  199. raiseRecoverable(pos, message) {
  200. this.raise(pos, message);
  201. }
  202. /**
  203. * Overwrites the default unexpected method to throw Esprima-style errors.
  204. * @param {int} pos The position of the error.
  205. * @throws {SyntaxError} A syntax error.
  206. * @returns {void}
  207. */
  208. unexpected(pos) {
  209. let message = "Unexpected token";
  210. if (pos !== null && pos !== void 0) {
  211. this.pos = pos;
  212. if (this.options.locations) {
  213. while (this.pos < this.lineStart) {
  214. this.lineStart = this.input.lastIndexOf("\n", this.lineStart - 2) + 1;
  215. --this.curLine;
  216. }
  217. }
  218. this.nextToken();
  219. }
  220. if (this.end > this.start) {
  221. message += " " + this.input.slice(this.start, this.end);
  222. }
  223. this.raise(this.start, message);
  224. }
  225. /*
  226. * Esprima-FB represents JSX strings as tokens called "JSXText", but Acorn-JSX
  227. * uses regular tt.string without any distinction between this and regular JS
  228. * strings. As such, we intercept an attempt to read a JSX string and set a flag
  229. * on extra so that when tokens are converted, the next token will be switched
  230. * to JSXText via onToken.
  231. */
  232. jsx_readString(quote) { // eslint-disable-line camelcase
  233. const result = super.jsx_readString(quote);
  234. if (this.type === tokTypes.string) {
  235. this[STATE].jsxAttrValueToken = true;
  236. }
  237. return result;
  238. }
  239. /**
  240. * Performs last-minute Esprima-specific compatibility checks and fixes.
  241. * @param {ASTNode} result The node to check.
  242. * @returns {ASTNode} The finished node.
  243. */
  244. [ESPRIMA_FINISH_NODE](result) {
  245. // Acorn doesn't count the opening and closing backticks as part of templates
  246. // so we have to adjust ranges/locations appropriately.
  247. if (result.type === "TemplateElement") {
  248. // additional adjustment needed if ${ is the last token
  249. const terminalDollarBraceL = this.input.slice(result.end, result.end + 2) === "${";
  250. if (result.range) {
  251. result.range[0]--;
  252. result.range[1] += (terminalDollarBraceL ? 2 : 1);
  253. }
  254. if (result.loc) {
  255. result.loc.start.column--;
  256. result.loc.end.column += (terminalDollarBraceL ? 2 : 1);
  257. }
  258. }
  259. if (this[STATE].attachComment) {
  260. commentAttachment.processComment(result);
  261. }
  262. if (result.type.indexOf("Function") > -1 && !result.generator) {
  263. result.generator = false;
  264. }
  265. return result;
  266. }
  267. };