b0VIM 8.0>i` Jmikebruce~mike/projects/oss/jshint/jshint/src/lex.jsutf-8 3210#"! Utp*<b  } v v yi 4ad&z}[10e n V U  w f Q = / !  x S Q P 5 w v k R : 3 2  k h $ R [<3*'TB>%?~zyM5ut?)%#"{zy }, return this.context.length > 0 && this.context[this.context.length - 1].type === ctxType; inContext: function(ctxType) { _lines: [],Lexer.prototype = {} } state.tab += " "; for (var i = 0; i < state.option.indent; i += 1) { this.templateStarts = []; this.context = []; this.inComment = false; this.input = ""; this.from = 1; this.char = 1; this.line = 0; this.prereg = true; this.setLines(lines); this.source = source; this.emitter = new events.EventEmitter(); } lines[0] = ""; } state.option.node = true; if (lines[0].indexOf("node") !== -1) { if (lines[0] && lines[0].substr(0, 2) === "#!") { // Shebangs are used by Node scripts. // If the first line is a shebang (#!), make it a blank and move on. } .split("\n"); .replace(/\r/g, "\n") .replace(/\r\n/g, "\n") lines = lines if (typeof lines === "string") { var lines = source;function Lexer(source) { */ * JSLint format. * Mozilla's JavaScript Parser API. Eventually, we will move away from * format while the event emitter uses a slightly modified version of * Note that the token() method returns tokens in a JSLint-compatible * * }); * } * // Produce a warning. * if (data.name.indexOf("_") >= 0) { * lex.on("Identifier", function(data) { * * emits events. * to token() method returning the next token, the Lexer object also * but you don't have to use its return value to get tokens. In addition * You have to use the token() method to move the lexer forward * * lex.token(); // returns the next token * lex.start(); * var lex = new Lexer("var i = 0;"); * * and produces a sequence of tokens. * This object does a char-by-char scan of the provided source code * * Lexer for JSHint./*} }; } _checks.splice(0, _checks.length); } _checks[check](); for (var check = 0; check < _checks.length; ++check) { check: function() { }, _checks.push(fn); push: function(fn) { return { var _checks = [];function asyncTrigger() {// environment state.// Object that handles postponed lexing verifications that checks the parsed} return str.length === 1 && isHex(str);function isHexDigit(str) {} return /^[0-9a-fA-F]+$/.test(str);function isHex(str) {}; Template: 2 Block: 1,var Context = {}; NoSubstTemplate: 13 TemplateTail: 12, TemplateMiddle: 11, TemplateHead: 10, RegExp: 9, Keyword: 6, Comment: 5, StringLiteral: 4, NumericLiteral: 3, Punctuator: 2, Identifier: 1,var Token = {// JS Parser API: https://developer.mozilla.org/en-US/docs/SpiderMonkey/Parser_API// while others are specific to JSHint parser.// Some of these token types are from JavaScript Parser APIvar es5IdentifierNames;// Loading of this module is deferred as an optimization for ES2015 inputvar nonAsciiIdentifierPartTable = require("../data/non-ascii-identifier-part-only.js");var nonAsciiIdentifierStartTable = require("../data/non-ascii-identifier-start.js");var asciiIdentifierPartTable = unicodeData.asciiIdentifierPartTable;var asciiIdentifierStartTable = unicodeData.asciiIdentifierStartTable;var unicodeData = require("../data/ascii-identifier-data.js");var state = require("./state.js").state;var reg = require("./reg.js");var events = require("events");var _ = require("lodash");"use strict"; */ * Lexical analysis and token construction./*adE 1 4xlkF.oT9 ~ } d 4 3  g ? " y q k g d c L 1 0 exports.Context = Context;exports.Lexer = Lexer;}; } } } return create("(punctuator)", token.value); default: break; } }; from: this.from character: this.char, line: this.line, isSpecial: token.isSpecial, type: token.commentType, body: token.body, value: token.value, id: '(comment)', return { if (token.isSpecial) { case Token.Comment: return create("(regexp)", token.value); case Token.RegExp: return create("(number)", token.value); }); isMalformed: token.isMalformed base: token.base, value: token.value, from: this.from, char: this.char, line: this.line, this.trigger("Number", { }); return state.isStrict() && token.isNonOctal; }, checks, function() { character: this.char line: this.line, code: "E068", this.triggerAsync("error", { }); return state.isStrict() && token.base === 8 && token.isLegacy; }, checks, function() { character: this.charad R meC" k c b 8  } obj.isPrope } obj.isProperty = isProperty; if (isProperty && obj.identifier) { } obj.isUnclosed = token.isUnclosed; // Mark token as unclosed string / template literal if (token && token.isUnclosed) { } obj.depth = token.depth; // Nested template depth if (token && token.depth) { } obj.context = token.context; // Context of current token if (token && token.context) { } obj.startLine = token.startLine; if (token && token.startLine && token.startLine !== this.line) { if (obj.identifier && token) obj.raw_text = token.text || token.value; obj.from = this.from; obj.character = this.char; obj.line = this.line; }ad"icN5"gf#  P # " { 0  y 2 ,  l K < ;  } k Y G 5 "  XPO*E i_WVpM$ _zrP/xpoE  } obj.isProperty = isProperty; if (isProperty && obj.identifier) { } obj.isUnclosed = token.isUnclosed; // Mark token as unclosed string / template literal if (token && token.isUnclosed) { } obj.depth = token.depth; // Nested template depth if (token && token.depth) { } obj.context = token.context; // Context of current token if (token && token.context) { } obj.startLine = token.startLine; if (token && token.startLine && token.startLine !== this.line) { if (obj.identifier && token) obj.raw_text = token.text || token.value; obj.from = this.from; obj.character = this obj.value = value; } else { obj.id = value; if (obj.type === "(punctuator)") { obj.type = obj.type || type; obj.identifier = (type === "(identifier)"); } obj = Object.create(state.syntax[type]); if (!obj) { } this.prereg = true; if (type === "(template)" || type === "(template middle)") { } } obj = Object.create(state.syntax[value] || state.syntax["(error)"]); if (_.has(state.syntax, value)) { } this.prereg = true; value === "default" || value === "extends") { value === "await" || value === "new" || value === "delete" || value === "typeof" || value === "instanceof" || value === "void" || if (value === "return" || value === "case" || value === "yield" || if (type === "(identifier)") { } obj = Object.create(state.syntax[value] || state.syntax["(error)"]); } this.prereg = true; default: break; this.prereg = false; case "--": case "++": case "}": case "]": case "#": case "~": case ")": case ".": switch (value) { if (type === "(punctuator)") { } this.prereg = false; if (type !== "(endline)" && type !== "(end)") { var obj; /*jshint validthis:true */ var create = function(type, value, isProperty, token) { // Produce a token object. var token; var checks = asyncTrigger(); /*jshint loopfunc:true */ token: function() { */ * the next token. It returns a token in a JSLint-compatible format. * Produce the next token. This function is called by advance() to get /* }, return true; } } ); function() { return true; } checks, { code: "W101", line: this.line, character: this.input.length }, "warning", this.triggerAsync( if (shouldTriggerError) { var shouldTriggerError = !inComment || !reg.maxlenException.test(inputTrimmed); startsWith.call(inputTrimmed, "/*"); startsWith.call(inputTrimmed, "//") || var inComment = this.inComment || state.option.maxlen < this.input.length) { if (!this.ignoringLinterErrors && state.option.maxlen && // long. // If there is a limit on line length, warn when lines get too this.input = this.input.replace(/\t/g, state.tab); } ); function() { return true; } checks, { code: "W125", line: this.line, character: char + 1 }, "warning", this.triggerAsync( if (char >= 0) { char = this.scanNonBreakingSpaces(); } } this.input = ""; if (!startsWith("/*", "//") && !(this.inComment && endsWith("*/"))) {adCe;10nM*  ~ c Y X F > = !  d >  t M 2  H G & ` C 7 tY3 }S8ED'o<  {zY8[C(v^CB line: this.line, code: "W115", this.triggerAsync("warning", { }, checks, function() { return token.base === 16 && state.jsonMode; }); data: [ "0x-" ] character: this.char, line: this.line, code: "W114", this.triggerAsync("warning", { } }); data: [ token.value ] character: this.char, line: this.line, code: "E067", this.trigger("error", { if (token.isMalformed) { case Token.NumericLiteral: return create("(identifier)", token.value, state.tokens.curr.id === ".", token); case Token.Keyword: /* falls through */ }, checks, function() { return true; }); isProperty: state.tokens.curr.id === "." raw_name: token.text, name: token.value, from: this.from, char: this.char, line: this.line, this.triggerAsync("Identifier", { case Token.Identifier: return create("(no subst template)", token.value, null, token); }); value: token.value startChar: token.startChar, startLine: token.startLine, from: this.from, char: this.char, line: this.line, this.trigger("NoSubstTemplate", { case Token.NoSubstTemplate: return create("(template tail)", token.value, null, token); }); value: token.value startChar: token.startChar, startLine: token.startLine, from: this.from, char: this.char, line: this.line, this.trigger("TemplateTail", { case Token.TemplateTail: return create("(template middle)", token.value, null, token); }); value: token.value startChar: token.startChar, startLine: token.startLine, from: this.from, char: this.char, line: this.line, this.trigger("TemplateMiddle", { case Token.TemplateMiddle: return create("(template)", token.value, null, token); }); value: token.value startChar: token.startChar, startLine: token.startLine, from: this.from, char: this.char, line: this.line, this.trigger("TemplateHead", { case Token.TemplateHead: return create("(string)", token.value, null, token); }, checks, function() { return true; }); quote: token.quote value: token.value, startChar: token.startChar, startLine: token.startLine, from: this.from, char: this.char, line: this.line, this.triggerAsync("String", { case Token.StringLiteral: switch (token.type) { } continue; } this.input = ""; }); data: [ this.peek() ] character: this.char, line: this.line, code: "E024", this.trigger("error", { // Unexpected character. if (this.input.length) { if (!token) { token = this.next(checks); } return create("(end)", ""); this.exhausted = true; } return null; if (this.exhausted) { } return create("(endline)", ""); if (this.nextLine(checks)) { if (!this.input.length) { for (;;) { }.bind(this); return obj; obj.check = checks.check;adNmW>!}i_ b \ [ B * K +  v o j i d  o N $    x ] \ / utGfG+yxML;650}|M932}K[QJI if (this.ignoringLinterErrors === true) { // if it doesn't already at least start or end a multi-line comment // If we are ignoring linter errors, replace the input with empty string }; }); return inputTrimmed.indexOf(suffix, inputTrimmed.length - suffix.length) !== -1; return _.some(arguments, function(suffix) { var endsWith = function() { }; }); return inputTrimmed.indexOf(prefix) === 0; return _.some(arguments, function(prefix) { var startsWith = function() { var inputTrimmed = this.input.trim(); this.from = 1; this.char = 1; this.line += 1; this.input = this.getLines()[this.line]; } return false; if (this.line >= this.getLines().length) { var char; nextLine: function(checks) { */ * switched, this method also checks for other minor warnings. * Switch to the next line and reset all char pointers. Once /* }, return null; // No token could be matched, give up. } return match; this.skip(match.tokenLength || match.value.length); if (match) { this.scanNumericLiteral(checks); this.scanIdentifier(checks) || this.scanKeyword() || this.scanPunctuator() || this.scanRegExp(checks) || match = // Methods that don't move the character pointer. } return match; if (match) { this.scanTemplateLiteral(checks); this.scanStringLiteral(checks) || var match = this.scanComments(checks) || // character pointer. // Methods that work with multi-line structures and move the } this.skip(); this.from += 1; while (reg.whitespace.test(this.peek())) { // Move to the next non-space character. this.from = this.char; next: function(checks) { */ * This method skips over all space characters. * Produce the next raw token or return 'null' if no tokens can be matched. /* }, this.input.search(/(\u00A0)/) : -1; return state.option.nonbsp ? scanNonBreakingSpaces: function() { */ * pages with non-breaking pages produce syntax errors. * can be mistakenly typed on OS X with option-space. Non UTF-8 web * Scan for any occurrence of non-breaking spaces. Non-breaking spaces /* }, }; isMalformed: malformed value: value, type: Token.RegExp, return { } }); character: this.char line: this.line, code: "W148", this.trigger("warning", { } else if (allFlags.indexOf("s") > -1 && !reg.regexpDot.test(body)) { }); data: [ malformedDesc ] character: this.char, line: this.line, code: "E016", this.trigger("error", { malformed = true; if (malformedDesc) { } malformedDesc = err.message; */ * property on the error object) is platform dependent. * validate RegExp literals, the description (exposed as the "data" * Because JSHint relies on the current engine's RegExp parser to /** } catch (err) { new RegExp(body, es5Flags); try { // Check regular expression for correctness. } }, checks, function() { return state.option.regexpu; }); character: this.char line: this.line, code: "W147", this.triggerAsync("warning", { if (allFlags.indexOf("u") === -1) { } index += 1; allFlags += char; value += char;ad4xZ9uZPO6 M " ! k R : 0 * ) z X ; $  v b : - #  c ? Nv]QP QpDz]F8\OEUM54 allFlags += char; } malformedDesc = "Duplicate RegExp flag"; if (allFlags.indexOf(char) > -1) { } es5Flags += char; } else { } malformedDesc = "Duplicate RegExp flag"; if (value.indexOf("s") > -1) { } ); function() { return true; } checks, }, data: [ "DotAll RegExp flag", "9" ] character: this.char, line: this.line, code: "W119", { "warning", this.triggerAsync( if (!state.inES9()) { } else if (char === "s") { body = translateUFlag(body); } malformedDesc = "Invalid quantifier"; } else if (hasInvalidQuantifier) { malformedDesc = "Invalid escape"; if (hasInvalidEscape) { }(groupReferences, groupCount, escapedChars, reg)); }); reg.regexpSyntaxChars.test(escapedChar); reg.regexpCharClasses.test(escapedChar) || reg.regexpControlEscapes.test(escapedChar) || escapedChar === "0" || escapedChar === "/" || return escapedChar === "u" || return !escapedChars.split("").every(function(escapedChar) { } return true; if (hasInvalidGroup) { }); } return true; if (groupReference > groupCount) { var hasInvalidGroup = groupReferences.some(function(groupReference) { var hasInvalidEscape = (function(groupReferences, groupCount, escapedChars, reg) { } ); function() { return true; } checks, }, data: [ "Unicode RegExp flag", "6" ] character: this.char, line: this.line, code: "W119", { "warning", this.triggerAsync( if (!state.inES6(true)) { } else if (char === "u") { } ); function() { return true; } checks, }, data: [ "Sticky RegExp flag", "6" ] character: this.char, line: this.line, code: "W119", { "warning", this.triggerAsync( if (!state.inES6(true)) { if (char === "y") { } break; if (!/[gimyus]/.test(char)) { char = this.peek(index); while (index < length) { // Parse flags (if any). } }); from: this.from line: this.line, return void this.trigger("fatal", { }); character: this.from line: this.line, code: "E015", this.trigger("error", { if (!terminated) { // error from which we cannot recover. // A regular expression that was never closed is an } index += 1; } break; index += 1; terminated = true; body = body.substr(0, body.length - 1); } else if (char === "/") { isGroup = false; } groupCount += 1; } else { } ); hasUFlag checks, }, data: [ "Quantified quantifiable" ] character: this.char, line: this.line, code: "E016", { "error", this.triggerAsync( if (reg.regexpQuantifiers.test(this.peek(index + 1))) {adywPO=cJ f B &  a ` +    j  r W L : 9 b CyxaE|tsX#"oaE&{zh`_H4"vbP/}sR4 isQuantifiable = false; if (isQuantifiable) { } else if (char === ")") { } isQuantifiable = true; (this.peek(index + 2) === "=" || this.peek(index + 2) === "!")) { if (this.peek(index + 1) === "?" && isGroup = true; } else if (char === "(") { continue; index += 1; isCharSet = true; if (char === "[") { } hasInvalidQuantifier = !checkQuantifier(); if (char === "{" && !hasInvalidQuantifier) { } continue; index += 1; if (isCharSet) { } continue; } ); hasUFlag checks, }, data: [ "Character class used in range" ] character: this.char, line: this.line, code: "E016", { "error", this.triggerAsync( reg.regexpCharClasses.test(escapeSequence)) { if (isCharSet && (this.peek(index) === "-" || isCharSetRange) && escapeSequence = scanRegexpEscapeSequence(); if (char === "\\") { } } isCharSetRange = true; } else if (char === "-") { } isCharSet = false; if (this.peek(index - 1) !== "\\" || this.peek(index - 2) === "\\") { if (char === "]") { if (isCharSet) { body += char; value += char; char = this.peek(index); isCharSetRange &= char === "-"; // iteration. // distinct locations, `isCharSetRange` is re-set at the onset of // Because an iteration of this loop may terminate in a number of while (index < length) { // We will check that later using the RegExp object. // care whether the resulting expression is valid or not. // cases aside (see scanRegexpEscapeSequence) we don't really // Try to get everything in between slashes. A couple of terminated = false; index += 1; } return null; if (!this.prereg || char !== "/") { // Regular expressions must start with '/' }.bind(this); ); astralSubstitute /[\uD800-\uDBFF][\uDC00-\uDFFF]/g, .replace( // with the "u" flag. // throwing on regular expressions that are only valid in combination // Replace each paired surrogate with a single ASCII symbol to avoid }.bind(this)) return astralSubstitute; } return String.fromCharCode(codePoint); if (codePoint <= 0xFFFF) { } return $0; if (reg.regexpSyntaxChars.test(literal)) { literal = String.fromCharCode(codePoint); } return; }); data: [ char ] character: this.char, line: this.line, code: "E016", this.trigger("error", { malformed = true; if (codePoint > 0x10FFFF) { var literal; var codePoint = parseInt($1 || $2, 16); .replace(/\\u\{([0-9a-fA-F]+)\}|\\u([a-fA-F0-9]{4})/g, function($0, $1, $2) { // information.) // symbols. (See the above note on `astralSubstitute` for more // character or a constant ASCII code point in the case of astral // Replace every Unicode escape sequence with the equivalent BMP return body var astralSubstitute = "\uFFFF"; // that would not be detected by this substitution. // scenarios. For example, `[\u{1044f}-\u{10440}]` is an invalid patternadNyaH!nS: n Y K /  z m C ( j P 5  w l d c = # t Y L :  eH&mlYGFgO3 waYXBnXPO7Z // Note: replacing with '\uFFFF' enables false positives in unlikely // approximation. // translating an ES6 "u"-flagged pattern to an ES5-compatible // The BMP character to use as a replacement for astral symbols when var translateUFlag = function(body) { }.bind(this); return true; } return Number(lowerBound) <= Number(upperBound); if (upperBound) { } return false; if (next !== "}") { } next = this.peek(lookahead + 1); upperBound += next; lookahead += 1; while (reg.decimalDigit.test(next)) { next = this.peek(lookahead + 1); lookahead += 1; } return false; if (next !== ",") { } return true; if (next === "}") { } return false; if (!lowerBound) { } next = this.peek(lookahead + 1); lowerBound += next; lookahead += 1; while (reg.decimalDigit.test(next)) { next = this.peek(lookahead + 1); var next; var upperBound = ""; var lowerBound = ""; var lookahead = index; var checkQuantifier = function() { }.bind(this); return char; value += char; body += char; index += 1; } ); hasUFlag checks, }, data: [ "Invalid decimal escape sequence" ] character: this.char, line: this.line, code: "E016", { "error", this.triggerAsync( } else if (char === "0" && reg.decimalDigit.test(this.peek(index + 1))) { ); function() { return true; } checks, }, data: [ char ] character: this.char, line: this.line, code: "W049", { "warning", this.triggerAsync( malformed = true; if (char === "<") { // Unexpected escaped character } ); function() { return true; } checks, }, character: this.char line: this.line, code: "W048", { "warning", this.triggerAsync( malformed = true; if (char < " ") { // Unexpected control character } } return sequence; index = x + 1; value += sequence; body += sequence; sequence += "}"; } else if (sequence.length > 2) { ); hasUFlag checks, }, data: [ "Invalid Unicode escape sequence" ] character: this.char, line: this.line, code: "E016", { "error", this.triggerAsync( if (next !== "}") { } next = this.peek(x); x += 1; sequence += next; while (isHex(next)) { next = this.peek(x); sequence = "u{"; var x = index + 2; if (char === "u" && this.peek(index + 1) === "{") { escapedChars += char; } return sequence; groupReferences.push(Number(sequence)); } next = this.peek(index + 1); value += char; body += char; sequence += char; char = next; index += 1; while (reg.nonzeroDigit.test(next) || next === "0") {ad8,v:9bXW y _ =  v V  v _ Q 5  } p f e +  l b a  ~R7{_F3,'&!]hbA. y^C# cK3jQ,+ next = this.peek(index + 1); sequence = char; if (reg.nonzeroDigit.test(char)) { char = this.peek(index); index += 1; var next, sequence; var scanRegexpEscapeSequence = function() { var terminated, malformedDesc; var groupCount = 0; var escapeSequence; var hasUFlag = function() { return allFlags.indexOf("u") > -1; }; var escapedChars = ""; var hasInvalidQuantifier = false; var isQuantifiable = false; var isGroup = false; var isCharSetRange = false; var isCharSet = false; var malformed = false; var es5Flags = ""; var allFlags = ""; var groupReferences = []; var body = ""; var value = char; var char = this.peek(); var length = this.input.length; var index = 0; scanRegExp: function(checks) { */ * your regular expression while others don't. * rare edge cases where one JavaScript engine complains about * them using system's RegExp object. This means that there are * regular expression values but then tries to compile and run * This method is platform dependent: it accepts almost any * * characters and/or lines or return 'null' if its not possible. * Extract a regular expression out of the next sequence of /* }, }; quote: quote isUnclosed: false, startChar: startChar, startLine: startLine, value: value, type: Token.StringLiteral, return { this.skip(); } } } this.skip(jump); value += char; if (char !== "") { // correct character column offset. // and errors reported in the subsequent loop iteration have the // this case, `this.char` should not be incremented so that warnings // If char is the empty string, end of the line has been reached. In } allowNewLine = parsed.allowNewLine; jump = parsed.jump; char = parsed.char; var parsed = this.scanEscapeSequence(checks); if (char === "\\") { // Special treatment for some escaped characters. } ); function() { return true; } checks, }, data: [ "" ] character: this.char, line: this.line, code: "W113", { "warning", this.triggerAsync( // Warn about a control character in a string. if (char < " ") { // parsing this character. var jump = 1; // A length of a jump, after we're done var char = this.peek(); allowNewLine = false; } else { // Any character other than End Of Line } }; quote: quote isUnclosed: true, startChar: startChar, startLine: startLine, value: value, type: Token.StringLiteral, return { if (!this.nextLine(checks)) { // error and implicitly close it at the EOF point. // If we get an EOF inside of an unclosed string, show an } }, checks, function() { return state.jsonMode && state.option.multistr; }); character: this.char line: this.line, code: "W042", this.triggerAsync("warning", { }, checks, function() { return !state.option.multistr; }); character: this.char line: this.line, code: "W043", this.triggerAsync("warning", {ad}mV0{Z5' { a : - #   y Y 9   z \ <  q  p W C "     H:5{^]6 mK~Z+*eZK' I // For JSON, show warning no matter what. // Otherwise show a warning if multistr option was not set. allowNewLine = false; } else { }); character: this.char line: this.line, code: "W112", this.trigger("warning", { // TODO: Emit error E029 and remove W112. // This condition unequivocally describes a syntax error. if (!allowNewLine) { // but it generates too many false positives. // Another approach is to implicitly close a string on EOL // // author simply forgot to escape the newline symbol. // and proceed like it was a legit multi-line string where // If an EOL is not preceded by a backslash, show a warning if (this.peek() === "") { // End Of Line while (this.peek() !== quote) { this.skip(); var allowNewLine = false; var startChar = this.char; var startLine = this.line; var value = ""; }, checks, function() { return state.jsonMode && quote !== "\""; }); character: this.char // +1? line: this.line, code: "W108", this.triggerAsync("warning", { // In JSON strings must always use double quotes. } return null; if (quote !== "\"" && quote !== "'") { // String must start with a quote. var quote = this.peek(); /*jshint loopfunc:true */ scanStringLiteral: function(checks) { */ * world"; * var str = "hello\ * * This method recognizes pseudo-multiline JavaScript strings: * * pointer. * span across multiple lines this method has to move the char * lines or return 'null' if its not possible. Since strings can * Extract a string out of the next sequence of characters and/or /* }, }; context: this.popContext() depth: depth, isUnclosed: false, startChar: startChar, startLine: startLine, value: value, type: tokenType, return { this.templateStarts.pop(); this.skip(1); tokenType = tokenType === Token.TemplateHead ? Token.NoSubstTemplate : Token.TemplateTail; // Final value is either NoSubstTemplate or TemplateTail } } this.skip(1); value += ch; // Otherwise, append the value and continue. } else if (ch !== '`') { this.skip(escape.jump); value += escape.char; var escape = this.scanEscapeSequence(checks); } else if (ch === '\\') { }; context: this.currentContext() depth: depth, isUnclosed: false, startChar: startChar, startLine: startLine, value: value, type: tokenType, return { this.skip(2); value += '${'; if (ch === '$' && this.peek(1) === '{') { } } }; context: this.popContext() depth: depth, isUnclosed: true, startChar: startChar, startLine: startLine, value: value, type: tokenType, return { }); character: startPos.char line: startPos.line, code: "E052", this.trigger("error", { var startPos = this.templateStarts.pop(); // Unclosed template literal --- point to the starting "`" if (!this.nextLine(checks)) { value += "\n"; while ((ch = this.peek()) === "") { while (this.peek() !== "`") { } return null; // Go lex something else. } else {ad Bs_RD0#vQ;" y k ] O A '  u h Z ,  g E -  v h C -  v @ ?   {zUE8)uon+&% L5/hgH( `SAnD0jBA tokenType = Token.TemplateMiddle; // If we're in a template context, and we have a '}', lex a TemplateMiddle. } else if (this.inContext(Context.Template) && this.peek() === "}") { this.pushContext(Context.Template); this.skip(1); depth = this.templateStarts.length; this.templateStarts.push({ line: this.line, char: this.char }); tokenType = Token.TemplateHead; // Template must start with a backtick. } ); function() { return true; } checks, }, data: ["template literal syntax", "6"] character: this.char, line: this.line, code: "W119", { "warning", this.triggerAsync( if (!state.inES6(true)) { if (this.peek() === "`") { var depth = this.templateStarts.length; var startChar = this.char; var startLine = this.line; var ch; var value = ""; var tokenType; scanTemplateLiteral: function(checks) { */ * the char pointer. * literals can span across multiple lines, this method has to move * and/or lines or return 'null' if its not possible. Since template * Extract a template literal out of the next sequence of characters /* }, return { char: char, jump: jump, allowNewLine: allowNewLine }; } break; char = ""; allowNewLine = true; case "": break; case "/": break; char = "\\\""; case "\"": break; char = "\\\\"; case "\\": break; jump = 3; char = String.fromCharCode(x); }, checks, function() { return state.jsonMode; }); data: [ "\\x-" ] character: this.char, line: this.line, code: "W114", this.triggerAsync("warning", { var x = parseInt(this.input.substr(1, 2), 16); case "x": break; char = "\v"; }, checks, function() { return state.jsonMode; }); data: [ "\\v" ] character: this.char, line: this.line, code: "W114", this.triggerAsync("warning", { case "v": break; jump = 5; char = String.fromCharCode(code); } }); data: [ "u" + sequence ] character: this.char, line: this.line, code: "W052", this.trigger("warning", { // TODO: Re-factor as an "error" (not a "warning"). // This condition unequivocally describes a syntax error. if (!isHex(sequence)) { var code = parseInt(sequence, 16); var sequence = this.input.substr(1, 4); case "u": break; function() { return state.isStrict(); }); }, checks, character: this.char line: this.line, code: "W115", this.triggerAsync("warning", { char = "\\" + char; case "7": case "6": case "5": case "4": case "3": case "2": case "1": break; function() { return n >= 0 && n <= 7 && state.isStrict(); }); }, checks, character: this.char line: this.line, code: "W115", this.triggerAsync("warning", { var n = parseInt(this.peek(1), 10); // Check if the number is between 00 and 07. // Octal literals fail in strict mode. char = "\\0"; case "0": break; char = "\\t"; case "t": break; char = "\\r"; case "r": break; char = "\\n"; case "n": break; char = "\\f"; case "f": break; char = "\\b";ad ]\! tS- k L 5 %  t J ; / .  o Q D : 9  h P :  n M (  mNM#wvU2 fG" wL6pidcbxjE/ case "b": break; }, checks, function() {return state.jsonMode; }); data: [ "\\'" ] character: this.char, line: this.line, code: "W114", this.triggerAsync("warning", { case "'": switch (char) { var char = this.peek(); this.skip(); var jump = 1; var allowNewLine = false; scanEscapeSequence: function(checks) { // Assumes previously parsed character was \ (=== '\\') and was not skipped. }, }; isMalformed: false isNonOctal: isNonOctal, base: base, value: value, type: Token.NumericLiteral, return { }, checks, function() { return !isFinite(value); }); data: [ value ] character: this.char + value.length, line: this.line, code: "W045", this.triggerAsync("warning", { // TODO: Extend this check to other numeric literals } } return null; if (isIdentifierStart(char)) { char = this.peek(index); if (index < length) { } } return null; } else { } index += 1; value += char; } break; if (!isDecimalDigit(char)) { char = this.peek(index); while (index < length) { index += 1; value += char; if (isDecimalDigit(char)) { char = this.peek(index); } index += 1; value += this.peek(index); if (char === "+" || char === "-") { char = this.peek(index); index += 1; value += char; if (char === "e" || char === "E") { // Exponent part. } } index += 1; value += char; } break; if (!isDecimalDigit(char)) { char = this.peek(index); while (index < length) { index += 1; value += char; if (char === ".") { // Decimal digits. } } }; isMalformed: false isLegacy: isLegacy, base: base, value: value, type: Token.NumericLiteral, return { } } return null; if (isIdentifierStart(char)) { char = this.peek(index); if (index < length) { } }; isMalformed: true value: value, type: Token.NumericLiteral, return { } else if (!isLegacy && value.length <= 2) { // 0x index += 1; value += char; } ); function() { return true; } checks, }, data: [value + char] character: this.char, line: this.line, code: "E067", { "error", this.triggerAsync( if (isLegacy || isNonOctal) { } ); function() { return true; } checks, }, data: [ "BigInt", "bigint" ] character: this.char, line: this.line, code: "W144", { "warning", this.triggerAsync( if (!state.option.unstable.bigint) { if (isBigInt) { if (isAllowedDigit !== isDecimalDigit || isBigInt) { var isBigInt = this.peek(index) === 'n'; } index += 1; value += char; } break; if (!isAllowedDigit(char)) {adv}|oQ=- C a N :  _ ; 5 4  r O I H # ^ ] ,    q E   ^JI%xA0S)iC oedB umlM,+wvu } isAllowedDigit = isDecimalDigit; isNonOctal = true; isLegacy = false; base = 10; if (isLegacy && isNonOctalDigit(char)) { char = this.peek(index); while (index < length) { } } isNonOctal = true; } else if (isDecimalDigit(char)) { isLegacy = true; base = 8; isAllowedDigit = isOctalDigit; if (isOctalDigit(char)) { // Legacy base-8 numbers. } value += char; index += 1; } ); function() { return true; } checks, }, data: [ "Binary integer literal", "6" ] character: this.char, line: this.line, code: "W119", { "warning", this.triggerAsync( if (!state.inES6(true)) { base = 2; isAllowedDigit = isBinaryDigit; if (char === "b" || char === "B") { // Base-2 numbers. } value += char; index += 1; } ); function() { return true; } checks, }, data: [ "Octal integer literal", "6" ] character: this.char, line: this.line, code: "W119", { "warning", this.triggerAsync( if (!state.inES6(true)) { base = 8; isAllowedDigit = isOctalDigit; if (char === "o" || char === "O") { // Base-8 numbers. } value += char; index += 1; base = 16; isAllowedDigit = isHexDigit; if (char === "x" || char === "X") { // Base-16 numbers. if (value === "0") { char = this.peek(index); index += 1; value = this.peek(index); if (char !== ".") { } return null; if (char !== "." && !isDecimalDigit(char)) { // Numbers must start either with a decimal digit or a point. } (ch >= "a" && ch <= "z") || (ch >= "A" && ch <= "Z"); return (ch === "$") || (ch === "_") || (ch === "\\") || function isIdentifierStart(ch) { } return (/^[01]$/).test(str); function isBinaryDigit(str) { } return str === "8" || str === "9"; function isNonOctalDigit(str) { } return (/^[0-7]$/).test(str); function isOctalDigit(str) { } return (/^[0-9]$/).test(str); function isDecimalDigit(str) { var isNonOctal = false; var isLegacy = false; var base = 10; var isAllowedDigit = isDecimalDigit; var char = this.peek(index); var length = this.input.length; var value = ""; var index = 0; scanNumericLiteral: function(checks) { */ * scanNumericLiteral function in the Esprima parser's source code. * This method's implementation was heavily influenced by the * * of the EcmaScript 5 specification. * supports all numeric literals described in section 7.8.3 * characters or return 'null' if its not possible. This method * Extract a numeric literal out of the next sequence of /* }, }; tokenLength: id.length text: id, value: value, type: Token.Identifier, return { } } ); function() { return true; } checks, }, data: ["unicode 8", "6"]adb/UNM e / )  ] W V ( o ] \ 6 !   r K J s r H '  zK5srI({M7!vuLzga`QB FE character: this.char, line: this.line, code: "W119", { "warning", this.triggerAsync( if (!es5IdentifierNames.test(value)) { es5IdentifierNames = require("../data/es5-identifier-names.js"); if (!state.inES6(true)) { value = removeEscapeSequences(id); } id += char; } break; if (char === null) { char = getIdentifierPart(); for (;;) { id = char; } return null; if (char === null) { char = getIdentifierStart(); } }); return String.fromCharCode(parseInt(codepoint, 16)); return id.replace(/\\u([0-9a-fA-F]{4})/g, function(m0, codepoint) { function removeEscapeSequences(id) { }.bind(this); return null; } return chr; index += 1; if (isNonAsciiIdentifierPart(code)) { } return null; } return chr; index += 1; if (asciiIdentifierPartTable[code]) { if (code < 128) { } return readUnicodeEscapeSequence(); if (code === 92) { var code = chr.charCodeAt(0); var chr = this.peek(index); /*jshint validthis:true */ var getIdentifierPart = function() { }.bind(this); return null; } return chr; index += 1; if (isNonAsciiIdentifierStart(code)) { } return null; } return chr; index += 1; if (asciiIdentifierStartTable[code]) { if (code < 128) { } return readUnicodeEscapeSequence(); if (code === 92) { var code = chr.charCodeAt(0); var chr = this.peek(index); /*jshint validthis:true */ var getIdentifierStart = function() { }.bind(this); return null; } return null; } return "\\u" + sequence; index += 5; if (asciiIdentifierPartTable[code] || isNonAsciiIdentifierPart(code)) { code = parseInt(sequence, 16); if (isHex(sequence)) { var code; this.peek(index + 3) + this.peek(index + 4); var sequence = this.peek(index + 1) + this.peek(index + 2) + } return null; if (this.peek(index) !== "u") { index += 1; /*jshint validthis:true */ var readUnicodeEscapeSequence = function() { } return isNonAsciiIdentifierStart(code) || nonAsciiIdentifierPartTable.indexOf(code) > -1; function isNonAsciiIdentifierPart(code) { } return nonAsciiIdentifierStartTable.indexOf(code) > -1; function isNonAsciiIdentifierStart(code) { var char, value; var index = 0; var id = ""; scanIdentifier: function(checks) { */ * characters or return 'null' if its not possible. * Extract a JavaScript identifier out of the next sequence of /* }, return null; } }; value: result[0] type: Token.Keyword, return { if (result && keywords.indexOf(result[0]) >= 0) { ]; "debugger", "instanceof", "true", "false", "null", "async", "await" "finally", "extends", "function", "continue", "switch", "export", "import", "default", "super", "return", "typeof", "delete", "catch", "throw", "const", "yield", "class", "void", "with", "enum", "while", "break", "try", "let", "this", "else", "case", "if", "in", "do", "var", "for", "new", var keywords = [ad=uY4a { D 8 . -   } Z I 8 i N *  U 2  p Z F ( cM4  wd^]E.cONL$m?tV  |=< var result = /^[a-zA-Z_$][a-zA-Z0-9_$]*/.exec(this.input); scanKeyword: function() { */ * return 'null' if its not possible. * Extract a keyword out of the next sequence of characters or /* }, } return commentToken("/*", body, { isMultiline: true }); this.inComment = false; this.skip(2); } } this.skip(); body += this.peek(); } else { } }); isMalformed: true isMultiline: true, return commentToken("/*", body, { this.inComment = false; }); character: startChar line: startLine, code: "E017", this.trigger("error", { if (!this.nextLine(checks)) { // trigger an error and end the comment implicitly. // If we hit EOF and our comment is still unclosed, body += "\n"; if (this.peek() === "") { // End of Line while (this.peek() !== "*" || this.peek(1) !== "/") { this.skip(2); this.inComment = true; if (ch2 === "*") { /* Multi-line comment */ var body = ""; } return commentToken("//", rest); this.skip(this.input.length); // Skip to the EOL. if (ch2 === "/") { // One-line comment } return null; if (ch1 !== "/" || (ch2 !== "*" && ch2 !== "/")) { // Comments must start either with // or /* } return null; this.skip(2); }); character: startChar line: startLine, code: "E018", this.trigger("error", { if (ch1 === "*" && ch2 === "/") { // End of unbegun comment. Raise an error and skip that input. } }; isMalformed: opt.isMalformed || false isSpecial: isSpecial, body: body, value: value, commentType: commentType, type: Token.Comment, return { }); } commentType = str; } } } break; isSpecial = false; self.ignoringLinterErrors = false; case "end": break; isSpecial = false; self.ignoringLinterErrors = true; case "start": switch (options[1]) { case "ignore": switch (options[0]) { if (options.length === 2) { }); return v.replace(/^\s+/, "").replace(/\s+$/, ""); var options = body.split(":").map(function(v) { default: break; commentType = "globals"; case "global": break; commentType = "members"; case "member": switch (str) { } return; if (!isSpecial) { } } body = body.substr(str.length + strIndex); isSpecial = true; if (isAllWhitespace) { var isAllWhitespace = body.substr(0, strIndex).trim().length === 0; if (!isSpecial && strIndex >= 0 && body.charAt(strIndex + str.length) === " ") { var strIndex = body.indexOf(str); // multiple spaces or tabs // To handle rarer case when special word is separated from label by } body = body.substr(str.length + 1); label = label + " " + str; isSpecial = true; body.substr(1, str.length) === str) { if (!isSpecial && body.charAt(0) === " " && body.charAt(str.length + 1) === " " &&ad%}PA! zts>/ q h b a ,  g N E ? > v U J B A 2  | k I . #   g # FtU@? y* qZRQ*)~eSIH_MCB~}| } body = body.substr(str.length); label = label + str; isSpecial = true; if (body.charAt(str.length) === " " && body.substr(0, str.length) === str) { } return; if (label === "//" && str !== "jshint" && str !== "jshint.unstable") { // comments. This introduced many problems with legit comments. // Don't recognize any special comments other than jshint for single-line } return; if (isSpecial) { special.forEach(function(str) { } commentType = "falls through"; isSpecial = true; if (label === "/*" && reg.fallsThrough.test(body)) { body = body.replace(/\n/g, " "); } value += "*/"; if (opt.isMultiline) { opt = opt || {}; var commentType = "plain"; var value = label + body; var isSpecial = false; ]; "global", "exported" "jshint", "jshint.unstable", "jslint", "members", "member", "globals", var special = [ function commentToken(label, body, opt) { // comments. // has all the data JSHint needs to work with special // Create a comment token object and make sure it var self = this; var startChar = this.char; var startLine = this.line; var rest = this.input.substr(2); var ch2 = this.peek(1); var ch1 = this.peek(); scanComments: function(checks) { */ * /*jshint, /*jslint, /*globals and so on. * also recognizes JSHint- and JSLint-specific comments such as * In addition to normal JavaScript comments (// and /*) this method * * pointer. * span across multiple lines this method has to move the char * lines or return 'null' if its not possible. Since comments can * Extract a comment out of the next sequence of characters and/or /* }, return null; } }; value: ch1 type: Token.Punctuator, return { } }; value: ch1 + ch2 type: Token.Punctuator, return { if (ch2 === "=") { if ("<>=!+-*%&|^/".indexOf(ch1) >= 0) { // <= >= != += -= *= %= &= |= ^= /= } }; value: ch1 + ch2 type: Token.Punctuator, return { } }; value: ch1 + ch2 + ch3 type: Token.Punctuator, return { if (ch1 === "*" && ch3 === "=") { if (ch1 === ch2 && ("+-<>&|*".indexOf(ch1) >= 0)) { // 2-character punctuators: ++ -- << >> && || ** } }; value: ch1 + ch2 type: Token.Punctuator, return { if (ch1 === "=" && ch2 === ">") { // Fat arrow punctuator } }; value: ">>=" type: Token.Punctuator, return { if (ch1 === ">" && ch2 === ">" && ch3 === "=") { } }; value: "<<=" type: Token.Punctuator, return { if (ch1 === "<" && ch2 === "<" && ch3 === "=") { } }; value: ">>>" type: Token.Punctuator, return { if (ch1 === ">" && ch2 === ">" && ch3 === ">") { } }; value: "!==" type: Token.Punctuator, return { if (ch1 === "!" && ch2 === "=" && ch3 === "=") { } }; value: "===" type: Token.Punctuator, return { if (ch1 === "=" && ch2 === "=" && ch3 === "=") { // 3-character punctuators: === !== >>> <<= >>= } }; value: ">>>=" type: Token.Punctuator,adsnmNrSNMH   y c S ?     y t @ 0 %  I : 4  D o R @    Gj\0|bTF8*{mF7rR?65 rlkON6 return { if (ch1 === ">" && ch2 === ">" && ch3 === ">" && ch4 === "=") { // 4-character punctuator: >>>= ch4 = this.peek(3); ch3 = this.peek(2); ch2 = this.peek(1); // Peek more characters } return null; case "": // We're at the end of input }; value: ch1 type: Token.Punctuator, return { case "#": // A pound sign (for Node shebangs) }; value: ch1 type: Token.Punctuator, return { } this.popContext(); if (this.inContext(Context.Block)) { case "}": // A block/object closer }; value: ch1 type: Token.Punctuator, return { this.pushContext(Context.Block); case "{": // A block/object opener }; value: ch1 type: Token.Punctuator, return { case "?": case "~": case ":": case "]": case "[": case ",": case ";": case ")": case "(": /* falls through */ } }; value: "..." type: Token.Punctuator, return { if (this.peek(1) === "." && this.peek(2) === ".") { } return null; if ((/^[0-9]$/).test(this.peek(1))) { case ".": // Most common single-character punctuators switch (ch1) { var ch2, ch3, ch4; var ch1 = this.peek(); scanPunctuator: function() { */ * scanPunctuator function in the Esprima parser's source code. * This method's implementation was heavily influenced by the * * or return 'null' if its not possible. * Extract a punctuator out of the next sequence of characters /* }, }.bind(this)); } this.trigger(type, args); if (fn()) { checks.push(function() { triggerAsync: function(type, args, checks, fn) { */ * a false context. * by the parser. This avoids parser's peek() to give the lexer * stored callback. To be later called using the check() function * last parameter, and the trigger function is called in a * Postpone a token event. the checking condition is set as /* }, this.emitter.emit.apply(this.emitter, Array.prototype.slice.call(arguments)); trigger: function() { */ * listener. * Trigger a token event. All arguments will be passed to each /* }, }.bind(this)); this.emitter.on(name, listener); names.split(" ").forEach(function(name) { on: function(names, listener) { */ * }); * // ... * lex.on("Identifier Number", function(data) { * * one call: * Underscore.js i.e. you can subscribe to multiple events with * Subscribe to a token event. The API for this method is similar /* }, this.input = this.input.slice(i); this.char += i; i = i || 1; skip: function(i) { */ * Move the char pointer forward i times. /* }, return this.input.charAt(i || 0); peek: function(i) { */ * char pointer. * Return the next i character without actually moving the /* }, state.lines = this._lines; this._lines = val; setLines: function(val) { }, return this._lines; this._lines = state.lines; getLines: function() { }, return this.context.length > 0 && this.context[this.context.length - 1]; currentContext: function() { }, return this.context.pop(); popContext: function() { }, this.context.push({ type: ctxType }); pushContext: function(ctxType) {