PageRenderTime 38ms CodeModel.GetById 20ms RepoModel.GetById 0ms app.codeStats 1ms

/node_modules/project-grunt/node_modules/grunt-contrib-jshint/node_modules/jshint/src/lex.js

https://gitlab.com/nVySin/Mo_Grabber
JavaScript | 1843 lines | 1435 code | 216 blank | 192 comment | 176 complexity | bd3143f5c73659aa3307330d909acc36 MD5 | raw file
  1. /*
  2. * Lexical analysis and token construction.
  3. */
  4. "use strict";
  5. var _ = require("lodash");
  6. var events = require("events");
  7. var reg = require("./reg.js");
  8. var state = require("./state.js").state;
  9. var unicodeData = require("../data/ascii-identifier-data.js");
  10. var asciiIdentifierStartTable = unicodeData.asciiIdentifierStartTable;
  11. var asciiIdentifierPartTable = unicodeData.asciiIdentifierPartTable;
  12. var nonAsciiIdentifierStartTable = require("../data/non-ascii-identifier-start.js");
  13. var nonAsciiIdentifierPartTable = require("../data/non-ascii-identifier-part-only.js");
  14. // Some of these token types are from JavaScript Parser API
  15. // while others are specific to JSHint parser.
  16. // JS Parser API: https://developer.mozilla.org/en-US/docs/SpiderMonkey/Parser_API
  17. var Token = {
  18. Identifier: 1,
  19. Punctuator: 2,
  20. NumericLiteral: 3,
  21. StringLiteral: 4,
  22. Comment: 5,
  23. Keyword: 6,
  24. NullLiteral: 7,
  25. BooleanLiteral: 8,
  26. RegExp: 9,
  27. TemplateHead: 10,
  28. TemplateMiddle: 11,
  29. TemplateTail: 12,
  30. NoSubstTemplate: 13
  31. };
  32. var Context = {
  33. Block: 1,
  34. Template: 2
  35. };
  36. // Object that handles postponed lexing verifications that checks the parsed
  37. // environment state.
  38. function asyncTrigger() {
  39. var _checks = [];
  40. return {
  41. push: function(fn) {
  42. _checks.push(fn);
  43. },
  44. check: function() {
  45. for (var check = 0; check < _checks.length; ++check) {
  46. _checks[check]();
  47. }
  48. _checks.splice(0, _checks.length);
  49. }
  50. };
  51. }
  52. /*
  53. * Lexer for JSHint.
  54. *
  55. * This object does a char-by-char scan of the provided source code
  56. * and produces a sequence of tokens.
  57. *
  58. * var lex = new Lexer("var i = 0;");
  59. * lex.start();
  60. * lex.token(); // returns the next token
  61. *
  62. * You have to use the token() method to move the lexer forward
  63. * but you don't have to use its return value to get tokens. In addition
  64. * to token() method returning the next token, the Lexer object also
  65. * emits events.
  66. *
  67. * lex.on("Identifier", function(data) {
  68. * if (data.name.indexOf("_") >= 0) {
  69. * // Produce a warning.
  70. * }
  71. * });
  72. *
  73. * Note that the token() method returns tokens in a JSLint-compatible
  74. * format while the event emitter uses a slightly modified version of
  75. * Mozilla's JavaScript Parser API. Eventually, we will move away from
  76. * JSLint format.
  77. */
  78. function Lexer(source) {
  79. var lines = source;
  80. if (typeof lines === "string") {
  81. lines = lines
  82. .replace(/\r\n/g, "\n")
  83. .replace(/\r/g, "\n")
  84. .split("\n");
  85. }
  86. // If the first line is a shebang (#!), make it a blank and move on.
  87. // Shebangs are used by Node scripts.
  88. if (lines[0] && lines[0].substr(0, 2) === "#!") {
  89. if (lines[0].indexOf("node") !== -1) {
  90. state.option.node = true;
  91. }
  92. lines[0] = "";
  93. }
  94. this.emitter = new events.EventEmitter();
  95. this.source = source;
  96. this.setLines(lines);
  97. this.prereg = true;
  98. this.line = 0;
  99. this.char = 1;
  100. this.from = 1;
  101. this.input = "";
  102. this.inComment = false;
  103. this.context = [];
  104. this.templateStarts = [];
  105. for (var i = 0; i < state.option.indent; i += 1) {
  106. state.tab += " ";
  107. }
  108. }
  109. Lexer.prototype = {
  110. _lines: [],
  111. inContext: function(ctxType) {
  112. return this.context.length > 0 && this.context[this.context.length - 1].type === ctxType;
  113. },
  114. pushContext: function(ctxType) {
  115. this.context.push({ type: ctxType });
  116. },
  117. popContext: function() {
  118. return this.context.pop();
  119. },
  120. isContext: function(context) {
  121. return this.context.length > 0 && this.context[this.context.length - 1] === context;
  122. },
  123. currentContext: function() {
  124. return this.context.length > 0 && this.context[this.context.length - 1];
  125. },
  126. getLines: function() {
  127. this._lines = state.lines;
  128. return this._lines;
  129. },
  130. setLines: function(val) {
  131. this._lines = val;
  132. state.lines = this._lines;
  133. },
  134. /*
  135. * Return the next i character without actually moving the
  136. * char pointer.
  137. */
  138. peek: function(i) {
  139. return this.input.charAt(i || 0);
  140. },
  141. /*
  142. * Move the char pointer forward i times.
  143. */
  144. skip: function(i) {
  145. i = i || 1;
  146. this.char += i;
  147. this.input = this.input.slice(i);
  148. },
  149. /*
  150. * Subscribe to a token event. The API for this method is similar
  151. * Underscore.js i.e. you can subscribe to multiple events with
  152. * one call:
  153. *
  154. * lex.on("Identifier Number", function(data) {
  155. * // ...
  156. * });
  157. */
  158. on: function(names, listener) {
  159. names.split(" ").forEach(function(name) {
  160. this.emitter.on(name, listener);
  161. }.bind(this));
  162. },
  163. /*
  164. * Trigger a token event. All arguments will be passed to each
  165. * listener.
  166. */
  167. trigger: function() {
  168. this.emitter.emit.apply(this.emitter, Array.prototype.slice.call(arguments));
  169. },
  170. /*
  171. * Postpone a token event. the checking condition is set as
  172. * last parameter, and the trigger function is called in a
  173. * stored callback. To be later called using the check() function
  174. * by the parser. This avoids parser's peek() to give the lexer
  175. * a false context.
  176. */
  177. triggerAsync: function(type, args, checks, fn) {
  178. checks.push(function() {
  179. if (fn()) {
  180. this.trigger(type, args);
  181. }
  182. }.bind(this));
  183. },
  184. /*
  185. * Extract a punctuator out of the next sequence of characters
  186. * or return 'null' if its not possible.
  187. *
  188. * This method's implementation was heavily influenced by the
  189. * scanPunctuator function in the Esprima parser's source code.
  190. */
  191. scanPunctuator: function() {
  192. var ch1 = this.peek();
  193. var ch2, ch3, ch4;
  194. switch (ch1) {
  195. // Most common single-character punctuators
  196. case ".":
  197. if ((/^[0-9]$/).test(this.peek(1))) {
  198. return null;
  199. }
  200. if (this.peek(1) === "." && this.peek(2) === ".") {
  201. return {
  202. type: Token.Punctuator,
  203. value: "..."
  204. };
  205. }
  206. /* falls through */
  207. case "(":
  208. case ")":
  209. case ";":
  210. case ",":
  211. case "[":
  212. case "]":
  213. case ":":
  214. case "~":
  215. case "?":
  216. return {
  217. type: Token.Punctuator,
  218. value: ch1
  219. };
  220. // A block/object opener
  221. case "{":
  222. this.pushContext(Context.Block);
  223. return {
  224. type: Token.Punctuator,
  225. value: ch1
  226. };
  227. // A block/object closer
  228. case "}":
  229. if (this.inContext(Context.Block)) {
  230. this.popContext();
  231. }
  232. return {
  233. type: Token.Punctuator,
  234. value: ch1
  235. };
  236. // A pound sign (for Node shebangs)
  237. case "#":
  238. return {
  239. type: Token.Punctuator,
  240. value: ch1
  241. };
  242. // We're at the end of input
  243. case "":
  244. return null;
  245. }
  246. // Peek more characters
  247. ch2 = this.peek(1);
  248. ch3 = this.peek(2);
  249. ch4 = this.peek(3);
  250. // 4-character punctuator: >>>=
  251. if (ch1 === ">" && ch2 === ">" && ch3 === ">" && ch4 === "=") {
  252. return {
  253. type: Token.Punctuator,
  254. value: ">>>="
  255. };
  256. }
  257. // 3-character punctuators: === !== >>> <<= >>=
  258. if (ch1 === "=" && ch2 === "=" && ch3 === "=") {
  259. return {
  260. type: Token.Punctuator,
  261. value: "==="
  262. };
  263. }
  264. if (ch1 === "!" && ch2 === "=" && ch3 === "=") {
  265. return {
  266. type: Token.Punctuator,
  267. value: "!=="
  268. };
  269. }
  270. if (ch1 === ">" && ch2 === ">" && ch3 === ">") {
  271. return {
  272. type: Token.Punctuator,
  273. value: ">>>"
  274. };
  275. }
  276. if (ch1 === "<" && ch2 === "<" && ch3 === "=") {
  277. return {
  278. type: Token.Punctuator,
  279. value: "<<="
  280. };
  281. }
  282. if (ch1 === ">" && ch2 === ">" && ch3 === "=") {
  283. return {
  284. type: Token.Punctuator,
  285. value: ">>="
  286. };
  287. }
  288. // Fat arrow punctuator
  289. if (ch1 === "=" && ch2 === ">") {
  290. return {
  291. type: Token.Punctuator,
  292. value: ch1 + ch2
  293. };
  294. }
  295. // 2-character punctuators: <= >= == != ++ -- << >> && ||
  296. // += -= *= %= &= |= ^= (but not /=, see below)
  297. if (ch1 === ch2 && ("+-<>&|".indexOf(ch1) >= 0)) {
  298. return {
  299. type: Token.Punctuator,
  300. value: ch1 + ch2
  301. };
  302. }
  303. if ("<>=!+-*%&|^".indexOf(ch1) >= 0) {
  304. if (ch2 === "=") {
  305. return {
  306. type: Token.Punctuator,
  307. value: ch1 + ch2
  308. };
  309. }
  310. return {
  311. type: Token.Punctuator,
  312. value: ch1
  313. };
  314. }
  315. // Special case: /=.
  316. if (ch1 === "/") {
  317. if (ch2 === "=") {
  318. return {
  319. type: Token.Punctuator,
  320. value: "/="
  321. };
  322. }
  323. return {
  324. type: Token.Punctuator,
  325. value: "/"
  326. };
  327. }
  328. return null;
  329. },
  330. /*
  331. * Extract a comment out of the next sequence of characters and/or
  332. * lines or return 'null' if its not possible. Since comments can
  333. * span across multiple lines this method has to move the char
  334. * pointer.
  335. *
  336. * In addition to normal JavaScript comments (// and /*) this method
  337. * also recognizes JSHint- and JSLint-specific comments such as
  338. * /*jshint, /*jslint, /*globals and so on.
  339. */
  340. scanComments: function() {
  341. var ch1 = this.peek();
  342. var ch2 = this.peek(1);
  343. var rest = this.input.substr(2);
  344. var startLine = this.line;
  345. var startChar = this.char;
  346. // Create a comment token object and make sure it
  347. // has all the data JSHint needs to work with special
  348. // comments.
  349. function commentToken(label, body, opt) {
  350. var special = ["jshint", "jslint", "members", "member", "globals", "global", "exported"];
  351. var isSpecial = false;
  352. var value = label + body;
  353. var commentType = "plain";
  354. opt = opt || {};
  355. if (opt.isMultiline) {
  356. value += "*/";
  357. }
  358. body = body.replace(/\n/g, " ");
  359. special.forEach(function(str) {
  360. if (isSpecial) {
  361. return;
  362. }
  363. // Don't recognize any special comments other than jshint for single-line
  364. // comments. This introduced many problems with legit comments.
  365. if (label === "//" && str !== "jshint") {
  366. return;
  367. }
  368. if (body.charAt(str.length) === " " && body.substr(0, str.length) === str) {
  369. isSpecial = true;
  370. label = label + str;
  371. body = body.substr(str.length);
  372. }
  373. if (!isSpecial && body.charAt(0) === " " && body.charAt(str.length + 1) === " " &&
  374. body.substr(1, str.length) === str) {
  375. isSpecial = true;
  376. label = label + " " + str;
  377. body = body.substr(str.length + 1);
  378. }
  379. if (!isSpecial) {
  380. return;
  381. }
  382. switch (str) {
  383. case "member":
  384. commentType = "members";
  385. break;
  386. case "global":
  387. commentType = "globals";
  388. break;
  389. default:
  390. commentType = str;
  391. }
  392. });
  393. return {
  394. type: Token.Comment,
  395. commentType: commentType,
  396. value: value,
  397. body: body,
  398. isSpecial: isSpecial,
  399. isMultiline: opt.isMultiline || false,
  400. isMalformed: opt.isMalformed || false
  401. };
  402. }
  403. // End of unbegun comment. Raise an error and skip that input.
  404. if (ch1 === "*" && ch2 === "/") {
  405. this.trigger("error", {
  406. code: "E018",
  407. line: startLine,
  408. character: startChar
  409. });
  410. this.skip(2);
  411. return null;
  412. }
  413. // Comments must start either with // or /*
  414. if (ch1 !== "/" || (ch2 !== "*" && ch2 !== "/")) {
  415. return null;
  416. }
  417. // One-line comment
  418. if (ch2 === "/") {
  419. this.skip(this.input.length); // Skip to the EOL.
  420. return commentToken("//", rest);
  421. }
  422. var body = "";
  423. /* Multi-line comment */
  424. if (ch2 === "*") {
  425. this.inComment = true;
  426. this.skip(2);
  427. while (this.peek() !== "*" || this.peek(1) !== "/") {
  428. if (this.peek() === "") { // End of Line
  429. body += "\n";
  430. // If we hit EOF and our comment is still unclosed,
  431. // trigger an error and end the comment implicitly.
  432. if (!this.nextLine()) {
  433. this.trigger("error", {
  434. code: "E017",
  435. line: startLine,
  436. character: startChar
  437. });
  438. this.inComment = false;
  439. return commentToken("/*", body, {
  440. isMultiline: true,
  441. isMalformed: true
  442. });
  443. }
  444. } else {
  445. body += this.peek();
  446. this.skip();
  447. }
  448. }
  449. this.skip(2);
  450. this.inComment = false;
  451. return commentToken("/*", body, { isMultiline: true });
  452. }
  453. },
  454. /*
  455. * Extract a keyword out of the next sequence of characters or
  456. * return 'null' if its not possible.
  457. */
  458. scanKeyword: function() {
  459. var result = /^[a-zA-Z_$][a-zA-Z0-9_$]*/.exec(this.input);
  460. var keywords = [
  461. "if", "in", "do", "var", "for", "new",
  462. "try", "let", "this", "else", "case",
  463. "void", "with", "enum", "while", "break",
  464. "catch", "throw", "const", "yield", "class",
  465. "super", "return", "typeof", "delete",
  466. "switch", "export", "import", "default",
  467. "finally", "extends", "function", "continue",
  468. "debugger", "instanceof"
  469. ];
  470. if (result && keywords.indexOf(result[0]) >= 0) {
  471. return {
  472. type: Token.Keyword,
  473. value: result[0]
  474. };
  475. }
  476. return null;
  477. },
  478. /*
  479. * Extract a JavaScript identifier out of the next sequence of
  480. * characters or return 'null' if its not possible. In addition,
  481. * to Identifier this method can also produce BooleanLiteral
  482. * (true/false) and NullLiteral (null).
  483. */
  484. scanIdentifier: function() {
  485. var id = "";
  486. var index = 0;
  487. var type, char;
  488. function isNonAsciiIdentifierStart(code) {
  489. return nonAsciiIdentifierStartTable.indexOf(code) > -1;
  490. }
  491. function isNonAsciiIdentifierPart(code) {
  492. return isNonAsciiIdentifierStart(code) || nonAsciiIdentifierPartTable.indexOf(code) > -1;
  493. }
  494. function isHexDigit(str) {
  495. return (/^[0-9a-fA-F]$/).test(str);
  496. }
  497. var readUnicodeEscapeSequence = function() {
  498. /*jshint validthis:true */
  499. index += 1;
  500. if (this.peek(index) !== "u") {
  501. return null;
  502. }
  503. var ch1 = this.peek(index + 1);
  504. var ch2 = this.peek(index + 2);
  505. var ch3 = this.peek(index + 3);
  506. var ch4 = this.peek(index + 4);
  507. var code;
  508. if (isHexDigit(ch1) && isHexDigit(ch2) && isHexDigit(ch3) && isHexDigit(ch4)) {
  509. code = parseInt(ch1 + ch2 + ch3 + ch4, 16);
  510. if (asciiIdentifierPartTable[code] || isNonAsciiIdentifierPart(code)) {
  511. index += 5;
  512. return "\\u" + ch1 + ch2 + ch3 + ch4;
  513. }
  514. return null;
  515. }
  516. return null;
  517. }.bind(this);
  518. var getIdentifierStart = function() {
  519. /*jshint validthis:true */
  520. var chr = this.peek(index);
  521. var code = chr.charCodeAt(0);
  522. if (code === 92) {
  523. return readUnicodeEscapeSequence();
  524. }
  525. if (code < 128) {
  526. if (asciiIdentifierStartTable[code]) {
  527. index += 1;
  528. return chr;
  529. }
  530. return null;
  531. }
  532. if (isNonAsciiIdentifierStart(code)) {
  533. index += 1;
  534. return chr;
  535. }
  536. return null;
  537. }.bind(this);
  538. var getIdentifierPart = function() {
  539. /*jshint validthis:true */
  540. var chr = this.peek(index);
  541. var code = chr.charCodeAt(0);
  542. if (code === 92) {
  543. return readUnicodeEscapeSequence();
  544. }
  545. if (code < 128) {
  546. if (asciiIdentifierPartTable[code]) {
  547. index += 1;
  548. return chr;
  549. }
  550. return null;
  551. }
  552. if (isNonAsciiIdentifierPart(code)) {
  553. index += 1;
  554. return chr;
  555. }
  556. return null;
  557. }.bind(this);
  558. function removeEscapeSequences(id) {
  559. return id.replace(/\\u([0-9a-fA-F]{4})/g, function(m0, codepoint) {
  560. return String.fromCharCode(parseInt(codepoint, 16));
  561. });
  562. }
  563. char = getIdentifierStart();
  564. if (char === null) {
  565. return null;
  566. }
  567. id = char;
  568. for (;;) {
  569. char = getIdentifierPart();
  570. if (char === null) {
  571. break;
  572. }
  573. id += char;
  574. }
  575. switch (id) {
  576. case "true":
  577. case "false":
  578. type = Token.BooleanLiteral;
  579. break;
  580. case "null":
  581. type = Token.NullLiteral;
  582. break;
  583. default:
  584. type = Token.Identifier;
  585. }
  586. return {
  587. type: type,
  588. value: removeEscapeSequences(id),
  589. text: id,
  590. tokenLength: id.length
  591. };
  592. },
  593. /*
  594. * Extract a numeric literal out of the next sequence of
  595. * characters or return 'null' if its not possible. This method
  596. * supports all numeric literals described in section 7.8.3
  597. * of the EcmaScript 5 specification.
  598. *
  599. * This method's implementation was heavily influenced by the
  600. * scanNumericLiteral function in the Esprima parser's source code.
  601. */
  602. scanNumericLiteral: function() {
  603. var index = 0;
  604. var value = "";
  605. var length = this.input.length;
  606. var char = this.peek(index);
  607. var bad;
  608. var isAllowedDigit = isDecimalDigit;
  609. var base = 10;
  610. var isLegacy = false;
  611. function isDecimalDigit(str) {
  612. return (/^[0-9]$/).test(str);
  613. }
  614. function isOctalDigit(str) {
  615. return (/^[0-7]$/).test(str);
  616. }
  617. function isBinaryDigit(str) {
  618. return (/^[01]$/).test(str);
  619. }
  620. function isHexDigit(str) {
  621. return (/^[0-9a-fA-F]$/).test(str);
  622. }
  623. function isIdentifierStart(ch) {
  624. return (ch === "$") || (ch === "_") || (ch === "\\") ||
  625. (ch >= "a" && ch <= "z") || (ch >= "A" && ch <= "Z");
  626. }
  627. // Numbers must start either with a decimal digit or a point.
  628. if (char !== "." && !isDecimalDigit(char)) {
  629. return null;
  630. }
  631. if (char !== ".") {
  632. value = this.peek(index);
  633. index += 1;
  634. char = this.peek(index);
  635. if (value === "0") {
  636. // Base-16 numbers.
  637. if (char === "x" || char === "X") {
  638. isAllowedDigit = isHexDigit;
  639. base = 16;
  640. index += 1;
  641. value += char;
  642. }
  643. // Base-8 numbers.
  644. if (char === "o" || char === "O") {
  645. isAllowedDigit = isOctalDigit;
  646. base = 8;
  647. if (!state.option.esnext) {
  648. this.trigger("warning", {
  649. code: "W119",
  650. line: this.line,
  651. character: this.char,
  652. data: [ "Octal integer literal" ]
  653. });
  654. }
  655. index += 1;
  656. value += char;
  657. }
  658. // Base-2 numbers.
  659. if (char === "b" || char === "B") {
  660. isAllowedDigit = isBinaryDigit;
  661. base = 2;
  662. if (!state.option.esnext) {
  663. this.trigger("warning", {
  664. code: "W119",
  665. line: this.line,
  666. character: this.char,
  667. data: [ "Binary integer literal" ]
  668. });
  669. }
  670. index += 1;
  671. value += char;
  672. }
  673. // Legacy base-8 numbers.
  674. if (isOctalDigit(char)) {
  675. isAllowedDigit = isOctalDigit;
  676. base = 8;
  677. isLegacy = true;
  678. bad = false;
  679. index += 1;
  680. value += char;
  681. }
  682. // Decimal numbers that start with '0' such as '09' are illegal
  683. // but we still parse them and return as malformed.
  684. if (!isOctalDigit(char) && isDecimalDigit(char)) {
  685. index += 1;
  686. value += char;
  687. }
  688. }
  689. while (index < length) {
  690. char = this.peek(index);
  691. if (isLegacy && isDecimalDigit(char)) {
  692. // Numbers like '019' (note the 9) are not valid octals
  693. // but we still parse them and mark as malformed.
  694. bad = true;
  695. } else if (!isAllowedDigit(char)) {
  696. break;
  697. }
  698. value += char;
  699. index += 1;
  700. }
  701. if (isAllowedDigit !== isDecimalDigit) {
  702. if (!isLegacy && value.length <= 2) { // 0x
  703. return {
  704. type: Token.NumericLiteral,
  705. value: value,
  706. isMalformed: true
  707. };
  708. }
  709. if (index < length) {
  710. char = this.peek(index);
  711. if (isIdentifierStart(char)) {
  712. return null;
  713. }
  714. }
  715. return {
  716. type: Token.NumericLiteral,
  717. value: value,
  718. base: base,
  719. isLegacy: isLegacy,
  720. isMalformed: false
  721. };
  722. }
  723. }
  724. // Decimal digits.
  725. if (char === ".") {
  726. value += char;
  727. index += 1;
  728. while (index < length) {
  729. char = this.peek(index);
  730. if (!isDecimalDigit(char)) {
  731. break;
  732. }
  733. value += char;
  734. index += 1;
  735. }
  736. }
  737. // Exponent part.
  738. if (char === "e" || char === "E") {
  739. value += char;
  740. index += 1;
  741. char = this.peek(index);
  742. if (char === "+" || char === "-") {
  743. value += this.peek(index);
  744. index += 1;
  745. }
  746. char = this.peek(index);
  747. if (isDecimalDigit(char)) {
  748. value += char;
  749. index += 1;
  750. while (index < length) {
  751. char = this.peek(index);
  752. if (!isDecimalDigit(char)) {
  753. break;
  754. }
  755. value += char;
  756. index += 1;
  757. }
  758. } else {
  759. return null;
  760. }
  761. }
  762. if (index < length) {
  763. char = this.peek(index);
  764. if (isIdentifierStart(char)) {
  765. return null;
  766. }
  767. }
  768. return {
  769. type: Token.NumericLiteral,
  770. value: value,
  771. base: base,
  772. isMalformed: !isFinite(value)
  773. };
  774. },
  775. // Assumes previously parsed character was \ (=== '\\') and was not skipped.
  776. scanEscapeSequence: function(checks) {
  777. var allowNewLine = false;
  778. var jump = 1;
  779. this.skip();
  780. var char = this.peek();
  781. switch (char) {
  782. case "'":
  783. this.triggerAsync("warning", {
  784. code: "W114",
  785. line: this.line,
  786. character: this.char,
  787. data: [ "\\'" ]
  788. }, checks, function() {return state.jsonMode; });
  789. break;
  790. case "b":
  791. char = "\\b";
  792. break;
  793. case "f":
  794. char = "\\f";
  795. break;
  796. case "n":
  797. char = "\\n";
  798. break;
  799. case "r":
  800. char = "\\r";
  801. break;
  802. case "t":
  803. char = "\\t";
  804. break;
  805. case "0":
  806. char = "\\0";
  807. // Octal literals fail in strict mode.
  808. // Check if the number is between 00 and 07.
  809. var n = parseInt(this.peek(1), 10);
  810. this.triggerAsync("warning", {
  811. code: "W115",
  812. line: this.line,
  813. character: this.char
  814. }, checks,
  815. function() { return n >= 0 && n <= 7 && state.isStrict(); });
  816. break;
  817. case "u":
  818. var hexCode = this.input.substr(1, 4);
  819. var code = parseInt(hexCode, 16);
  820. if (isNaN(code)) {
  821. this.trigger("warning", {
  822. code: "W052",
  823. line: this.line,
  824. character: this.char,
  825. data: [ "u" + hexCode ]
  826. });
  827. }
  828. char = String.fromCharCode(code);
  829. jump = 5;
  830. break;
  831. case "v":
  832. this.triggerAsync("warning", {
  833. code: "W114",
  834. line: this.line,
  835. character: this.char,
  836. data: [ "\\v" ]
  837. }, checks, function() { return state.jsonMode; });
  838. char = "\v";
  839. break;
  840. case "x":
  841. var x = parseInt(this.input.substr(1, 2), 16);
  842. this.triggerAsync("warning", {
  843. code: "W114",
  844. line: this.line,
  845. character: this.char,
  846. data: [ "\\x-" ]
  847. }, checks, function() { return state.jsonMode; });
  848. char = String.fromCharCode(x);
  849. jump = 3;
  850. break;
  851. case "\\":
  852. char = "\\\\";
  853. break;
  854. case "\"":
  855. char = "\\\"";
  856. break;
  857. case "/":
  858. break;
  859. case "":
  860. allowNewLine = true;
  861. char = "";
  862. break;
  863. }
  864. return { char: char, jump: jump, allowNewLine: allowNewLine };
  865. },
  866. /*
  867. * Extract a template literal out of the next sequence of characters
  868. * and/or lines or return 'null' if its not possible. Since template
  869. * literals can span across multiple lines, this method has to move
  870. * the char pointer.
  871. */
  872. scanTemplateLiteral: function(checks) {
  873. var tokenType;
  874. var value = "";
  875. var ch;
  876. var startLine = this.line;
  877. var startChar = this.char;
  878. var depth = this.templateStarts.length;
  879. if (!state.option.esnext) {
  880. // Only lex template strings in ESNext mode.
  881. return null;
  882. } else if (this.peek() === "`") {
  883. // Template must start with a backtick.
  884. tokenType = Token.TemplateHead;
  885. this.templateStarts.push({ line: this.line, char: this.char });
  886. depth = this.templateStarts.length;
  887. this.skip(1);
  888. this.pushContext(Context.Template);
  889. } else if (this.inContext(Context.Template) && this.peek() === "}") {
  890. // If we're in a template context, and we have a '}', lex a TemplateMiddle.
  891. tokenType = Token.TemplateMiddle;
  892. } else {
  893. // Go lex something else.
  894. return null;
  895. }
  896. while (this.peek() !== "`") {
  897. while ((ch = this.peek()) === "") {
  898. value += "\n";
  899. if (!this.nextLine()) {
  900. // Unclosed template literal --- point to the starting "`"
  901. var startPos = this.templateStarts.pop();
  902. this.trigger("error", {
  903. code: "E052",
  904. line: startPos.line,
  905. character: startPos.char
  906. });
  907. return {
  908. type: tokenType,
  909. value: value,
  910. startLine: startLine,
  911. startChar: startChar,
  912. isUnclosed: true,
  913. depth: depth,
  914. context: this.popContext()
  915. };
  916. }
  917. }
  918. if (ch === '$' && this.peek(1) === '{') {
  919. value += '${';
  920. this.skip(2);
  921. return {
  922. type: tokenType,
  923. value: value,
  924. startLine: startLine,
  925. startChar: startChar,
  926. isUnclosed: false,
  927. depth: depth,
  928. context: this.currentContext()
  929. };
  930. } else if (ch === '\\') {
  931. var escape = this.scanEscapeSequence(checks);
  932. value += escape.char;
  933. this.skip(escape.jump);
  934. } else if (ch !== '`') {
  935. // Otherwise, append the value and continue.
  936. value += ch;
  937. this.skip(1);
  938. }
  939. }
  940. // Final value is either NoSubstTemplate or TemplateTail
  941. tokenType = tokenType === Token.TemplateHead ? Token.NoSubstTemplate : Token.TemplateTail;
  942. this.skip(1);
  943. this.templateStarts.pop();
  944. return {
  945. type: tokenType,
  946. value: value,
  947. startLine: startLine,
  948. startChar: startChar,
  949. isUnclosed: false,
  950. depth: depth,
  951. context: this.popContext()
  952. };
  953. },
  954. /*
  955. * Extract a string out of the next sequence of characters and/or
  956. * lines or return 'null' if its not possible. Since strings can
  957. * span across multiple lines this method has to move the char
  958. * pointer.
  959. *
  960. * This method recognizes pseudo-multiline JavaScript strings:
  961. *
  962. * var str = "hello\
  963. * world";
  964. */
  965. scanStringLiteral: function(checks) {
  966. /*jshint loopfunc:true */
  967. var quote = this.peek();
  968. // String must start with a quote.
  969. if (quote !== "\"" && quote !== "'") {
  970. return null;
  971. }
  972. // In JSON strings must always use double quotes.
  973. this.triggerAsync("warning", {
  974. code: "W108",
  975. line: this.line,
  976. character: this.char // +1?
  977. }, checks, function() { return state.jsonMode && quote !== "\""; });
  978. var value = "";
  979. var startLine = this.line;
  980. var startChar = this.char;
  981. var allowNewLine = false;
  982. this.skip();
  983. while (this.peek() !== quote) {
  984. if (this.peek() === "") { // End Of Line
  985. // If an EOL is not preceded by a backslash, show a warning
  986. // and proceed like it was a legit multi-line string where
  987. // author simply forgot to escape the newline symbol.
  988. //
  989. // Another approach is to implicitly close a string on EOL
  990. // but it generates too many false positives.
  991. if (!allowNewLine) {
  992. this.trigger("warning", {
  993. code: "W112",
  994. line: this.line,
  995. character: this.char
  996. });
  997. } else {
  998. allowNewLine = false;
  999. // Otherwise show a warning if multistr option was not set.
  1000. // For JSON, show warning no matter what.
  1001. this.triggerAsync("warning", {
  1002. code: "W043",
  1003. line: this.line,
  1004. character: this.char
  1005. }, checks, function() { return !state.option.multistr; });
  1006. this.triggerAsync("warning", {
  1007. code: "W042",
  1008. line: this.line,
  1009. character: this.char
  1010. }, checks, function() { return state.jsonMode && state.option.multistr; });
  1011. }
  1012. // If we get an EOF inside of an unclosed string, show an
  1013. // error and implicitly close it at the EOF point.
  1014. if (!this.nextLine()) {
  1015. this.trigger("error", {
  1016. code: "E029",
  1017. line: startLine,
  1018. character: startChar
  1019. });
  1020. return {
  1021. type: Token.StringLiteral,
  1022. value: value,
  1023. startLine: startLine,
  1024. startChar: startChar,
  1025. isUnclosed: true,
  1026. quote: quote
  1027. };
  1028. }
  1029. } else { // Any character other than End Of Line
  1030. allowNewLine = false;
  1031. var char = this.peek();
  1032. var jump = 1; // A length of a jump, after we're done
  1033. // parsing this character.
  1034. if (char < " ") {
  1035. // Warn about a control character in a string.
  1036. this.trigger("warning", {
  1037. code: "W113",
  1038. line: this.line,
  1039. character: this.char,
  1040. data: [ "<non-printable>" ]
  1041. });
  1042. }
  1043. // Special treatment for some escaped characters.
  1044. if (char === "\\") {
  1045. var parsed = this.scanEscapeSequence(checks);
  1046. char = parsed.char;
  1047. jump = parsed.jump;
  1048. allowNewLine = parsed.allowNewLine;
  1049. }
  1050. value += char;
  1051. this.skip(jump);
  1052. }
  1053. }
  1054. this.skip();
  1055. return {
  1056. type: Token.StringLiteral,
  1057. value: value,
  1058. startLine: startLine,
  1059. startChar: startChar,
  1060. isUnclosed: false,
  1061. quote: quote
  1062. };
  1063. },
  1064. /*
  1065. * Extract a regular expression out of the next sequence of
  1066. * characters and/or lines or return 'null' if its not possible.
  1067. *
  1068. * This method is platform dependent: it accepts almost any
  1069. * regular expression values but then tries to compile and run
  1070. * them using system's RegExp object. This means that there are
  1071. * rare edge cases where one JavaScript engine complains about
  1072. * your regular expression while others don't.
  1073. */
  1074. scanRegExp: function() {
  1075. var index = 0;
  1076. var length = this.input.length;
  1077. var char = this.peek();
  1078. var value = char;
  1079. var body = "";
  1080. var flags = [];
  1081. var malformed = false;
  1082. var isCharSet = false;
  1083. var terminated;
  1084. var scanUnexpectedChars = function() {
  1085. // Unexpected control character
  1086. if (char < " ") {
  1087. malformed = true;
  1088. this.trigger("warning", {
  1089. code: "W048",
  1090. line: this.line,
  1091. character: this.char
  1092. });
  1093. }
  1094. // Unexpected escaped character
  1095. if (char === "<") {
  1096. malformed = true;
  1097. this.trigger("warning", {
  1098. code: "W049",
  1099. line: this.line,
  1100. character: this.char,
  1101. data: [ char ]
  1102. });
  1103. }
  1104. }.bind(this);
  1105. // Regular expressions must start with '/'
  1106. if (!this.prereg || char !== "/") {
  1107. return null;
  1108. }
  1109. index += 1;
  1110. terminated = false;
  1111. // Try to get everything in between slashes. A couple of
  1112. // cases aside (see scanUnexpectedChars) we don't really
  1113. // care whether the resulting expression is valid or not.
  1114. // We will check that later using the RegExp object.
  1115. while (index < length) {
  1116. char = this.peek(index);
  1117. value += char;
  1118. body += char;
  1119. if (isCharSet) {
  1120. if (char === "]") {
  1121. if (this.peek(index - 1) !== "\\" || this.peek(index - 2) === "\\") {
  1122. isCharSet = false;
  1123. }
  1124. }
  1125. if (char === "\\") {
  1126. index += 1;
  1127. char = this.peek(index);
  1128. body += char;
  1129. value += char;
  1130. scanUnexpectedChars();
  1131. }
  1132. index += 1;
  1133. continue;
  1134. }
  1135. if (char === "\\") {
  1136. index += 1;
  1137. char = this.peek(index);
  1138. body += char;
  1139. value += char;
  1140. scanUnexpectedChars();
  1141. if (char === "/") {
  1142. index += 1;
  1143. continue;
  1144. }
  1145. if (char === "[") {
  1146. index += 1;
  1147. continue;
  1148. }
  1149. }
  1150. if (char === "[") {
  1151. isCharSet = true;
  1152. index += 1;
  1153. continue;
  1154. }
  1155. if (char === "/") {
  1156. body = body.substr(0, body.length - 1);
  1157. terminated = true;
  1158. index += 1;
  1159. break;
  1160. }
  1161. index += 1;
  1162. }
  1163. // A regular expression that was never closed is an
  1164. // error from which we cannot recover.
  1165. if (!terminated) {
  1166. this.trigger("error", {
  1167. code: "E015",
  1168. line: this.line,
  1169. character: this.from
  1170. });
  1171. return void this.trigger("fatal", {
  1172. line: this.line,
  1173. from: this.from
  1174. });
  1175. }
  1176. // Parse flags (if any).
  1177. while (index < length) {
  1178. char = this.peek(index);
  1179. if (!/[gim]/.test(char)) {
  1180. break;
  1181. }
  1182. flags.push(char);
  1183. value += char;
  1184. index += 1;
  1185. }
  1186. // Check regular expression for correctness.
  1187. try {
  1188. new RegExp(body, flags.join(""));
  1189. } catch (err) {
  1190. malformed = true;
  1191. this.trigger("error", {
  1192. code: "E016",
  1193. line: this.line,
  1194. character: this.char,
  1195. data: [ err.message ] // Platform dependent!
  1196. });
  1197. }
  1198. return {
  1199. type: Token.RegExp,
  1200. value: value,
  1201. flags: flags,
  1202. isMalformed: malformed
  1203. };
  1204. },
  1205. /*
  1206. * Scan for any occurrence of non-breaking spaces. Non-breaking spaces
  1207. * can be mistakenly typed on OS X with option-space. Non UTF-8 web
  1208. * pages with non-breaking pages produce syntax errors.
  1209. */
  1210. scanNonBreakingSpaces: function() {
  1211. return state.option.nonbsp ?
  1212. this.input.search(/(\u00A0)/) : -1;
  1213. },
  1214. /*
  1215. * Scan for characters that get silently deleted by one or more browsers.
  1216. */
  1217. scanUnsafeChars: function() {
  1218. return this.input.search(reg.unsafeChars);
  1219. },
  1220. /*
  1221. * Produce the next raw token or return 'null' if no tokens can be matched.
  1222. * This method skips over all space characters.
  1223. */
  1224. next: function(checks) {
  1225. this.from = this.char;
  1226. // Move to the next non-space character.
  1227. var start;
  1228. if (/\s/.test(this.peek())) {
  1229. start = this.char;
  1230. while (/\s/.test(this.peek())) {
  1231. this.from += 1;
  1232. this.skip();
  1233. }
  1234. }
  1235. // Methods that work with multi-line structures and move the
  1236. // character pointer.
  1237. var match = this.scanComments() ||
  1238. this.scanStringLiteral(checks) ||
  1239. this.scanTemplateLiteral(checks);
  1240. if (match) {
  1241. return match;
  1242. }
  1243. // Methods that don't move the character pointer.
  1244. match =
  1245. this.scanRegExp() ||
  1246. this.scanPunctuator() ||
  1247. this.scanKeyword() ||
  1248. this.scanIdentifier() ||
  1249. this.scanNumericLiteral();
  1250. if (match) {
  1251. this.skip(match.tokenLength || match.value.length);
  1252. return match;
  1253. }
  1254. // No token could be matched, give up.
  1255. return null;
  1256. },
  1257. /*
  1258. * Switch to the next line and reset all char pointers. Once
  1259. * switched, this method also checks for other minor warnings.
  1260. */
  1261. nextLine: function() {
  1262. var char;
  1263. if (this.line >= this.getLines().length) {
  1264. return false;
  1265. }
  1266. this.input = this.getLines()[this.line];
  1267. this.line += 1;
  1268. this.char = 1;
  1269. this.from = 1;
  1270. var inputTrimmed = this.input.trim();
  1271. var startsWith = function() {
  1272. return _.some(arguments, function(prefix) {
  1273. return inputTrimmed.indexOf(prefix) === 0;
  1274. });
  1275. };
  1276. var endsWith = function() {
  1277. return _.some(arguments, function(suffix) {
  1278. return inputTrimmed.indexOf(suffix, inputTrimmed.length - suffix.length) !== -1;
  1279. });
  1280. };
  1281. // If we are ignoring linter errors, replace the input with empty string
  1282. // if it doesn't already at least start or end a multi-line comment
  1283. if (state.ignoreLinterErrors === true) {
  1284. if (!startsWith("/*", "//") && !(this.inComment && endsWith("*/"))) {
  1285. this.input = "";
  1286. }
  1287. }
  1288. char = this.scanNonBreakingSpaces();
  1289. if (char >= 0) {
  1290. this.trigger("warning", { code: "W125", line: this.line, character: char + 1 });
  1291. }
  1292. this.input = this.input.replace(/\t/g, state.tab);
  1293. char = this.scanUnsafeChars();
  1294. if (char >= 0) {
  1295. this.trigger("warning", { code: "W100", line: this.line, character: char });
  1296. }
  1297. // If there is a limit on line length, warn when lines get too
  1298. // long.
  1299. if (state.option.maxlen && state.option.maxlen < this.input.length) {
  1300. var inComment = this.inComment ||
  1301. startsWith.call(inputTrimmed, "//") ||
  1302. startsWith.call(inputTrimmed, "/*");
  1303. var shouldTriggerError = !inComment || !reg.maxlenException.test(inputTrimmed);
  1304. if (shouldTriggerError) {
  1305. this.trigger("warning", { code: "W101", line: this.line, character: this.input.length });
  1306. }
  1307. }
  1308. return true;
  1309. },
  1310. /*
  1311. * This is simply a synonym for nextLine() method with a friendlier
  1312. * public name.
  1313. */
  1314. start: function() {
  1315. this.nextLine();
  1316. },
  1317. /*
  1318. * Produce the next token. This function is called by advance() to get
  1319. * the next token. It returns a token in a JSLint-compatible format.
  1320. */
  1321. token: function() {
  1322. /*jshint loopfunc:true */
  1323. var checks = asyncTrigger();
  1324. var token;
  1325. function isReserved(token, isProperty) {
  1326. if (!token.reserved) {
  1327. return false;
  1328. }
  1329. var meta = token.meta;
  1330. if (meta && meta.isFutureReservedWord && state.inES5()) {
  1331. // ES3 FutureReservedWord in an ES5 environment.
  1332. if (!meta.es5) {
  1333. return false;
  1334. }
  1335. // Some ES5 FutureReservedWord identifiers are active only
  1336. // within a strict mode environment.
  1337. if (meta.strictOnly) {
  1338. if (!state.option.strict && !state.isStrict()) {
  1339. return false;
  1340. }
  1341. }
  1342. if (isProperty) {
  1343. return false;
  1344. }
  1345. }
  1346. return true;
  1347. }
  1348. // Produce a token object.
  1349. var create = function(type, value, isProperty, token) {
  1350. /*jshint validthis:true */
  1351. var obj;
  1352. if (type !== "(endline)" && type !== "(end)") {
  1353. this.prereg = false;
  1354. }
  1355. if (type === "(punctuator)") {
  1356. switch (value) {
  1357. case ".":
  1358. case ")":
  1359. case "~":
  1360. case "#":
  1361. case "]":
  1362. case "++":
  1363. case "--":
  1364. this.prereg = false;
  1365. break;
  1366. default:
  1367. this.prereg = true;
  1368. }
  1369. obj = Object.create(state.syntax[value] || state.syntax["(error)"]);
  1370. }
  1371. if (type === "(identifier)") {
  1372. if (value === "return" || value === "case" || value === "typeof") {
  1373. this.prereg = true;
  1374. }
  1375. if (_.has(state.syntax, value)) {
  1376. obj = Object.create(state.syntax[value] || state.syntax["(error)"]);
  1377. // If this can't be a reserved keyword, reset the object.
  1378. if (!isReserved(obj, isProperty && type === "(identifier)")) {
  1379. obj = null;
  1380. }
  1381. }
  1382. }
  1383. if (!obj) {
  1384. obj = Object.create(state.syntax[type]);
  1385. }
  1386. obj.identifier = (type === "(identifier)");
  1387. obj.type = obj.type || type;
  1388. obj.value = value;
  1389. obj.line = this.line;
  1390. obj.character = this.char;
  1391. obj.from = this.from;
  1392. if (obj.identifier && token) obj.raw_text = token.text || token.value;
  1393. if (token && token.startLine && token.startLine !== this.line) {
  1394. obj.startLine = token.startLine;
  1395. }
  1396. if (token && token.context) {
  1397. // Context of current token
  1398. obj.context = token.context;
  1399. }
  1400. if (token && token.depth) {
  1401. // Nested template depth
  1402. obj.depth = token.depth;
  1403. }
  1404. if (token && token.isUnclosed) {
  1405. // Mark token as unclosed string / template literal
  1406. obj.isUnclosed = token.isUnclosed;
  1407. }
  1408. if (isProperty && obj.identifier) {
  1409. obj.isProperty = isProperty;
  1410. }
  1411. obj.check = checks.check;
  1412. return obj;
  1413. }.bind(this);
  1414. for (;;) {
  1415. if (!this.input.length) {
  1416. if (this.nextLine()) {
  1417. return create("(endline)", "");
  1418. }
  1419. if (this.exhausted) {
  1420. return null;
  1421. }
  1422. this.exhausted = true;
  1423. return create("(end)", "");
  1424. }
  1425. token = this.next(checks);
  1426. if (!token) {
  1427. if (this.input.length) {
  1428. // Unexpected character.
  1429. this.trigger("error", {
  1430. code: "E024",
  1431. line: this.line,
  1432. character: this.char,
  1433. data: [ this.peek() ]
  1434. });
  1435. this.input = "";
  1436. }
  1437. continue;
  1438. }
  1439. switch (token.type) {
  1440. case Token.StringLiteral:
  1441. this.triggerAsync("String", {
  1442. line: this.line,
  1443. char: this.char,
  1444. from: this.from,
  1445. startLine: token.startLine,
  1446. startChar: token.startChar,
  1447. value: token.value,
  1448. quote: token.quote
  1449. }, checks, function() { return true; });
  1450. return create("(string)", token.value, null, token);
  1451. case Token.TemplateHead:
  1452. this.trigger("TemplateHead", {
  1453. line: this.line,
  1454. char: this.char,
  1455. from: this.from,
  1456. startLine: token.startLine,
  1457. startChar: token.startChar,
  1458. value: token.value
  1459. });
  1460. return create("(template)", token.value, null, token);
  1461. case Token.TemplateMiddle:
  1462. this.trigger("TemplateMiddle", {
  1463. line: this.line,
  1464. char: this.char,
  1465. from: this.from,
  1466. startLine: token.startLine,
  1467. startChar: token.startChar,
  1468. value: token.value
  1469. });
  1470. return create("(template middle)", token.value, null, token);
  1471. case Token.TemplateTail:
  1472. this.trigger("TemplateTail", {
  1473. line: this.line,
  1474. char: this.char,
  1475. from: this.from,
  1476. startLine: token.startLine,
  1477. startChar: token.startChar,
  1478. value: token.value
  1479. });
  1480. return create("(template tail)", token.value, null, token);
  1481. case Token.NoSubstTemplate:
  1482. this.trigger("NoSubstTemplate", {
  1483. line: this.line,
  1484. char: this.char,
  1485. from: this.from,
  1486. startLine: token.startLine,
  1487. startChar: token.startChar,
  1488. value: token.value
  1489. });
  1490. return create("(no subst template)", token.value, null, token);
  1491. case Token.Identifier:
  1492. this.trigger("Identifier", {
  1493. line: this.line,
  1494. char: this.char,
  1495. from: this.form,
  1496. name: token.value,
  1497. raw_name: token.text,
  1498. isProperty: state.tokens.curr.id === "."
  1499. });
  1500. /* falls through */
  1501. case Token.Keyword:
  1502. case Token.NullLiteral:
  1503. case Token.BooleanLiteral:
  1504. return create("(identifier)", token.value, state.tokens.curr.id === ".", token);
  1505. case Token.NumericLiteral:
  1506. if (token.isMalformed) {
  1507. this.trigger("warning", {
  1508. code: "W045",
  1509. line: this.line,
  1510. character: this.char,
  1511. data: [ token.value ]
  1512. });
  1513. }
  1514. this.triggerAsync("warning", {
  1515. code: "W114",
  1516. line: this.line,
  1517. character: this.char,
  1518. data: [ "0x-" ]
  1519. }, checks, function() { return token.base === 16 && state.jsonMode; });
  1520. this.triggerAsync("warning", {
  1521. code: "W115",
  1522. line: this.line,
  1523. character: this.char
  1524. }, checks, function() {
  1525. return state.isStrict() && token.base === 8 && token.isLegacy;
  1526. });
  1527. this.trigger("Number", {
  1528. line: this.line,
  1529. char: this.char,
  1530. from: this.from,
  1531. value: token.value,
  1532. base: token.base,
  1533. isMalformed: token.malformed
  1534. });
  1535. return create("(number)", token.value);
  1536. case Token.RegExp:
  1537. return create("(regexp)", token.value);
  1538. case Token.Comment:
  1539. state.tokens.curr.comment = true;
  1540. if (token.isSpecial) {
  1541. return {
  1542. id: '(comment)',
  1543. value: token.value,
  1544. body: token.body,
  1545. type: token.commentType,
  1546. isSpecial: token.isSpecial,
  1547. line: this.line,
  1548. character: this.char,
  1549. from: this.from
  1550. };
  1551. }
  1552. break;
  1553. case "":
  1554. break;
  1555. default:
  1556. return create("(punctuator)", token.value);
  1557. }
  1558. }
  1559. }
  1560. };
  1561. exports.Lexer = Lexer;
  1562. exports.Context = Context;