| // jslint.js |
| // 2016-07-13 |
| // Copyright (c) 2015 Douglas Crockford (www.JSLint.com) |
| |
| // Permission is hereby granted, free of charge, to any person obtaining a copy |
| // of this software and associated documentation files (the "Software"), to deal |
| // in the Software without restriction, including without limitation the rights |
| // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell |
| // copies of the Software, and to permit persons to whom the Software is |
| // furnished to do so, subject to the following conditions: |
| |
| // The above copyright notice and this permission notice shall be included in |
| // all copies or substantial portions of the Software. |
| |
| // The Software shall be used for Good, not Evil. |
| |
| // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR |
| // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, |
| // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE |
| // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER |
| // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, |
| // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE |
| // SOFTWARE. |
| |
| // jslint(source, option_object, global_array) is a function that takes 3 |
| // arguments. The second two arguments are optional. |
| |
| // source A text to analyze, a string or an array of strings. |
| // option_object An object whose keys correspond to option names. |
| // global_array An array of strings containing global variables that |
| // the file is allowed readonly access. |
| |
| // jslint returns an object containing its results. The object contains a lot |
| // of valuable information. It can be used to generate reports. The object |
| // contains: |
| |
| // directives: an array of directive comment tokens. |
| // edition: the version of JSLint that did the analysis. |
| // functions: an array of objects that represent all of the functions |
| // declared in the file. |
| // global: an object representing the global object. Its .context property |
| // is an object containing a property for each global variable. |
| // id: "(JSLint)" |
| // imports: an array of strings representing each of the imports. |
| // json: true if the file is a JSON text. |
| // lines: an array of strings, the source. |
| // module: true if an import or export statement was used. |
| // ok: true if no warnings were generated. This is what you want. |
| // option: the option argument. |
| // property: a property object. |
| // stop: true if JSLint was unable to finish. You don't want this. |
| // tokens: an array of objects representing the tokens in the file. |
| // tree: the token objects arranged in a tree. |
| // warnings: an array of warning objects. A warning object can contain: |
| // name: "JSLintError" |
| // column: A column number in the file. |
| // line: A line number in the file. |
| // code: A warning code string. |
| // message: The warning message string. |
| // a: Exhibit A. |
| // b: Exhibit B. |
| // c: Exhibit C. |
| // d: Exhibit D. |
| |
| // jslint works in several phases. In any of these phases, errors might be |
| // found. Sometimes JSLint is able to recover from an error and continue |
| // parsing. In some cases, it cannot and will stop early. If that should happen, |
| // repair your code and try again. |
| |
| // Phases: |
| |
| // 1. If the source is a single string, split it into an array of strings. |
| // 2. Turn the source into an array of tokens. |
| // 3. Furcate the tokens into a parse tree. |
| // 4. Walk the tree, traversing all of the nodes of the tree. It is a |
| // recursive traversal. Each node may be processed on the way down |
| // (preaction) and on the way up (postaction). |
| // 5. Check the whitespace between the tokens. |
| |
| // jslint can also examine JSON text. It decides that a file is JSON text if |
| // the first token is "[" or "{". Processing of JSON text is much simpler than |
| // the processing of JavaScript programs. Only the first three phases are |
| // required. |
| |
| // WARNING: JSLint will hurt your feelings. |
| |
| /*property |
| a, and, arity, b, bad_assignment_a, bad_directive_a, bad_get, |
| bad_module_name_a, bad_option_a, bad_property_a, bad_set, bitwise, block, |
| body, browser, c, calls, catch, charAt, charCodeAt, closer, closure, code, |
| column, complex, concat, constant, context, couch, create, d, dead, devel, |
| directive, directives, disrupt, dot, duplicate_a, edition, ellipsis, else, |
| empty_block, es6, escape_mega, eval, every, expected_a, expected_a_at_b_c, |
| expected_a_b, expected_a_b_from_c_d, expected_a_before_b, |
| expected_digits_after_a, expected_four_digits, expected_identifier_a, |
| expected_line_break_a_b, expected_regexp_factor_a, expected_space_a_b, |
| expected_statements_a, expected_string_a, expected_type_string_a, |
| expression, extra, flag, for, forEach, free, from, fud, fudge, function, |
| function_in_loop, functions, g, global, i, id, identifier, import, imports, |
| inc, indexOf, infix_in, init, initial, isArray, isNaN, join, json, keys, |
| label, label_a, lbp, led, length, level, line, lines, live, loop, m, |
| margin, match, maxerr, maxlen, message, misplaced_a, misplaced_directive_a, |
| missing_browser, module, multivar, naked_block, name, names, |
| nested_comment, new, node, not_label_a, nr, nud, number_isNaN, ok, open, |
| option, out_of_scope_a, parameters, pop, property, push, qmark, quote, |
| redefinition_a_b, replace, reserved_a, role, search, signature, single, |
| slice, some, sort, split, statement, stop, strict, subscript_a, switch, |
| test, this, thru, toString, todo_comment, tokens, too_long, too_many, |
| too_many_digits, tree, type, u, unclosed_comment, unclosed_mega, |
| unclosed_string, undeclared_a, unexpected_a, unexpected_a_after_b, |
| unexpected_at_top_level_a, unexpected_char_a, unexpected_comment, |
| unexpected_directive_a, unexpected_expression_a, unexpected_label_a, |
| unexpected_parens, unexpected_space_a_b, unexpected_statement_a, |
| unexpected_trailing_space, unexpected_typeof_a, uninitialized_a, |
| unreachable_a, unregistered_property_a, unsafe, unused_a, use_spaces, |
| use_strict, used, value, var_loop, var_switch, variable, warning, warnings, |
| weird_condition_a, weird_expression_a, weird_loop, weird_relation_a, white, |
| wrap_assignment, wrap_condition, wrap_immediate, wrap_parameter, |
| wrap_regexp, wrap_unary, wrapped, writable, y |
| */ |
| |
| var jslint = (function JSLint() { |
| "use strict"; |
| |
| function empty() { |
| |
| // The empty function produces a new empty object that inherits nothing. This is |
| // much better than {} because confusions around accidental method names like |
| // "constructor" are completely avoided. |
| |
| return Object.create(null); |
| } |
| |
| function populate(object, array, value) { |
| |
| // Augment an object by taking property names from an array of strings. |
| |
| array.forEach(function (name) { |
| object[name] = value; |
| }); |
| } |
| |
| var allowed_option = { |
| |
| // These are the options that are recognized in the option object or that may |
| // appear in a /*jslint*/ directive. Most options will have a boolean value, |
| // usually true. Some options will also predefine some number of global |
| // variables. |
| |
| bitwise: true, |
| browser: [ |
| "Audio", "clearInterval", "clearTimeout", "document", "event", |
| "FormData", "history", "Image", "localStorage", "location", "name", |
| "navigator", "Option", "screen", "sessionStorage", "setInterval", |
| "setTimeout", "Storage", "XMLHttpRequest" |
| ], |
| couch: [ |
| "emit", "getRow", "isArray", "log", "provides", "registerType", |
| "require", "send", "start", "sum", "toJSON" |
| ], |
| devel: [ |
| "alert", "confirm", "console", "Debug", "opera", "prompt", "WSH" |
| ], |
| es6: [ |
| "ArrayBuffer", "DataView", "Float32Array", "Float64Array", |
| "Generator", "GeneratorFunction", "Int8Array", "Int16Array", |
| "Int32Array", "Intl", "Map", "Promise", "Proxy", "Reflect", |
| "Set", "Symbol", "System", "Uint8Array", "Uint8ClampedArray", |
| "Uint16Array", "Uint32Array", "WeakMap", "WeakSet" |
| ], |
| eval: true, |
| for: true, |
| fudge: true, |
| maxerr: 10000, |
| maxlen: 10000, |
| multivar: true, |
| node: [ |
| "Buffer", "clearImmediate", "clearInterval", "clearTimeout", |
| "console", "exports", "global", "module", "process", "querystring", |
| "require", "setImmediate", "setInterval", "setTimeout", |
| "__dirname", "__filename" |
| ], |
| single: true, |
| this: true, |
| white: true |
| }; |
| |
| var spaceop = { |
| |
| // This is the set of infix operators that require a space on each side. |
| |
| "!=": true, |
| "!==": true, |
| "%": true, |
| "%=": true, |
| "&": true, |
| "&=": true, |
| "&&": true, |
| "*": true, |
| "*=": true, |
| "+=": true, |
| "-=": true, |
| "/": true, |
| "/=": true, |
| "<": true, |
| "<=": true, |
| "<<": true, |
| "<<=": true, |
| "=": true, |
| "==": true, |
| "===": true, |
| "=>": true, |
| ">": true, |
| ">=": true, |
| ">>": true, |
| ">>=": true, |
| ">>>": true, |
| ">>>=": true, |
| "^": true, |
| "^=": true, |
| "|": true, |
| "|=": true, |
| "||": true |
| }; |
| |
| var bitwiseop = { |
| |
| // These are the bitwise operators. |
| |
| "~": true, |
| "^": true, |
| "^=": true, |
| "&": true, |
| "&=": true, |
| "|": true, |
| "|=": true, |
| "<<": true, |
| "<<=": true, |
| ">>": true, |
| ">>=": true, |
| ">>>": true, |
| ">>>=": true |
| }; |
| |
| var opener = { |
| |
| // The open and close pairs. |
| |
| "(": ")", // paren |
| "[": "]", // bracket |
| "{": "}", // brace |
| "${": "}" // mega |
| }; |
| |
| var relationop = { |
| |
| // The relational operators. |
| |
| "!=": true, |
| "!==": true, |
| "==": true, |
| "===": true, |
| "<": true, |
| "<=": true, |
| ">": true, |
| ">=": true |
| }; |
| |
| var standard = [ |
| |
| // These are the globals that are provided by the ES5 language standard. |
| |
| "Array", "Boolean", "Date", "decodeURI", "decodeURIComponent", |
| "encodeURI", "encodeURIComponent", "Error", "EvalError", "isFinite", |
| "JSON", "Math", "Number", "Object", "parseInt", "parseFloat", |
| "RangeError", "ReferenceError", "RegExp", "String", "SyntaxError", |
| "TypeError", "URIError" |
| ]; |
| |
| var bundle = { |
| |
| // The bundle contains the raw text messages that are generated by jslint. It |
| // seems that they are all error messages and warnings. There are no "Atta |
| // boy!" or "You are so awesome!" messages. There is no positive reinforcement |
| // or encouragement. This relentless negativity can undermine self-esteem and |
| // wound the inner child. But if you accept it as sound advice rather than as |
| // personal criticism, it can make your programs better. |
| |
| and: "The '&&' subexpression should be wrapped in parens.", |
| bad_assignment_a: "Bad assignment to '{a}'.", |
| bad_directive_a: "Bad directive '{a}'.", |
| bad_get: "A get function takes no parameters.", |
| bad_module_name_a: "Bad module name '{a}'.", |
| bad_option_a: "Bad option '{a}'.", |
| bad_property_a: "Bad property name '{a}'.", |
| bad_set: "A set function takes one parameter.", |
| duplicate_a: "Duplicate '{a}'.", |
| empty_block: "Empty block.", |
| es6: "Unexpected ES6 feature '{a}'.", |
| escape_mega: "Unexpected escapement in mega literal.", |
| expected_a: "Expected '{a}'.", |
| expected_a_at_b_c: "Expected '{a}' at column {b}, not column {c}.", |
| expected_a_b: "Expected '{a}' and instead saw '{b}'.", |
| expected_a_b_from_c_d: "Expected '{a}' to match '{b}' from line {c} and instead saw '{d}'.", |
| expected_a_before_b: "Expected '{a}' before '{b}'.", |
| expected_digits_after_a: "Expected digits after '{a}'.", |
| expected_four_digits: "Expected four digits after '\\u'.", |
| expected_identifier_a: "Expected an identifier and instead saw '{a}'.", |
| expected_line_break_a_b: "Expected a line break between '{a}' and '{b}'.", |
| expected_regexp_factor_a: "Expected a regexp factor and instead saw '{a}'.", |
| expected_space_a_b: "Expected one space between '{a}' and '{b}'.", |
| expected_statements_a: "Expected statements before '{a}'.", |
| expected_string_a: "Expected a string and instead saw '{a}'.", |
| expected_type_string_a: "Expected a type string and instead saw '{a}'.", |
| function_in_loop: "Don't make functions within a loop.", |
| infix_in: "Unexpected 'in'. Compare with undefined, or use the hasOwnProperty method instead.", |
| isNaN: "Use the isNaN function to compare with NaN.", |
| label_a: "'{a}' is a statement label.", |
| misplaced_a: "Place '{a}' at the outermost level.", |
| misplaced_directive_a: "Place the '/*{a}*/' directive before the first statement.", |
| missing_browser: "/*global*/ requires the Assume a browser option.", |
| naked_block: "Naked block.", |
| nested_comment: "Nested comment.", |
| not_label_a: "'{a}' is not a label.", |
| number_isNaN: "Use Number.isNaN function to compare with NaN.", |
| out_of_scope_a: "'{a}' is out of scope.", |
| redefinition_a_b: "Redefinition of '{a}' from line {b}.", |
| reserved_a: "Reserved name '{a}'.", |
| subscript_a: "['{a}'] is better written in dot notation.", |
| todo_comment: "Unexpected TODO comment.", |
| too_long: "Line too long.", |
| too_many: "Too many warnings.", |
| too_many_digits: "Too many digits.", |
| unclosed_comment: "Unclosed comment.", |
| unclosed_mega: "Unclosed mega literal.", |
| unclosed_string: "Unclosed string.", |
| undeclared_a: "Undeclared '{a}'.", |
| unexpected_a: "Unexpected '{a}'.", |
| unexpected_a_after_b: "Unexpected '{a}' after '{b}'.", |
| unexpected_at_top_level_a: "Expected '{a}' to be in a function.", |
| unexpected_char_a: "Unexpected character '{a}'.", |
| unexpected_comment: "Unexpected comment.", |
| unexpected_directive_a: "When using modules, don't use directive '/*{a}'.", |
| unexpected_expression_a: "Unexpected expression '{a}' in statement position.", |
| unexpected_label_a: "Unexpected label '{a}'.", |
| unexpected_parens: "Don't wrap function literals in parens.", |
| unexpected_space_a_b: "Unexpected space between '{a}' and '{b}'.", |
| unexpected_statement_a: "Unexpected statement '{a}' in expression position.", |
| unexpected_trailing_space: "Unexpected trailing space.", |
| unexpected_typeof_a: "Unexpected 'typeof'. Use '===' to compare directly with {a}.", |
| uninitialized_a: "Uninitialized '{a}'.", |
| unreachable_a: "Unreachable '{a}'.", |
| unregistered_property_a: "Unregistered property name '{a}'.", |
| unsafe: "Unsafe character '{a}'.", |
| unused_a: "Unused '{a}'.", |
| use_spaces: "Use spaces, not tabs.", |
| use_strict: "This function needs a 'use strict' pragma.", |
| var_loop: "Don't declare variables in a loop.", |
| var_switch: "Don't declare variables in a switch.", |
| weird_condition_a: "Weird condition '{a}'.", |
| weird_expression_a: "Weird expression '{a}'.", |
| weird_loop: "Weird loop.", |
| weird_relation_a: "Weird relation '{a}'.", |
| wrap_assignment: "Don't wrap assignment statements in parens.", |
| wrap_condition: "Wrap the condition in parens.", |
| wrap_immediate: "Wrap an immediate function invocation in " + |
| "parentheses to assist the reader in understanding that the " + |
| "expression is the result of a function, and not the " + |
| "function itself.", |
| wrap_parameter: "Wrap the parameter in parens.", |
| wrap_regexp: "Wrap this regexp in parens to avoid confusion.", |
| wrap_unary: "Wrap the unary expression in parens." |
| }; |
| |
| // Regular expression literals: |
| |
| // supplant {variables} |
| var rx_supplant = /\{([^{}]*)\}/g; |
| // carriage return, carriage return linefeed, or linefeed |
| var rx_crlf = /\n|\r\n?/; |
| // unsafe characters that are silently deleted by one or more browsers |
| var rx_unsafe = /[\u0000-\u001f\u007f-\u009f\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/; |
| // identifier |
| var rx_identifier = /^([a-zA-Z_$][a-zA-Z0-9_$]*)$/; |
| var rx_module = /^[a-zA-Z0-9_$:.@\-\/]+$/; |
| var rx_bad_property = /^_|\$|Sync$|_$/; |
| // star slash |
| var rx_star_slash = /\*\//; |
| // slash star |
| var rx_slash_star = /\/\*/; |
| // slash star or ending slash |
| var rx_slash_star_or_slash = /\/\*|\/$/; |
| // uncompleted work comment |
| var rx_todo = /\b(?:todo|TO\s?DO|HACK)\b/; |
| // tab |
| var rx_tab = /\t/g; |
| // directive |
| var rx_directive = /^(jslint|property|global)\s+(.*)$/; |
| var rx_directive_part = /^([a-zA-Z$_][a-zA-Z0-9$_]*)\s*(?::\s*(true|false|[0-9]+)\s*)?(?:,\s*)?(.*)$/; |
| // token (sorry it is so long) |
| var rx_token = /^((\s+)|([a-zA-Z_$][a-zA-Z0-9_$]*)|[(){}\[\]\?,:;'"~`]|=(?:==?|>)?|\.+|\/[=*\/]?|\*[\/=]?|\+(?:=|\++)?|-(?:=|-+)?|[\^%]=?|&[&=]?|\|[\|=]?|>{1,3}=?|<<?=?|!={0,2}|(0|[1-9][0-9]*))(.*)$/; |
| var rx_digits = /^([0-9]+)(.*)$/; |
| var rx_hexs = /^([0-9a-fA-F]+)(.*)$/; |
| var rx_octals = /^([0-7]+)(.*)$/; |
| var rx_bits = /^([01]+)(.*)$/; |
| // mega |
| var rx_mega = /[`\\]|\$\{/; |
| // indentation |
| var rx_colons = /^(.*)\?([:.]*)$/; |
| var rx_dot = /\.$/; |
| // JSON number |
| var rx_JSON_number = /^-?\d+(?:\.\d*)?(?:e[\-+]?\d+)?$/i; |
| // initial cap |
| var rx_cap = /^[A-Z]/; |
| |
| function is_letter(string) { |
| return (string >= "a" && string <= "z\uffff") || |
| (string >= "A" && string <= "Z\uffff"); |
| } |
| |
| function supplant(string, object) { |
| return string.replace(rx_supplant, function (found, filling) { |
| var replacement = object[filling]; |
| return (replacement !== undefined) |
| ? replacement |
| : found; |
| }); |
| } |
| |
| var anon = "anonymous"; // The guessed name for anonymous functions. |
| var blockage; // The current block. |
| var block_stack; // The stack of blocks. |
| var declared_globals; // The object containing the global declarations. |
| var directives; // The directive comments. |
| var directive_mode; // true if directives are still allowed. |
| var early_stop; // true if JSLint cannot finish. |
| var export_mode; // true if an export statement was seen. |
| var fudge; // true if the natural numbers start with 1. |
| var functionage; // The current function. |
| var functions; // The array containing all of the functions. |
| var global; // The global object; the outermost context. |
| var imports; // The array collecting all import-from strings. |
| var json_mode; // true if parsing JSON. |
| var lines; // The array containing source lines. |
| var module_mode; // true if import or export was used. |
| var next_token; // The next token to be examined in the parse. |
| var option; // The options parameter. |
| var property; // The object containing the tallied property names. |
| var mega_mode; // true if currently parsing a megastring literal. |
| var stack; // The stack of functions. |
| var syntax; // The object containing the parser. |
| var token; // The current token being examined in the parse. |
| var token_nr; // The number of the next token. |
| var tokens; // The array of tokens. |
| var tenure; // The predefined property registry. |
| var tree; // The abstract parse tree. |
| var var_mode; // true if using var; false if using let. |
| var warnings; // The array collecting all generated warnings. |
| |
| // Error reportage functions: |
| |
| function artifact(the_token) { |
| |
| // Return a string representing an artifact. |
| |
| if (the_token === undefined) { |
| the_token = next_token; |
| } |
| return (the_token.id === "(string)" || the_token.id === "(number)") |
| ? String(the_token.value) |
| : the_token.id; |
| } |
| |
| function artifact_line(the_token) { |
| |
| // Return the fudged line number of an artifact. |
| |
| if (the_token === undefined) { |
| the_token = next_token; |
| } |
| return the_token.line + fudge; |
| } |
| |
| function artifact_column(the_token) { |
| |
| // Return the fudged column number of an artifact. |
| |
| if (the_token === undefined) { |
| the_token = next_token; |
| } |
| return the_token.from + fudge; |
| } |
| |
| function warn_at(code, line, column, a, b, c, d) { |
| |
| // Report an error at some line and column of the program. The warning object |
| // resembles an exception. |
| |
| var warning = { // ~~ |
| name: "JSLintError", |
| column: column, |
| line: line, |
| code: code |
| }; |
| if (a !== undefined) { |
| warning.a = a; |
| } |
| if (b !== undefined) { |
| warning.b = b; |
| } |
| if (c !== undefined) { |
| warning.c = c; |
| } |
| if (d !== undefined) { |
| warning.d = d; |
| } |
| warning.message = supplant(bundle[code] || code, warning); |
| warnings.push(warning); |
| return ( |
| typeof option.maxerr === "number" && |
| warnings.length === option.maxerr |
| ) ? stop_at("too_many", line, column) |
| : warning; |
| } |
| |
| function stop_at(code, line, column, a, b, c, d) { |
| |
| // Same as warn_at, except that it stops the analysis. |
| |
| throw warn_at(code, line, column, a, b, c, d); |
| } |
| |
| function warn(code, the_token, a, b, c, d) { |
| |
| // Same as warn_at, except the warning will be associated with a specific token. |
| // If there is already a warning on this token, suppress the new one. It is |
| // likely that the first warning will be the most meaningful. |
| |
| if (the_token === undefined) { |
| the_token = next_token; |
| } |
| if (the_token.warning === undefined) { |
| the_token.warning = warn_at( |
| code, |
| the_token.line, |
| the_token.from, |
| a || artifact(the_token), |
| b, |
| c, |
| d |
| ); |
| return the_token.warning; |
| } |
| } |
| |
| function stop(code, the_token, a, b, c, d) { |
| |
| // Similar to warn and stop_at. If the token already had a warning, that |
| // warning will be replaced with this new one. It is likely that the stopping |
| // warning will be the more meaningful. |
| |
| if (the_token === undefined) { |
| the_token = next_token; |
| } |
| delete the_token.warning; |
| throw warn(code, the_token, a, b, c, d); |
| } |
| |
| // Tokenize: |
| |
| function tokenize(source) { |
| |
| // tokenize takes a source and produces from it an array of token objects. |
| // JavaScript is notoriously difficult to tokenize because of the horrible |
| // interactions between automatic semicolon insertion, regular expression |
| // literals, and now megastring literals. JSLint benefits from eliminating |
| // automatic semicolon insertion and nested megastring literals, which allows |
| // full tokenization to precede parsing. |
| |
| // If the source is not an array, then it is split into lines at the |
| // carriage return/linefeed. |
| |
| lines = (Array.isArray(source)) |
| ? source |
| : source.split(rx_crlf); |
| tokens = []; |
| |
| var char; // a popular character |
| var column = 0; // the column number of the next character |
| var from; // the starting column number of the token |
| var line = -1; // the line number of the next character |
| var nr = 0; // the next token number |
| var previous = global; // the previous token including comments |
| var prior = global; // the previous token excluding comments |
| var mega_from; // the starting column of megastring |
| var mega_line; // the starting line of megastring |
| var snippet; // a piece of string |
| var source_line; // the current line source string |
| |
| function next_line() { |
| |
| // Put the next line of source in source_line. If the line contains tabs, |
| // replace them with spaces and give a warning. Also warn if the line contains |
| // unsafe characters or is too damn long. |
| |
| var at; |
| column = 0; |
| line += 1; |
| source_line = lines[line]; |
| if (source_line !== undefined) { |
| at = source_line.search(rx_tab); |
| if (at >= 0) { |
| if (!option.white) { |
| warn_at("use_spaces", line, at + 1); |
| } |
| source_line = source_line.replace(rx_tab, " "); |
| } |
| at = source_line.search(rx_unsafe); |
| if (at >= 0) { |
| warn_at( |
| "unsafe", |
| line, |
| column + at, |
| "U+" + source_line.charCodeAt(at).toString(16) |
| ); |
| } |
| if (option.maxlen && option.maxlen < source_line.length) { |
| warn_at("too_long", line, source_line.length); |
| } else if (!option.white && source_line.slice(-1) === " ") { |
| warn_at( |
| "unexpected_trailing_space", |
| line, |
| source_line.length - 1 |
| ); |
| } |
| } |
| return source_line; |
| } |
| |
| // Most tokens, including the identifiers, operators, and punctuators, can be |
| // found with a regular expression. Regular expressions cannot correctly match |
| // regular expression literals, so we will match those the hard way. String |
| // literals and number literals can be matched by regular expressions, but they |
| // don't provide good warnings. The functions snip, next_char, prev_char, |
| // some_digits, and escape help in the parsing of literals. |
| |
| function snip() { |
| |
| // Remove the last character from snippet. |
| |
| snippet = snippet.slice(0, -1); |
| } |
| |
| function next_char(match) { |
| |
| // Get the next character from the source line. Remove it from the source_line, |
| // and append it to the snippet. Optionally check that the previous character |
| // matched an expected value. |
| |
| if (match !== undefined && char !== match) { |
| return stop_at( |
| (char === "") |
| ? "expected_a" |
| : "expected_a_b", |
| line, |
| column - 1, |
| match, |
| char |
| ); |
| } |
| if (source_line) { |
| char = source_line.charAt(0); |
| source_line = source_line.slice(1); |
| snippet += char; |
| } else { |
| char = ""; |
| snippet += " "; |
| } |
| column += 1; |
| return char; |
| } |
| |
| function back_char() { |
| |
| // Back up one character by moving a character from the end of the snippet to |
| // the front of the source_line. |
| |
| if (snippet) { |
| char = snippet.slice(-1); |
| source_line = char + source_line; |
| column -= 1; |
| snip(); |
| } else { |
| char = ""; |
| } |
| return char; |
| } |
| |
| function some_digits(rx, quiet) { |
| var result = source_line.match(rx); |
| if (result) { |
| char = result[1]; |
| column += char.length; |
| source_line = result[2]; |
| snippet += char; |
| } else { |
| char = ""; |
| if (!quiet) { |
| warn_at( |
| "expected_digits_after_a", |
| line, |
| column, |
| snippet |
| ); |
| } |
| } |
| return char.length; |
| } |
| |
| function escape(extra) { |
| switch (next_char("\\")) { |
| case "\\": |
| case "\"": |
| case "'": |
| case "/": |
| case ":": |
| case "=": |
| case "|": |
| case "b": |
| case "f": |
| case "n": |
| case "r": |
| case "t": |
| case " ": |
| break; |
| case "u": |
| if (next_char("u") === "{") { |
| if (some_digits(rx_hexs) > 5) { |
| warn_at("too_many_digits", line, column - 1); |
| } |
| if (!option.es6) { |
| warn_at("es6", line, column, "u{"); |
| } |
| if (next_char() !== "}") { |
| stop_at("expected_a_before_b", line, column, "}", char); |
| } |
| next_char(); |
| return; |
| } |
| back_char(); |
| if (some_digits(rx_hexs, true) < 4) { |
| warn_at("expected_four_digits", line, column - 1); |
| } |
| break; |
| case "": |
| return stop_at("unclosed_string", line, column); |
| default: |
| if (extra && extra.indexOf(char) < 0) { |
| warn_at("unexpected_a_after_b", line, column, char, "\\"); |
| } |
| } |
| next_char(); |
| } |
| |
| function make(id, value, identifier) { |
| |
| // Make the token object and append it to the tokens list. |
| |
| var the_token = { |
| from: from, |
| id: id, |
| identifier: !!identifier, |
| line: line, |
| nr: nr, |
| thru: column |
| }; |
| tokens[nr] = the_token; |
| nr += 1; |
| |
| // Directives must appear before the first statement. |
| |
| if (id !== "(comment)" && id !== ";") { |
| directive_mode = false; |
| } |
| |
| // If the token is to have a value, give it one. |
| |
| if (value !== undefined) { |
| the_token.value = value; |
| } |
| |
| // If this token is an identifier that touches a preceding number, or |
| // a "/", comment, or regular expression literal that touches a preceding |
| // comment or regular expression literal, then give a missing space warning. |
| // This warning is not suppressed by option.white. |
| |
| if ( |
| previous.line === line && |
| previous.thru === from && |
| ( |
| (id === "(comment)" || id === "(regexp)" || id === "/") && |
| ( |
| previous.id === "(comment)" || |
| previous.id === "(regexp)" |
| ) |
| ) |
| ) { |
| warn( |
| "expected_space_a_b", |
| the_token, |
| artifact(previous), |
| artifact(the_token) |
| ); |
| } |
| if (previous.id === "." && id === "(number)") { |
| warn("expected_a_before_b", previous, "0", "."); |
| } |
| if (prior.id === "." && the_token.identifier) { |
| the_token.dot = true; |
| } |
| |
| // The previous token is used to detect adjacency problems. |
| |
| previous = the_token; |
| |
| // The prior token is a previous token that was not a comment. The prior token |
| // is used to disambiguate "/", which can mean division or regular expression |
| // literal. |
| |
| if (previous.id !== "(comment)") { |
| prior = previous; |
| } |
| return the_token; |
| } |
| |
| function parse_directive(the_comment, body) { |
| |
| // JSLint recognizes three directives that can be encoded in comments. This |
| // function processes one item, and calls itself recursively to process the |
| // next one. |
| |
| var result = body.match(rx_directive_part); |
| if (result) { |
| var allowed; |
| var name = result[1]; |
| var value = result[2]; |
| switch (the_comment.directive) { |
| case "jslint": |
| allowed = allowed_option[name]; |
| switch (typeof allowed) { |
| case "boolean": |
| case "object": |
| switch (value) { |
| case "true": |
| case "": |
| case undefined: |
| option[name] = true; |
| if (Array.isArray(allowed)) { |
| populate(declared_globals, allowed, false); |
| } |
| break; |
| case "false": |
| option[name] = false; |
| break; |
| default: |
| warn( |
| "bad_option_a", |
| the_comment, |
| name + ":" + value |
| ); |
| } |
| break; |
| case "number": |
| if (isFinite(+value)) { |
| option[name] = +value; |
| } else { |
| warn( |
| "bad_option_a", |
| the_comment, |
| name + ":" + value |
| ); |
| } |
| break; |
| default: |
| warn("bad_option_a", the_comment, name); |
| } |
| break; |
| case "property": |
| if (tenure === undefined) { |
| tenure = empty(); |
| } |
| tenure[name] = true; |
| break; |
| case "global": |
| if (value) { |
| warn("bad_option_a", the_comment, name + ":" + value); |
| } |
| declared_globals[name] = false; |
| module_mode = the_comment; |
| break; |
| } |
| return parse_directive(the_comment, result[3]); |
| } |
| if (body) { |
| return stop("bad_directive_a", the_comment, body); |
| } |
| } |
| |
| function comment(snippet) { |
| |
| // Make a comment object. Comments are not allowed in JSON text. Comments can |
| // include directives and notices of incompletion. |
| |
| var the_comment = make("(comment)", snippet); |
| if (Array.isArray(snippet)) { |
| snippet = snippet.join(" "); |
| } |
| if (!option.devel && rx_todo.test(snippet)) { |
| warn("todo_comment", the_comment); |
| } |
| var result = snippet.match(rx_directive); |
| if (result) { |
| if (!directive_mode) { |
| warn_at("misplaced_directive_a", line, from, result[1]); |
| } else { |
| the_comment.directive = result[1]; |
| parse_directive(the_comment, result[2]); |
| } |
| directives.push(the_comment); |
| } |
| return the_comment; |
| } |
| |
| function regexp() { |
| |
| // Parse a regular expression literal. |
| |
| var result; |
| var value; |
| |
| function quantifier() { |
| |
| // Match an optional quantifier. |
| |
| switch (char) { |
| case "?": |
| case "*": |
| case "+": |
| next_char(); |
| break; |
| case "{": |
| if (some_digits(rx_digits, true) === 0) { |
| warn_at("expected_a", line, column, "0"); |
| } |
| if (next_char() === ",") { |
| some_digits(rx_digits, true); |
| next_char(); |
| } |
| next_char("}"); |
| break; |
| default: |
| return; |
| } |
| if (char === "?") { |
| next_char("?"); |
| } |
| } |
| |
| function subklass() { |
| |
| // Match a character in a character class. |
| |
| switch (char) { |
| case "\\": |
| escape(); |
| return true; |
| case "[": |
| case "]": |
| case "/": |
| case "^": |
| case "-": |
| case "|": |
| case "": |
| return false; |
| case "`": |
| if (mega_mode) { |
| warn_at("unexpected_a", line, column, "`"); |
| } |
| next_char(); |
| return true; |
| case " ": |
| warn_at("expected_a_before_b", line, column, "\\", " "); |
| next_char(); |
| return true; |
| default: |
| next_char(); |
| return true; |
| } |
| } |
| |
| function ranges() { |
| |
| // Match a range of subclasses. |
| |
| if (subklass()) { |
| if (char === "-") { |
| next_char("-"); |
| if (!subklass()) { |
| return stop_at( |
| "unexpected_a", |
| line, |
| column - 1, |
| "-" |
| ); |
| } |
| } |
| return ranges(); |
| } |
| } |
| |
| function klass() { |
| |
| // Match a class. |
| |
| next_char("["); |
| if (char === "^") { |
| next_char("^"); |
| } |
| (function classy() { |
| ranges(); |
| if (char !== "]" && char !== "") { |
| warn_at( |
| "expected_a_before_b", |
| line, |
| column, |
| "\\", |
| char |
| ); |
| next_char(); |
| return classy(); |
| } |
| }()); |
| next_char("]"); |
| } |
| |
| function choice() { |
| |
| function group() { |
| |
| // Match a group that starts with left paren. |
| |
| next_char("("); |
| if (char === "?") { |
| next_char("?"); |
| switch (char) { |
| case ":": |
| case "=": |
| case "!": |
| next_char(); |
| break; |
| default: |
| next_char(":"); |
| } |
| } else if (char === ":") { |
| warn_at("expected_a_before_b", line, column, "?", ":"); |
| } |
| choice(); |
| next_char(")"); |
| } |
| |
| function factor() { |
| switch (char) { |
| case "[": |
| klass(); |
| return true; |
| case "\\": |
| escape("BbDdSsWw^${}[]().|*+?"); |
| return true; |
| case "(": |
| group(); |
| return true; |
| case "/": |
| case "|": |
| case "]": |
| case ")": |
| case "}": |
| case "{": |
| case "?": |
| case "+": |
| case "*": |
| case "": |
| return false; |
| case "`": |
| if (mega_mode) { |
| warn_at("unexpected_a", line, column, "`"); |
| } |
| break; |
| case " ": |
| warn_at("expected_a_before_b", line, column, "\\", " "); |
| break; |
| } |
| next_char(); |
| return true; |
| } |
| |
| function sequence(follow) { |
| if (factor()) { |
| quantifier(); |
| return sequence(true); |
| } |
| if (!follow) { |
| warn_at("expected_regexp_factor_a", line, column, char); |
| } |
| } |
| |
| // Match a choice (a sequence that can be followed by | and another choice). |
| |
| sequence(); |
| if (char === "|") { |
| next_char("|"); |
| return choice(); |
| } |
| } |
| |
| // Scan the regexp literal. Give a warning if the first character is = because |
| // /= looks like a division assignment operator. |
| |
| snippet = ""; |
| next_char(); |
| if (char === "=") { |
| warn_at("expected_a_before_b", line, column, "\\", "="); |
| } |
| choice(); |
| |
| // Make sure there is a closing slash. |
| |
| snip(); |
| value = snippet; |
| next_char("/"); |
| |
| // Process dangling flag letters. |
| |
| var allowed = { |
| g: true, |
| i: true, |
| m: true, |
| u: 6, |
| y: 6 |
| }; |
| var flag = empty(); |
| (function make_flag() { |
| if (is_letter(char)) { |
| switch (allowed[char]) { |
| case true: |
| break; |
| case 6: |
| if (!option.es6) { |
| warn_at("es6", line, column, char); |
| } |
| break; |
| default: |
| warn_at("unexpected_a", line, column, char); |
| } |
| allowed[char] = false; |
| flag[char] = true; |
| next_char(); |
| return make_flag(); |
| } |
| }()); |
| back_char(); |
| if (char === "/" || char === "*") { |
| return stop_at("unexpected_a", line, from, char); |
| } |
| result = make("(regexp)", char); |
| result.flag = flag; |
| result.value = value; |
| return result; |
| } |
| |
| function string(quote) { |
| |
| // Make a string token. |
| |
| var the_token; |
| snippet = ""; |
| next_char(); |
| |
| return (function next() { |
| switch (char) { |
| case quote: |
| snip(); |
| the_token = make("(string)", snippet); |
| the_token.quote = quote; |
| return the_token; |
| case "\\": |
| escape(); |
| break; |
| case "": |
| return stop_at("unclosed_string", line, column); |
| case "`": |
| if (mega_mode) { |
| warn_at("unexpected_a", line, column, "`"); |
| } |
| next_char("`"); |
| break; |
| default: |
| next_char(); |
| } |
| return next(); |
| }()); |
| } |
| |
| function frack() { |
| if (char === ".") { |
| some_digits(rx_digits); |
| next_char(); |
| } |
| if (char === "E" || char === "e") { |
| next_char(); |
| if (char !== "+" && char !== "-") { |
| back_char(); |
| } |
| some_digits(rx_digits); |
| next_char(); |
| } |
| } |
| |
| function number() { |
| if (snippet === "0") { |
| switch (next_char()) { |
| case ".": |
| frack(); |
| break; |
| case "b": |
| some_digits(rx_bits); |
| next_char(); |
| break; |
| case "o": |
| some_digits(rx_octals); |
| next_char(); |
| break; |
| case "x": |
| some_digits(rx_hexs); |
| next_char(); |
| break; |
| } |
| } else { |
| next_char(); |
| frack(); |
| } |
| |
| // If the next character after a number is a digit or letter, then something |
| // unexpected is going on. |
| |
| if ( |
| (char >= "0" && char <= "9") || |
| (char >= "a" && char <= "z") || |
| (char >= "A" && char <= "Z") |
| ) { |
| return stop_at( |
| "unexpected_a_after_b", |
| line, |
| column - 1, |
| snippet.slice(-1), |
| snippet.slice(0, -1) |
| ); |
| } |
| back_char(); |
| return make("(number)", snippet); |
| } |
| |
| function lex() { |
| var array; |
| var i = 0; |
| var j = 0; |
| var last; |
| var result; |
| var the_token; |
| if (!source_line) { |
| source_line = next_line(); |
| from = 0; |
| return (source_line === undefined) |
| ? (mega_mode) |
| ? stop_at("unclosed_mega", mega_line, mega_from) |
| : make("(end)") |
| : lex(); |
| } |
| from = column; |
| result = source_line.match(rx_token); |
| |
| // result[1] token |
| // result[2] whitespace |
| // result[3] identifier |
| // result[4] number |
| // result[5] rest |
| |
| if (!result) { |
| return stop_at( |
| "unexpected_char_a", |
| line, |
| column, |
| source_line.charAt(0) |
| ); |
| } |
| |
| snippet = result[1]; |
| column += snippet.length; |
| source_line = result[5]; |
| |
| // Whitespace was matched. Call lex again to get more. |
| |
| if (result[2]) { |
| return lex(); |
| } |
| |
| // The token is an identifier. |
| |
| if (result[3]) { |
| return make(snippet, undefined, true); |
| } |
| |
| // The token is a number. |
| |
| if (result[4]) { |
| return number(snippet); |
| } |
| |
| // The token is something miscellaneous. |
| |
| switch (snippet) { |
| |
| // The token is a single or double quote string. |
| |
| case "\"": |
| return string(snippet); |
| |
| case "'": |
| if (!option.single) { |
| warn_at("expected_a_b", line, column, "\"", "'"); |
| } |
| return string(snippet); |
| |
| // The token is a megastring. We don't allow any kind of mega nesting. |
| |
| case "`": |
| if (mega_mode) { |
| return stop_at("expected_a_b", line, column, "}", "`"); |
| } |
| snippet = ""; |
| mega_from = from; |
| mega_line = line; |
| mega_mode = true; |
| |
| // Parsing a mega literal is tricky. First make a ` token. |
| |
| make("`"); |
| from += 1; |
| |
| // Then loop, building up a string, possibly from many lines, until seeing |
| // the end of file, a closing `, or a ${ indicting an expression within the |
| // string. |
| |
| (function part() { |
| var at = source_line.search(rx_mega); |
| |
| // If neither ` nor ${ is seen, then the whole line joins the snippet. |
| |
| if (at < 0) { |
| snippet += source_line + "\n"; |
| return (next_line() === undefined) |
| ? stop_at("unclosed_mega", mega_line, mega_from) |
| : part(); |
| } |
| |
| // if either ` or ${ was found, then the preceding joins the snippet to become |
| // a string token. |
| |
| snippet += source_line.slice(0, at); |
| column += at; |
| source_line = source_line.slice(at); |
| if (source_line.charAt(0) === "\\") { |
| stop_at("escape_mega", line, at); |
| } |
| make("(string)", snippet).quote = "`"; |
| snippet = ""; |
| |
| // If ${, then make tokens that will become part of an expression until |
| // a } token is made. |
| |
| if (source_line.charAt(0) === "$") { |
| column += 2; |
| make("${"); |
| source_line = source_line.slice(2); |
| (function expr() { |
| var id = lex().id; |
| if (id === "{") { |
| return stop_at( |
| "expected_a_b", |
| line, |
| column, |
| "}", |
| "{" |
| ); |
| } |
| if (id !== "}") { |
| return expr(); |
| } |
| }()); |
| return part(); |
| } |
| }()); |
| source_line = source_line.slice(1); |
| column += 1; |
| mega_mode = false; |
| return make("`"); |
| |
| // The token is a // comment. |
| |
| case "//": |
| snippet = source_line; |
| source_line = ""; |
| the_token = comment(snippet); |
| if (mega_mode) { |
| warn("unexpected_comment", the_token, "`"); |
| } |
| return the_token; |
| |
| // The token is a /* comment. |
| |
| case "/*": |
| array = []; |
| if (source_line.charAt(0) === "/") { |
| warn_at("unexpected_a", line, column + i, "/"); |
| } |
| (function next() { |
| if (source_line > "") { |
| i = source_line.search(rx_star_slash); |
| if (i >= 0) { |
| return; |
| } |
| j = source_line.search(rx_slash_star); |
| if (j >= 0) { |
| warn_at("nested_comment", line, column + j); |
| } |
| } |
| array.push(source_line); |
| source_line = next_line(); |
| if (source_line === undefined) { |
| return stop_at("unclosed_comment", line, column); |
| } |
| return next(); |
| }()); |
| snippet = source_line.slice(0, i); |
| j = snippet.search(rx_slash_star_or_slash); |
| if (j >= 0) { |
| warn_at("nested_comment", line, column + j); |
| } |
| array.push(snippet); |
| column += i + 2; |
| source_line = source_line.slice(i + 2); |
| return comment(array); |
| |
| // The token is a slash. |
| |
| case "/": |
| |
| // The / can be a division operator or the beginning of a regular expression |
| // literal. It is not possible to know which without doing a complete parse. |
| // We want to complete the tokenization before we begin to parse, so we will |
| // estimate. This estimator can fail in some cases. For example, it cannot |
| // know if "}" is ending a block or ending an object literal, so it can |
| // behave incorrectly in that case; it is not meaningful to divide an |
| // object, so it is likely that we can get away with it. We avoided the worst |
| // cases by eliminating automatic semicolon insertion. |
| |
| if (prior.identifier) { |
| if (!prior.dot) { |
| switch (prior.id) { |
| case "return": |
| return regexp(); |
| case "(begin)": |
| case "case": |
| case "delete": |
| case "in": |
| case "instanceof": |
| case "new": |
| case "typeof": |
| case "void": |
| case "yield": |
| the_token = regexp(); |
| return stop("unexpected_a", the_token); |
| } |
| } |
| } else { |
| last = prior.id.charAt(prior.id.length - 1); |
| if ("(,=:?[".indexOf(last) >= 0) { |
| return regexp(); |
| } |
| if ("!&|{};~+-*%/^<>".indexOf(last) >= 0) { |
| the_token = regexp(); |
| warn("wrap_regexp", the_token); |
| return the_token; |
| } |
| } |
| if (source_line.charAt(0) === "/") { |
| column += 1; |
| source_line = source_line.slice(1); |
| snippet = "/="; |
| warn_at("unexpected_a", line, column, "/="); |
| } |
| break; |
| } |
| return make(snippet); |
| } |
| |
| // This is the only loop in JSLint. It will turn into a recursive call to lex |
| // when ES6 has been finished and widely deployed and adopted. |
| |
| while (true) { |
| if (lex().id === "(end)") { |
| break; |
| } |
| } |
| } |
| |
| // Parsing: |
| |
| // Parsing weaves the tokens into an abstract syntax tree. During that process, |
| // a token may be given any of these properties: |
| |
| // arity string |
| // label identifier |
| // name identifier |
| // expression expressions |
| // block statements |
| // else statements (else, default, catch) |
| |
| // Specialized tokens may have additional properties. |
| |
| function survey(name) { |
| var id = name.id; |
| |
| // Tally the property name. If it is a string, only tally strings that conform |
| // to the identifier rules. |
| |
| if (id === "(string)") { |
| id = name.value; |
| if (!rx_identifier.test(id)) { |
| return id; |
| } |
| } else if (id === "`") { |
| if (name.value.length === 1) { |
| id = name.value[0].value; |
| if (!rx_identifier.test(id)) { |
| return id; |
| } |
| } |
| } else if (!name.identifier) { |
| return stop("expected_identifier_a", name); |
| } |
| |
| // If we have seen this name before, increment its count. |
| |
| if (typeof property[id] === "number") { |
| property[id] += 1; |
| |
| // If this is the first time seeing this property name, and if there is a |
| // tenure list, then it must be on the list. Otherwise, it must conform to |
| // the rules for good property names. |
| |
| } else { |
| if (tenure !== undefined) { |
| if (tenure[id] !== true) { |
| warn("unregistered_property_a", name); |
| } |
| } else { |
| if (name.identifier && rx_bad_property.test(id)) { |
| warn("bad_property_a", name); |
| } |
| } |
| property[id] = 1; |
| } |
| return id; |
| } |
| |
| function dispense() { |
| |
| // Deliver the next token, skipping the comments. |
| |
| var cadet = tokens[token_nr]; |
| token_nr += 1; |
| if (cadet.id === "(comment)") { |
| if (json_mode) { |
| warn("unexpected_a", cadet); |
| } |
| return dispense(); |
| } else { |
| return cadet; |
| } |
| } |
| |
| function lookahead() { |
| |
| // Look ahead one token without advancing. |
| |
| var old_token_nr = token_nr; |
| var cadet = dispense(true); |
| token_nr = old_token_nr; |
| return cadet; |
| } |
| |
| function advance(id, match) { |
| |
| // Produce the next token. |
| |
| // Attempt to give helpful names to anonymous functions. |
| |
| if (token.identifier && token.id !== "function") { |
| anon = token.id; |
| } else if (token.id === "(string)" && rx_identifier.test(token.value)) { |
| anon = token.value; |
| } |
| |
| // Attempt to match next_token with an expected id. |
| |
| if (id !== undefined && next_token.id !== id) { |
| return (match === undefined) |
| ? stop("expected_a_b", next_token, id, artifact()) |
| : stop( |
| "expected_a_b_from_c_d", |
| next_token, |
| id, |
| artifact(match), |
| artifact_line(match), |
| artifact(next_token) |
| ); |
| } |
| |
| // Promote the tokens, skipping comments. |
| |
| token = next_token; |
| next_token = dispense(); |
| if (next_token.id === "(end)") { |
| token_nr -= 1; |
| } |
| } |
| |
| // Parsing of JSON is simple: |
| |
| function json_value() { |
| |
| function json_object() { |
| var brace = next_token; |
| var object = empty(); |
| advance("{"); |
| if (next_token.id !== "}") { |
| (function next() { |
| if (next_token.quote !== "\"") { |
| warn("unexpected_a", next_token, next_token.quote); |
| } |
| advance("(string)"); |
| if (object[token.value] !== undefined) { |
| warn("duplicate_a", token); |
| } else if (token.value === "__proto__") { |
| warn("bad_property_a", token); |
| } else { |
| object[token.value] = token; |
| } |
| advance(":"); |
| json_value(); |
| if (next_token.id === ",") { |
| advance(","); |
| return next(); |
| } |
| }()); |
| } |
| advance("}", brace); |
| } |
| |
| function json_array() { |
| var bracket = next_token; |
| advance("["); |
| if (next_token.id !== "]") { |
| (function next() { |
| json_value(); |
| if (next_token.id === ",") { |
| advance(","); |
| return next(); |
| } |
| }()); |
| } |
| advance("]", bracket); |
| } |
| |
| switch (next_token.id) { |
| case "{": |
| json_object(); |
| break; |
| case "[": |
| json_array(); |
| break; |
| case "true": |
| case "false": |
| case "null": |
| advance(); |
| break; |
| case "(number)": |
| if (!rx_JSON_number.test(next_token.value)) { |
| warn("unexpected_a"); |
| } |
| advance(); |
| break; |
| case "(string)": |
| if (next_token.quote !== "\"") { |
| warn("unexpected_a", next_token, next_token.quote); |
| } |
| advance(); |
| break; |
| case "-": |
| advance("-"); |
| advance("(number)"); |
| break; |
| default: |
| stop("unexpected_a"); |
| } |
| } |
| |
| // Now we parse JavaScript. |
| |
| function enroll(name, role, readonly) { |
| |
| // Enroll a name into the current function context. The role can be exception, |
| // function, label, parameter, or variable. We look for variable redefinition |
| // because it causes confusion. |
| |
| var id = name.id; |
| |
| // Reserved words may not be enrolled. |
| |
| if (syntax[id] !== undefined && id !== "ignore") { |
| warn("reserved_a", name); |
| } else { |
| |
| // Has the name been enrolled in this context? |
| |
| var earlier = functionage.context[id]; |
| if (earlier) { |
| warn( |
| "redefinition_a_b", |
| name, |
| name.id, |
| earlier.line + fudge |
| ); |
| |
| // Has the name been enrolled in an outer context? |
| |
| } else { |
| stack.forEach(function (value) { |
| var item = value.context[id]; |
| if (item !== undefined) { |
| earlier = item; |
| } |
| }); |
| if (earlier) { |
| if (id === "ignore") { |
| if (earlier.role === "variable") { |
| warn("unexpected_a", name); |
| } |
| } else { |
| if (( |
| role !== "exception" || |
| earlier.role !== "exception" |
| ) && role !== "parameter") { |
| warn( |
| "redefinition_a_b", |
| name, |
| name.id, |
| earlier.line + fudge |
| ); |
| } |
| } |
| } |
| |
| // Enroll it. |
| |
| functionage.context[id] = name; |
| name.dead = true; |
| name.function = functionage; |
| name.init = false; |
| name.role = role; |
| name.used = 0; |
| name.writable = !readonly; |
| } |
| } |
| } |
| |
| function expression(rbp, initial) { |
| |
| // This is the heart of the Pratt parser. I retained Pratt's nomenclature. |
| // They are elements of the parsing method called Top Down Operator Precedence. |
| |
| // nud Null denotation |
| // led Left denotation |
| // lbp Left binding power |
| // rbp Right binding power |
| |
| // It processes a nud (variable, constant, prefix operator). It will then |
| // process leds (infix operators) until the bind powers cause it to stop. It |
| // returns the expression's parse tree. |
| |
| var left; |
| var the_symbol; |
| |
| // Statements will have already advanced, so advance now only if the token is |
| // not the first of a statement, |
| |
| if (!initial) { |
| advance(); |
| } |
| the_symbol = syntax[token.id]; |
| if (the_symbol !== undefined && the_symbol.nud !== undefined) { |
| left = the_symbol.nud(); |
| } else if (token.identifier) { |
| left = token; |
| left.arity = "variable"; |
| } else { |
| return stop("unexpected_a", token); |
| } |
| (function right() { |
| the_symbol = syntax[next_token.id]; |
| if ( |
| the_symbol !== undefined && |
| the_symbol.led !== undefined && |
| rbp < the_symbol.lbp |
| ) { |
| advance(); |
| left = the_symbol.led(left); |
| return right(); |
| } |
| }()); |
| return left; |
| } |
| |
| function condition() { |
| |
| // Parse the condition part of a do, if, while. |
| |
| var the_paren = next_token; |
| var the_value; |
| the_paren.free = true; |
| advance("("); |
| the_value = expression(0); |
| advance(")"); |
| if (the_value.wrapped === true) { |
| warn("unexpected_a", the_paren); |
| } |
| switch (the_value.id) { |
| case "?": |
| case "~": |
| case "&": |
| case "|": |
| case "^": |
| case "<<": |
| case ">>": |
| case ">>>": |
| case "+": |
| case "-": |
| case "*": |
| case "/": |
| case "%": |
| case "typeof": |
| case "(number)": |
| case "(string)": |
| warn("unexpected_a", the_value); |
| break; |
| } |
| return the_value; |
| } |
| |
| function is_weird(thing) { |
| return ( |
| thing.id === "(regexp)" || |
| thing.id === "{" || |
| thing.id === "=>" || |
| thing.id === "function" || |
| (thing.id === "[" && thing.arity === "unary") |
| ); |
| } |
| |
| function are_similar(a, b) { |
| if (a === b) { |
| return true; |
| } |
| if (Array.isArray(a)) { |
| return ( |
| Array.isArray(b) && |
| a.length === b.length && |
| a.every(function (value, index) { |
| return are_similar(value, b[index]); |
| }) |
| ); |
| } |
| if (Array.isArray(b)) { |
| return false; |
| } |
| if (a.id === "(number)" && b.id === "(number)") { |
| return a.value === b.value; |
| } |
| var a_string; |
| var b_string; |
| if (a.id === "(string)") { |
| a_string = a.value; |
| } else if (a.id === "`" && a.constant) { |
| a_string = a.value[0]; |
| } |
| if (b.id === "(string)") { |
| b_string = b.value; |
| } else if (b.id === "`" && b.constant) { |
| b_string = b.value[0]; |
| } |
| if (typeof a_string === "string") { |
| return a_string === b_string; |
| } |
| if (is_weird(a) || is_weird(b)) { |
| return false; |
| } |
| if (a.arity === b.arity && a.id === b.id) { |
| if (a.id === ".") { |
| return are_similar(a.expression, b.expression) && |
| are_similar(a.name, b.name); |
| } |
| switch (a.arity) { |
| case "unary": |
| return are_similar(a.expression, b.expression); |
| case "binary": |
| return ( |
| a.id !== "(" && |
| are_similar(a.expression[0], b.expression[0]) && |
| are_similar(a.expression[1], b.expression[1]) |
| ); |
| case "ternary": |
| return ( |
| are_similar(a.expression[0], b.expression[0]) && |
| are_similar(a.expression[1], b.expression[1]) && |
| are_similar(a.expression[2], b.expression[2]) |
| ); |
| case "function": |
| case "regexp": |
| return false; |
| default: |
| return true; |
| } |
| } |
| return false; |
| } |
| |
| function semicolon() { |
| |
| // Try to match a semicolon. |
| |
| if (next_token.id === ";") { |
| advance(";"); |
| } else { |
| warn_at( |
| "expected_a_b", |
| token.line, |
| token.thru, |
| ";", |
| artifact(next_token) |
| ); |
| } |
| anon = "anonymous"; |
| } |
| |
| function statement() { |
| |
| // Parse a statement. Any statement may have a label, but only four statements |
| // have use for one. A statement can be one of the standard statements, or |
| // an assignment expression, or an invocation expression. |
| |
| var first; |
| var the_label; |
| var the_statement; |
| var the_symbol; |
| advance(); |
| if (token.identifier && next_token.id === ":") { |
| the_label = token; |
| if (the_label.id === "ignore") { |
| warn("unexpected_a", the_label); |
| } |
| advance(":"); |
| switch (next_token.id) { |
| case "do": |
| case "for": |
| case "switch": |
| case "while": |
| enroll(the_label, "label", true); |
| the_label.init = true; |
| the_label.dead = false; |
| the_statement = statement(); |
| the_statement.label = the_label; |
| the_statement.statement = true; |
| return the_statement; |
| default: |
| advance(); |
| warn("unexpected_label_a", the_label); |
| } |
| } |
| |
| // Parse the statement. |
| |
| first = token; |
| first.statement = true; |
| the_symbol = syntax[first.id]; |
| if (the_symbol !== undefined && the_symbol.fud !== undefined) { |
| the_symbol.disrupt = false; |
| the_symbol.statement = true; |
| the_statement = the_symbol.fud(); |
| } else { |
| |
| // It is an expression statement. |
| |
| the_statement = expression(0, true); |
| if (the_statement.wrapped && the_statement.id !== "(") { |
| warn("unexpected_a", first); |
| } |
| semicolon(); |
| } |
| if (the_label !== undefined) { |
| the_label.dead = true; |
| } |
| return the_statement; |
| } |
| |
| function statements() { |
| |
| // Parse a list of statements. Give a warning if an unreachable statement |
| // follows a disruptive statement. |
| |
| var array = []; |
| (function next(disrupt) { |
| var a_statement; |
| switch (next_token.id) { |
| case "}": |
| case "case": |
| case "default": |
| case "else": |
| case "(end)": |
| break; |
| default: |
| a_statement = statement(); |
| array.push(a_statement); |
| if (disrupt) { |
| warn("unreachable_a", a_statement); |
| } |
| return next(a_statement.disrupt); |
| } |
| }(false)); |
| return array; |
| } |
| |
| function not_top_level(thing) { |
| |
| // Some features should not be at the outermost level. |
| |
| if (functionage === global) { |
| warn("unexpected_at_top_level_a", thing); |
| } |
| } |
| |
| function top_level_only(the_thing) { |
| |
| // Some features must be at the most outermost level. |
| |
| if (blockage !== global) { |
| warn("misplaced_a", the_thing); |
| } |
| } |
| |
| function block(special) { |
| |
| // Parse a block, a sequence of statements wrapped in braces. |
| // special "body" The block is a function body. |
| // "ignore" No warning on an empty block. |
| // "naked" No advance. |
| // undefined An ordinary block. |
| |
| var stmts; |
| var the_block; |
| if (special !== "naked") { |
| advance("{"); |
| } |
| the_block = token; |
| the_block.arity = "statement"; |
| the_block.body = special === "body"; |
| |
| // All top level function bodies should include the "use strict" pragma unless |
| // the whole file is strict or the file is a module or the function parameters |
| // use es6 syntax. |
| |
| if ( |
| special === "body" && |
| stack.length === 1 && |
| next_token.value === "use strict" |
| ) { |
| the_block.strict = next_token; |
| next_token.statement = true; |
| advance("(string)"); |
| advance(";"); |
| } |
| stmts = statements(); |
| the_block.block = stmts; |
| if (stmts.length === 0) { |
| if (!option.devel && special !== "ignore") { |
| warn("empty_block", the_block); |
| } |
| the_block.disrupt = false; |
| } else { |
| the_block.disrupt = stmts[stmts.length - 1].disrupt; |
| } |
| advance("}"); |
| return the_block; |
| } |
| |
| function mutation_check(the_thing) { |
| |
| // The only expressions that may be assigned to are |
| // e.b |
| // e[b] |
| // v |
| |
| if ( |
| the_thing.id !== "." && |
| the_thing.arity !== "variable" && |
| (the_thing.id !== "[" || the_thing.arity !== "binary") |
| ) { |
| warn("bad_assignment_a", the_thing); |
| return false; |
| } |
| return true; |
| } |
| |
| function left_check(left, right) { |
| |
| // Warn if the left is not one of these: |
| // e.b |
| // e[b] |
| // e() |
| // identifier |
| |
| var id = left.id; |
| if ( |
| !left.identifier && |
| ( |
| left.arity !== "binary" || |
| (id !== "." && id !== "(" && id !== "[") |
| ) |
| ) { |
| warn("unexpected_a", right); |
| return false; |
| } |
| return true; |
| } |
| |
| // These functions are used to specify the grammar of our language: |
| |
| function symbol(id, bp) { |
| |
| // Make a symbol if it does not already exist in the language's syntax. |
| |
| var the_symbol = syntax[id]; |
| if (the_symbol === undefined) { |
| the_symbol = empty(); |
| the_symbol.id = id; |
| the_symbol.lbp = bp || 0; |
| syntax[id] = the_symbol; |
| } |
| return the_symbol; |
| } |
| |
| function assignment(id) { |
| |
| // Make an assignment operator. The one true assignment is different because |
| // its left side, when it is a variable, is not treated as an expression. |
| // That case is special because that is when a variable gets initialized. The |
| // other assignment operators can modify, but they cannot initialize. |
| |
| var the_symbol = symbol(id, 20); |
| the_symbol.led = function (left) { |
| var the_token = token; |
| var right; |
| the_token.arity = "assignment"; |
| right = expression(20 - 1); |
| if (id === "=" && left.arity === "variable") { |
| the_token.names = left; |
| the_token.expression = right; |
| } else { |
| the_token.expression = [left, right]; |
| } |
| switch (right.arity) { |
| case "assignment": |
| case "pre": |
| case "post": |
| warn("unexpected_a", right); |
| break; |
| } |
| if ( |
| option.es6 && |
| left.arity === "unary" && |
| (left.id === "[" || left.id === "{") |
| ) { |
| warn("expected_a_before_b", left, "const", left.id); |
| } else { |
| mutation_check(left); |
| } |
| return the_token; |
| }; |
| return the_symbol; |
| } |
| |
| function constant(id, type, value) { |
| |
| // Make a constant symbol. |
| |
| var the_symbol = symbol(id); |
| the_symbol.constant = true; |
| the_symbol.nud = (typeof value === "function") |
| ? value |
| : function () { |
| token.constant = true; |
| if (value !== undefined) { |
| token.value = value; |
| } |
| return token; |
| }; |
| the_symbol.type = type; |
| the_symbol.value = value; |
| return the_symbol; |
| } |
| |
| function infix(id, bp, f) { |
| |
| // Make an infix operator. |
| |
| var the_symbol = symbol(id, bp); |
| the_symbol.led = function (left) { |
| var the_token = token; |
| the_token.arity = "binary"; |
| if (f !== undefined) { |
| return f(left); |
| } |
| the_token.expression = [left, expression(bp)]; |
| return the_token; |
| }; |
| return the_symbol; |
| } |
| |
| function post(id) { |
| |
| // Make one of the post operators. |
| |
| var the_symbol = symbol(id, 150); |
| the_symbol.led = function (left) { |
| token.expression = left; |
| token.arity = "post"; |
| mutation_check(token.expression); |
| return token; |
| }; |
| return the_symbol; |
| } |
| |
| function pre(id) { |
| |
| // Make one of the pre operators. |
| |
| var the_symbol = symbol(id); |
| the_symbol.nud = function () { |
| var the_token = token; |
| the_token.arity = "pre"; |
| the_token.expression = expression(150); |
| mutation_check(the_token.expression); |
| return the_token; |
| }; |
| return the_symbol; |
| } |
| |
| function prefix(id, f) { |
| |
| // Make a prefix operator. |
| |
| var the_symbol = symbol(id); |
| the_symbol.nud = function () { |
| var the_token = token; |
| the_token.arity = "unary"; |
| if (typeof f === "function") { |
| return f(); |
| } |
| the_token.expression = expression(150); |
| return the_token; |
| }; |
| return the_symbol; |
| } |
| |
| function stmt(id, f) { |
| |
| // Make a statement. |
| |
| var the_symbol = symbol(id); |
| the_symbol.fud = function () { |
| token.arity = "statement"; |
| return f(); |
| }; |
| return the_symbol; |
| } |
| |
| function ternary(id1, id2) { |
| |
| // Make a ternary operator. |
| |
| var the_symbol = symbol(id1, 30); |
| the_symbol.led = function (left) { |
| var the_token = token; |
| var second = expression(20); |
| advance(id2); |
| token.arity = "ternary"; |
| the_token.arity = "ternary"; |
| the_token.expression = [left, second, expression(10)]; |
| return the_token; |
| }; |
| return the_symbol; |
| } |
| |
| // Begin defining the language. |
| |
| syntax = empty(); |
| |
| symbol("}"); |
| symbol(")"); |
| symbol("]"); |
| symbol(","); |
| symbol(";"); |
| symbol(":"); |
| symbol("*/"); |
| symbol("await"); |
| symbol("case"); |
| symbol("catch"); |
| symbol("class"); |
| symbol("default"); |
| symbol("else"); |
| symbol("enum"); |
| symbol("finally"); |
| symbol("implements"); |
| symbol("interface"); |
| symbol("package"); |
| symbol("private"); |
| symbol("protected"); |
| symbol("public"); |
| symbol("static"); |
| symbol("super"); |
| symbol("void"); |
| symbol("yield"); |
| |
| constant("(number)", "number"); |
| constant("(regexp)", "regexp"); |
| constant("(string)", "string"); |
| constant("arguments", "object", function () { |
| if (option.es6) { |
| warn("unexpected_a", token); |
| } |
| return token; |
| }); |
| constant("eval", "function", function () { |
| if (!option.eval) { |
| warn("unexpected_a", token); |
| } else if (next_token.id !== "(") { |
| warn("expected_a_before_b", next_token, "(", artifact()); |
| } |
| return token; |
| }); |
| constant("false", "boolean", false); |
| constant("Function", "function", function () { |
| if (!option.eval) { |
| warn("unexpected_a", token); |
| } else if (next_token.id !== "(") { |
| warn("expected_a_before_b", next_token, "(", artifact()); |
| } |
| return token; |
| }); |
| constant("ignore", "undefined", function () { |
| warn("unexpected_a", token); |
| return token; |
| }); |
| constant("Infinity", "number", Infinity); |
| constant("isNaN", "function", function () { |
| if (option.es6) { |
| warn("expected_a_b", token, "Number.isNaN", "isNaN"); |
| } |
| return token; |
| }); |
| constant("NaN", "number", NaN); |
| constant("null", "null", null); |
| constant("this", "object", function () { |
| if (!option.this) { |
| warn("unexpected_a", token); |
| } |
| return token; |
| }); |
| constant("true", "boolean", true); |
| constant("undefined", "undefined"); |
| |
| assignment("="); |
| assignment("+="); |
| assignment("-="); |
| assignment("*="); |
| assignment("/="); |
| assignment("%="); |
| assignment("&="); |
| assignment("|="); |
| assignment("^="); |
| assignment("<<="); |
| assignment(">>="); |
| assignment(">>>="); |
| |
| infix("||", 40); |
| infix("&&", 50); |
| infix("|", 70); |
| infix("^", 80); |
| infix("&", 90); |
| infix("==", 100); |
| infix("===", 100); |
| infix("!=", 100); |
| infix("!==", 100); |
| infix("<", 110); |
| infix(">", 110); |
| infix("<=", 110); |
| infix(">=", 110); |
| infix("in", 110); |
| infix("instanceof", 110); |
| infix("<<", 120); |
| infix(">>", 120); |
| infix(">>>", 120); |
| infix("+", 130); |
| infix("-", 130); |
| infix("*", 140); |
| infix("/", 140); |
| infix("%", 140); |
| infix("(", 160, function (left) { |
| var the_paren = token; |
| var the_argument; |
| if (left.id !== "function") { |
| left_check(left, the_paren); |
| } |
| if (functionage.arity === "statement" && left.identifier) { |
| functionage.name.calls[left.id] = left; |
| } |
| the_paren.expression = [left]; |
| if (next_token.id !== ")") { |
| (function next() { |
| var ellipsis; |
| if (next_token.id === "...") { |
| if (!option.es6) { |
| warn("es6"); |
| } |
| ellipsis = true; |
| advance("..."); |
| } |
| the_argument = expression(10); |
| if (ellipsis) { |
| the_argument.ellipsis = true; |
| } |
| the_paren.expression.push(the_argument); |
| if (next_token.id === ",") { |
| advance(","); |
| return next(); |
| } |
| }()); |
| } |
| advance(")", the_paren); |
| if (the_paren.expression.length === 2) { |
| the_paren.free = true; |
| if (the_argument.wrapped === true) { |
| warn("unexpected_a", the_paren); |
| } |
| if (the_argument.id === "(") { |
| the_argument.wrapped = true; |
| } |
| } else { |
| the_paren.free = false; |
| } |
| return the_paren; |
| }); |
| infix(".", 170, function (left) { |
| var the_token = token; |
| var name = next_token; |
| if ( |
| (left.id !== "(string)" || name.id !== "indexOf") && |
| (left.id !== "[" || ( |
| name.id !== "concat" && name.id !== "forEach" |
| )) && |
| (left.id !== "+" || name.id !== "slice") && |
| (left.id !== "(regexp)" || ( |
| name.id !== "exec" && name.id !== "test" |
| )) |
| ) { |
| left_check(left, the_token); |
| } |
| if (!name.identifier) { |
| stop("expected_identifier_a"); |
| } |
| advance(); |
| survey(name); |
| |
| // The property name is not an expression. |
| |
| the_token.name = name; |
| the_token.expression = left; |
| return the_token; |
| }); |
| infix("[", 170, function (left) { |
| var the_token = token; |
| var the_subscript = expression(0); |
| if (the_subscript.id === "(string)" || the_subscript.id === "`") { |
| var name = survey(the_subscript); |
| if (rx_identifier.test(name)) { |
| warn("subscript_a", the_subscript, name); |
| } |
| } |
| left_check(left, the_token); |
| the_token.expression = [left, the_subscript]; |
| advance("]"); |
| return the_token; |
| }); |
| infix("=>", 170, function (left) { |
| return stop("wrap_parameter", left); |
| }); |
| |
| function do_tick() { |
| var the_tick = token; |
| if (!option.es6) { |
| warn("es6", the_tick); |
| } |
| the_tick.value = []; |
| the_tick.expression = []; |
| if (next_token.id !== "`") { |
| (function part() { |
| advance("(string)"); |
| the_tick.value.push(token); |
| if (next_token.id === "${") { |
| advance("${"); |
| the_tick.expression.push(expression(0)); |
| advance("}"); |
| return part(); |
| } |
| }()); |
| } |
| advance("`"); |
| return the_tick; |
| } |
| |
| infix("`", 160, function (left) { |
| var the_tick = do_tick(); |
| left_check(left, the_tick); |
| the_tick.expression = [left].concat(the_tick.expression); |
| return the_tick; |
| }); |
| |
| post("++"); |
| post("--"); |
| pre("++"); |
| pre("--"); |
| |
| prefix("+"); |
| prefix("-"); |
| prefix("~"); |
| prefix("!"); |
| prefix("!!"); |
| prefix("[", function () { |
| var the_token = token; |
| the_token.expression = []; |
| if (next_token.id !== "]") { |
| (function next() { |
| var element; |
| var ellipsis = false; |
| if (next_token.id === "...") { |
| ellipsis = true; |
| if (!option.es6) { |
| warn("es6"); |
| } |
| advance("..."); |
| } |
| element = expression(10); |
| if (ellipsis) { |
| element.ellipsis = true; |
| } |
| the_token.expression.push(element); |
| if (next_token.id === ",") { |
| advance(","); |
| return next(); |
| } |
| }()); |
| } |
| advance("]"); |
| return the_token; |
| }); |
| prefix("/=", function () { |
| stop("expected_a_b", token, "/\\=", "/="); |
| }); |
| prefix("=>", function () { |
| return stop("expected_a_before_b", token, "()", "=>"); |
| }); |
| prefix("new", function () { |
| var the_new = token; |
| var right = expression(160); |
| if (next_token.id !== "(") { |
| warn("expected_a_before_b", next_token, "()", artifact(next_token)); |
| } |
| the_new.expression = right; |
| return the_new; |
| }); |
| prefix("typeof"); |
| prefix("void", function () { |
| var the_void = token; |
| warn("unexpected_a", the_void); |
| the_void.expression = expression(0); |
| return the_void; |
| }); |
| |
| function parameter_list() { |
| var complex = false; |
| var list = []; |
| var signature = ["("]; |
| if (next_token.id !== ")" && next_token.id !== "(end)") { |
| (function parameter() { |
| var ellipsis = false; |
| var param; |
| if (next_token.id === "{") { |
| complex = true; |
| if (!option.es6) { |
| warn("es6"); |
| } |
| param = next_token; |
| param.names = []; |
| advance("{"); |
| signature.push("{"); |
| (function subparameter() { |
| var subparam = next_token; |
| if (!subparam.identifier) { |
| return stop("expected_identifier_a"); |
| } |
| survey(subparam); |
| advance(); |
| signature.push(subparam.id); |
| if (next_token.id === ":") { |
| advance(":"); |
| advance(); |
| token.label = subparam; |
| subparam = token; |
| if (!subparam.identifier) { |
| return stop("expected_identifier_a"); |
| } |
| } |
| param.names.push(subparam); |
| if (next_token.id === ",") { |
| advance(","); |
| signature.push(", "); |
| return subparameter(); |
| } |
| }()); |
| list.push(param); |
| advance("}"); |
| signature.push("}"); |
| if (next_token.id === ",") { |
| advance(","); |
| signature.push(", "); |
| return parameter(); |
| } |
| } else if (next_token.id === "[") { |
| complex = true; |
| if (!option.es6) { |
| warn("es6"); |
| } |
| param = next_token; |
| param.names = []; |
| advance("["); |
| signature.push("[]"); |
| (function subparameter() { |
| var subparam = next_token; |
| if (!subparam.identifier) { |
| return stop("expected_identifier_a"); |
| } |
| advance(); |
| param.names.push(subparam); |
| if (next_token.id === ",") { |
| advance(","); |
| return subparameter(); |
| } |
| }()); |
| list.push(param); |
| advance("]"); |
| if (next_token.id === ",") { |
| advance(","); |
| signature.push(", "); |
| return parameter(); |
| } |
| } else { |
| if (next_token.id === "...") { |
| complex = true; |
| if (!option.es6) { |
| warn("es6"); |
| } |
| ellipsis = true; |
| signature.push("..."); |
| advance("..."); |
| } |
| if (!next_token.identifier) { |
| return stop("expected_identifier_a"); |
| } |
| param = next_token; |
| list.push(param); |
| advance(); |
| signature.push(param.id); |
| if (ellipsis) { |
| param.ellipsis = true; |
| } else { |
| if (next_token.id === "=") { |
| complex = true; |
| if (!option.es6) { |
| stop("unexpected_statement_a"); |
| } |
| advance("="); |
| param.expression = expression(0); |
| } |
| if (next_token.id === ",") { |
| advance(","); |
| signature.push(", "); |
| return parameter(); |
| } |
| } |
| } |
| }()); |
| } |
| advance(")"); |
| signature.push(")"); |
| return [list, signature.join(""), complex]; |
| } |
| |
| function do_function(the_function) { |
| var name; |
| if (the_function === undefined) { |
| the_function = token; |
| |
| // A function statement must have a name that will be in the parent's scope. |
| |
| if (the_function.arity === "statement") { |
| if (!next_token.identifier) { |
| return stop("expected_identifier_a", next_token); |
| } |
| name = next_token; |
| enroll(name, "variable", true); |
| the_function.name = name; |
| name.init = true; |
| name.calls = empty(); |
| advance(); |
| } else if (name === undefined) { |
| |
| // A function expression may have an optional name. |
| |
| if (next_token.identifier) { |
| name = next_token; |
| the_function.name = name; |
| advance(); |
| } else { |
| the_function.name = anon; |
| } |
| } |
| } else { |
| name = the_function.name; |
| } |
| the_function.level = functionage.level + 1; |
| if (mega_mode) { |
| warn("unexpected_a", the_function); |
| } |
| |
| // Don't make functions in loops. It is inefficient, and it can lead to scoping |
| // errors. |
| |
| if (functionage.loop > 0) { |
| warn("function_in_loop", the_function); |
| } |
| |
| // Give the function properties for storing its names and for observing the |
| // depth of loops and switches. |
| |
| the_function.context = empty(); |
| the_function.loop = 0; |
| the_function.switch = 0; |
| |
| // Push the current function context and establish a new one. |
| |
| stack.push(functionage); |
| functions.push(the_function); |
| functionage = the_function; |
| if (the_function.arity !== "statement" && typeof name === "object") { |
| enroll(name, "function", true); |
| name.dead = false; |
| name.init = true; |
| name.used = 1; |
| } |
| |
| // Parse the parameter list. |
| |
| advance("("); |
| token.free = false; |
| token.arity = "function"; |
| var pl = parameter_list(); |
| functionage.parameters = pl[0]; |
| functionage.signature = pl[1]; |
| functionage.complex = pl[2]; |
| functionage.parameters.forEach(function enroll_parameter(name) { |
| if (name.identifier) { |
| enroll(name, "parameter", false); |
| } else { |
| name.names.forEach(enroll_parameter); |
| } |
| }); |
| |
| // The function's body is a block. |
| |
| the_function.block = block("body"); |
| if ( |
| the_function.arity === "statement" && |
| next_token.line === token.line |
| ) { |
| return stop("unexpected_a", next_token); |
| } |
| if (next_token.id === "." || next_token.id === "[") { |
| warn("unexpected_a"); |
| } |
| |
| // Restore the previous context. |
| |
| functionage = stack.pop(); |
| return the_function; |
| } |
| |
| prefix("function", do_function); |
| |
| function fart(pl) { |
| if (next_token.id === ";") { |
| stop("wrap_assignment", token); |
| } |
| advance("=>"); |
| var the_arrow = token; |
| the_arrow.arity = "binary"; |
| the_arrow.name = "=>"; |
| the_arrow.level = functionage.level + 1; |
| functions.push(the_arrow); |
| if (functionage.loop > 0) { |
| warn("function_in_loop", the_arrow); |
| } |
| |
| // Give the function properties storing its names and for observing the depth |
| // of loops and switches. |
| |
| the_arrow.context = empty(); |
| the_arrow.loop = 0; |
| the_arrow.switch = 0; |
| |
| // Push the current function context and establish a new one. |
| |
| stack.push(functionage); |
| functionage = the_arrow; |
| the_arrow.parameters = pl[0]; |
| the_arrow.signature = pl[1]; |
| the_arrow.complex = true; |
| the_arrow.parameters.forEach(function (name) { |
| enroll(name, "parameter", true); |
| }); |
| if (!option.es6) { |
| warn("es6", the_arrow); |
| } |
| if (next_token.id === "{") { |
| warn("expected_a_b", the_arrow, "function", "=>"); |
| the_arrow.block = block("body"); |
| } else { |
| the_arrow.expression = expression(0); |
| } |
| functionage = stack.pop(); |
| return the_arrow; |
| } |
| |
| prefix("(", function () { |
| var the_paren = token; |
| var the_value; |
| var cadet = lookahead().id; |
| |
| // We can distinguish between a parameter list for => and a wrapped expression |
| // with one token of lookahead. |
| |
| if ( |
| next_token.id === ")" || |
| next_token.id === "..." || |
| (next_token.identifier && (cadet === "," || cadet === "=")) |
| ) { |
| the_paren.free = false; |
| return fart(parameter_list()); |
| } |
| the_paren.free = true; |
| the_value = expression(0); |
| if (the_value.wrapped === true) { |
| warn("unexpected_a", the_paren); |
| } |
| the_value.wrapped = true; |
| advance(")", the_paren); |
| if (next_token.id === "=>") { |
| if (the_value.arity !== "variable") { |
| if (the_value.id === "{" || the_value.id === "[") { |
| warn("expected_a_before_b", the_paren, "function", "("); |
| return stop("expected_a_b", next_token, "{", "=>"); |
| } |
| return stop("expected_identifier_a", the_value); |
| } |
| the_paren.expression = [the_value]; |
| return fart([the_paren.expression, "(" + the_value.id + ")"]); |
| } |
| return the_value; |
| }); |
| prefix("`", do_tick); |
| prefix("{", function () { |
| var the_brace = token; |
| var seen = empty(); |
| the_brace.expression = []; |
| if (next_token.id !== "}") { |
| (function member() { |
| var extra; |
| var id; |
| var name = next_token; |
| var value; |
| advance(); |
| if ( |
| (name.id === "get" || name.id === "set") && |
| next_token.identifier |
| ) { |
| extra = name.id + " " + next_token.id; |
| name = next_token; |
| advance(); |
| id = survey(name); |
| if (seen[extra] === true || seen[id] === true) { |
| warn("duplicate_a", name); |
| } |
| seen[id] = false; |
| seen[extra] = true; |
| } else { |
| id = survey(name); |
| if (typeof seen[id] === "boolean") { |
| warn("duplicate_a", name); |
| } |
| seen[id] = true; |
| } |
| if (name.identifier) { |
| switch (next_token.id) { |
| case "}": |
| case ",": |
| if (!option.es6) { |
| warn("es6"); |
| } |
| if (typeof extra === "string") { |
| advance("("); |
| } |
| value = expression(Infinity, true); |
| break; |
| case "(": |
| if (!option.es6 && typeof extra !== "string") { |
| warn("es6"); |
| } |
| value = do_function({ |
| arity: "unary", |
| from: name.from, |
| id: "function", |
| line: name.line, |
| name: (typeof extra === "string") |
| ? extra |
| : id, |
| thru: name.from |
| }); |
| break; |
| default: |
| if (typeof extra === "string") { |
| advance("("); |
| } |
| advance(":"); |
| value = expression(0); |
| } |
| value.label = name; |
| if (typeof extra === "string") { |
| value.extra = extra; |
| } |
| the_brace.expression.push(value); |
| } else { |
| advance(":"); |
| value = expression(0); |
| value.label = name; |
| the_brace.expression.push(value); |
| } |
| if (next_token.id === ",") { |
| advance(","); |
| return member(); |
| } |
| }()); |
| } |
| advance("}"); |
| return the_brace; |
| }); |
| |
| stmt(";", function () { |
| warn("unexpected_a", token); |
| return token; |
| }); |
| stmt("{", function () { |
| warn("naked_block", token); |
| return block("naked"); |
| }); |
| stmt("break", function () { |
| var the_break = token; |
| var the_label; |
| if (functionage.loop < 1 && functionage.switch < 1) { |
| warn("unexpected_a", the_break); |
| } |
| the_break.disrupt = true; |
| if (next_token.identifier && token.line === next_token.line) { |
| the_label = functionage.context[next_token.id]; |
| if ( |
| the_label === undefined || |
| the_label.role !== "label" || |
| the_label.dead |
| ) { |
| warn((the_label !== undefined && the_label.dead) |
| ? "out_of_scope_a" |
| : "not_label_a"); |
| } else { |
| the_label.used += 1; |
| } |
| the_break.label = next_token; |
| advance(); |
| } |
| advance(";"); |
| return the_break; |
| }); |
| |
| function do_var() { |
| var the_statement = token; |
| var is_const = the_statement.id === "const"; |
| the_statement.names = []; |
| |
| // A program may use var or let, but not both, and let and const require |
| // option.es6. |
| |
| if (is_const) { |
| if (!option.es6) { |
| warn("es6", the_statement); |
| } |
| } else if (var_mode === undefined) { |
| var_mode = the_statement.id; |
| if (!option.es6 && var_mode !== "var") { |
| warn("es6", the_statement); |
| } |
| } else if (the_statement.id !== var_mode) { |
| warn( |
| "expected_a_b", |
| the_statement, |
| var_mode, |
| the_statement.id |
| ); |
| } |
| |
| // We don't expect to see variables created in switch statements. |
| |
| if (functionage.switch > 0) { |
| warn("var_switch", the_statement); |
| } |
| if (functionage.loop > 0 && the_statement.id === "var") { |
| warn("var_loop", the_statement); |
| } |
| (function next() { |
| if (next_token.id === "{" && the_statement.id !== "var") { |
| var the_brace = next_token; |
| the_brace.names = []; |
| advance("{"); |
| (function pair() { |
| if (!next_token.identifier) { |
| return stop("expected_identifier_a", next_token); |
| } |
| var name = next_token; |
| survey(name); |
| advance(); |
| if (next_token.id === ":") { |
| advance(":"); |
| if (!next_token.identifier) { |
| return stop("expected_identifier_a", next_token); |
| } |
| next_token.label = name; |
| the_brace.names.push(next_token); |
| enroll(next_token, "variable", is_const); |
| advance(); |
| } else { |
| the_brace.names.push(name); |
| enroll(name, "variable", is_const); |
| } |
| if (next_token.id === ",") { |
| advance(","); |
| return pair(); |
| } |
| }()); |
| advance("}"); |
| advance("="); |
| the_brace.expression = expression(0); |
| the_statement.names.push(the_brace); |
| } else if (next_token.id === "[" && the_statement.id !== "var") { |
| var the_bracket = next_token; |
| the_bracket.names = []; |
| advance("["); |
| (function element() { |
| var ellipsis; |
| if (next_token.id === "...") { |
| ellipsis = true; |
| advance("..."); |
| } |
| if (!next_token.identifier) { |
| return stop("expected_identifier_a", next_token); |
| } |
| var name = next_token; |
| advance(); |
| the_bracket.names.push(name); |
| enroll(name, "variable", the_statement.id === "const"); |
| if (ellipsis) { |
| name.ellipsis = true; |
| } else if (next_token.id === ",") { |
| advance(","); |
| return element(); |
| } |
| }()); |
| advance("]"); |
| advance("="); |
| the_bracket.expression = expression(0); |
| the_statement.names.push(the_bracket); |
| } else if (next_token.identifier) { |
| var name = next_token; |
| advance(); |
| if (name.id === "ignore") { |
| warn("unexpected_a", name); |
| } |
| enroll(name, "variable", is_const); |
| if (next_token.id === "=" || is_const) { |
| advance("="); |
| name.expression = expression(0); |
| name.init = true; |
| } |
| the_statement.names.push(name); |
| } else { |
| return stop("expected_identifier_a", next_token); |
| } |
| if (next_token.id === ",") { |
| if (!option.multivar) { |
| warn("expected_a_b", next_token, ";", ","); |
| } |
| advance(","); |
| return next(); |
| } |
| }()); |
| the_statement.open = |
| the_statement.names.length > 1 && |
| the_statement.line !== the_statement.names[1].line; |
| semicolon(); |
| return the_statement; |
| } |
| |
| stmt("const", do_var); |
| stmt("continue", function () { |
| var the_continue = token; |
| if (functionage.loop < 1) { |
| warn("unexpected_a", the_continue); |
| } |
| not_top_level(the_continue); |
| the_continue.disrupt = true; |
| warn("unexpected_a", the_continue); |
| advance(";"); |
| return the_continue; |
| }); |
| stmt("debugger", function () { |
| var the_debug = token; |
| if (!option.devel) { |
| warn("unexpected_a", the_debug); |
| } |
| semicolon(); |
| return the_debug; |
| }); |
| stmt("delete", function () { |
| var the_token = token; |
| var the_value = expression(0); |
| if ( |
| (the_value.id !== "." && the_value.id !== "[") || |
| the_value.arity !== "binary" |
| ) { |
| stop("expected_a_b", the_value, ".", artifact(the_value)); |
| } |
| the_token.expression = the_value; |
| semicolon(); |
| return the_token; |
| }); |
| stmt("do", function () { |
| var the_do = token; |
| not_top_level(the_do); |
| functionage.loop += 1; |
| the_do.block = block(); |
| advance("while"); |
| the_do.expression = condition(); |
| semicolon(); |
| if (the_do.block.disrupt === true) { |
| warn("weird_loop", the_do); |
| } |
| functionage.loop -= 1; |
| return the_do; |
| }); |
| stmt("export", function () { |
| var the_export = token; |
| if (!option.es6) { |
| warn("es6", the_export); |
| } |
| if (typeof module_mode === "object") { |
| warn("unexpected_directive_a", module_mode, module_mode.directive); |
| } |
| advance("default"); |
| if (export_mode) { |
| warn("duplicate_a", token); |
| } |
| module_mode = true; |
| export_mode = true; |
| the_export.expression = expression(0); |
| semicolon(); |
| return the_export; |
| }); |
| stmt("for", function () { |
| var first; |
| var the_for = token; |
| if (!option.for) { |
| warn("unexpected_a", the_for); |
| } |
| not_top_level(the_for); |
| functionage.loop += 1; |
| advance("("); |
| token.free = true; |
| if (next_token.id === ";") { |
| return stop("expected_a_b", the_for, "while (", "for (;"); |
| } |
| if ( |
| next_token.id === "var" || |
| next_token.id === "let" || |
| next_token.id === "const" |
| ) { |
| return stop("unexpected_a"); |
| } |
| first = expression(0); |
| if (first.id === "in") { |
| if (first.expression[0].arity !== "variable") { |
| warn("bad_assignment_a", first.expression[0]); |
| } |
| the_for.name = first.expression[0]; |
| the_for.expression = first.expression[1]; |
| warn("expected_a_b", the_for, "Object.keys", "for in"); |
| } else { |
| the_for.initial = first; |
| advance(";"); |
| the_for.expression = expression(0); |
| advance(";"); |
| the_for.inc = expression(0); |
| if (the_for.inc.id === "++") { |
| warn("expected_a_b", the_for.inc, "+= 1", "++"); |
| } |
| } |
| advance(")"); |
| the_for.block = block(); |
| if (the_for.block.disrupt === true) { |
| warn("weird_loop", the_for); |
| } |
| functionage.loop -= 1; |
| return the_for; |
| }); |
| stmt("function", do_function); |
| stmt("if", function () { |
| var the_else; |
| var the_if = token; |
| the_if.expression = condition(); |
| the_if.block = block(); |
| if (next_token.id === "else") { |
| advance("else"); |
| the_else = token; |
| the_if.else = (next_token.id === "if") |
| ? statement() |
| : block(); |
| if (the_if.block.disrupt === true) { |
| if (the_if.else.disrupt === true) { |
| the_if.disrupt = true; |
| } else { |
| warn("unexpected_a", the_else); |
| } |
| } |
| } |
| return the_if; |
| }); |
| stmt("import", function () { |
| var the_import = token; |
| var name; |
| if (!option.es6) { |
| warn("es6", the_import); |
| } else if (typeof module_mode === "object") { |
| warn("unexpected_directive_a", module_mode, module_mode.directive); |
| } |
| module_mode = true; |
| if (next_token.identifier) { |
| name = next_token; |
| advance(); |
| if (name.id === "ignore") { |
| warn("unexpected_a", name); |
| } |
| enroll(name, "variable", true); |
| the_import.name = name; |
| } else { |
| var names = []; |
| advance("{"); |
| if (next_token.id !== "}") { |
| while (true) { |
| if (!next_token.identifier) { |
| stop("expected_identifier_a"); |
| } |
| name = next_token; |
| advance(); |
| if (name.id === "ignore") { |
| warn("unexpected_a", name); |
| } |
| enroll(name, "variable", true); |
| names.push(name); |
| if (next_token.id !== ",") { |
| break; |
| } |
| advance(","); |
| } |
| } |
| advance("}"); |
| the_import.name = names; |
| } |
| advance("from"); |
| advance("(string)"); |
| the_import.import = token; |
| if (!rx_module.test(token.value)) { |
| warn("bad_module_name_a", token); |
| } |
| imports.push(token.value); |
| semicolon(); |
| return the_import; |
| }); |
| stmt("let", do_var); |
| stmt("return", function () { |
| var the_return = token; |
| not_top_level(the_return); |
| the_return.disrupt = true; |
| if (next_token.id !== ";" && the_return.line === next_token.line) { |
| the_return.expression = expression(10); |
| } |
| advance(";"); |
| return the_return; |
| }); |
| stmt("switch", function () { |
| var dups = []; |
| var last; |
| var stmts; |
| var the_cases = []; |
| var the_disrupt = true; |
| var the_switch = token; |
| not_top_level(the_switch); |
| functionage.switch += 1; |
| advance("("); |
| token.free = true; |
| the_switch.expression = expression(0); |
| the_switch.block = the_cases; |
| advance(")"); |
| advance("{"); |
| (function major() { |
| var the_case = next_token; |
| the_case.arity = "statement"; |
| the_case.expression = []; |
| (function minor() { |
| advance("case"); |
| token.switch = true; |
| var exp = expression(0); |
| if (dups.some(function (thing) { |
| return are_similar(thing, exp); |
| })) { |
| warn("unexpected_a", exp); |
| } |
| dups.push(exp); |
| the_case.expression.push(exp); |
| advance(":"); |
| if (next_token.id === "case") { |
| return minor(); |
| } |
| }()); |
| stmts = statements(); |
| if (stmts.length < 1) { |
| warn("expected_statements_a"); |
| return; |
| } |
| the_case.block = stmts; |
| the_cases.push(the_case); |
| last = stmts[stmts.length - 1]; |
| if (last.disrupt) { |
| if (last.id === "break" && last.label === undefined) { |
| the_disrupt = false; |
| } |
| } else { |
| warn( |
| "expected_a_before_b", |
| next_token, |
| "break;", |
| artifact(next_token) |
| ); |
| } |
| if (next_token.id === "case") { |
| return major(); |
| } |
| }()); |
| dups = undefined; |
| if (next_token.id === "default") { |
| var the_default = next_token; |
| advance("default"); |
| token.switch = true; |
| advance(":"); |
| the_switch.else = statements(); |
| if (the_switch.else.length < 1) { |
| warn("unexpected_a", the_default); |
| the_disrupt = false; |
| } else { |
| var the_last = the_switch.else[the_switch.else.length - 1]; |
| if (the_last.id === "break" && the_last.label === undefined) { |
| warn("unexpected_a", the_last); |
| the_last.disrupt = false; |
| } |
| the_disrupt = the_disrupt && the_last.disrupt; |
| } |
| } else { |
| the_disrupt = false; |
| } |
| advance("}", the_switch); |
| functionage.switch -= 1; |
| the_switch.disrupt = the_disrupt; |
| return the_switch; |
| }); |
| stmt("throw", function () { |
| var the_throw = token; |
| the_throw.disrupt = true; |
| the_throw.expression = expression(10); |
| semicolon(); |
| return the_throw; |
| }); |
| stmt("try", function () { |
| var clause = false; |
| var the_catch; |
| var the_disrupt; |
| var the_try = token; |
| the_try.block = block(); |
| the_disrupt = the_try.block.disrupt; |
| if (next_token.id === "catch") { |
| var ignored = "ignore"; |
| clause = true; |
| the_catch = next_token; |
| the_try.catch = the_catch; |
| advance("catch"); |
| advance("("); |
| if (!next_token.identifier) { |
| return stop("expected_identifier_a", next_token); |
| } |
| if (next_token.id !== "ignore") { |
| ignored = undefined; |
| the_catch.name = next_token; |
| enroll(next_token, "exception", true); |
| } |
| advance(); |
| advance(")"); |
| the_catch.block = block(ignored); |
| if (the_catch.block.disrupt !== true) { |
| the_disrupt = false; |
| } |
| } |
| if (next_token.id === "finally") { |
| clause = true; |
| advance("finally"); |
| the_try.else = block(); |
| the_disrupt = the_try.else.disrupt; |
| } |
| the_try.disrupt = the_disrupt; |
| if (!clause) { |
| warn( |
| "expected_a_before_b", |
| next_token, |
| "catch", |
| artifact(next_token) |
| ); |
| } |
| return the_try; |
| }); |
| stmt("var", do_var); |
| stmt("while", function () { |
| var the_while = token; |
| not_top_level(the_while); |
| functionage.loop += 1; |
| the_while.expression = condition(); |
| the_while.block = block(); |
| if (the_while.block.disrupt === true) { |
| warn("weird_loop", the_while); |
| } |
| functionage.loop -= 1; |
| return the_while; |
| }); |
| stmt("with", function () { |
| stop("unexpected_a", token); |
| }); |
| |
| ternary("?", ":"); |
| |
| // Ambulation of the parse tree. |
| |
| function action(when) { |
| |
| // Produce a function that will register task functions that will be called as |
| // the tree is traversed. |
| |
| return function (arity, id, task) { |
| var a_set = when[arity]; |
| var i_set; |
| |
| // The id parameter is optional. If excluded, the task will be applied to all |
| // ids. |
| |
| if (typeof id !== "string") { |
| task = id; |
| id = "(all)"; |
| } |
| |
| // If this arity has no registrations yet, then create a set object to hold |
| // them. |
| |
| if (a_set === undefined) { |
| a_set = empty(); |
| when[arity] = a_set; |
| } |
| |
| // If this id has no registrations yet, then create a set array to hold them. |
| |
| i_set = a_set[id]; |
| if (i_set === undefined) { |
| i_set = []; |
| a_set[id] = i_set; |
| } |
| |
| // Register the task with the arity and the id. |
| |
| i_set.push(task); |
| }; |
| } |
| |
| function amble(when) { |
| |
| // Produce a function that will act on the tasks registered by an action |
| // function while walking the tree. |
| |
| return function (the_token) { |
| |
| // Given a task set that was built by an action function, run all of the |
| // relevant tasks on the token. |
| |
| var a_set = when[the_token.arity]; |
| var i_set; |
| |
| // If there are tasks associated with the token's arity... |
| |
| if (a_set !== undefined) { |
| |
| // If there are tasks associated with the token's id... |
| |
| i_set = a_set[the_token.id]; |
| if (i_set !== undefined) { |
| i_set.forEach(function (task) { |
| return task(the_token); |
| }); |
| } |
| |
| // If there are tasks for all ids. |
| |
| i_set = a_set["(all)"]; |
| if (i_set !== undefined) { |
| i_set.forEach(function (task) { |
| return task(the_token); |
| }); |
| } |
| } |
| }; |
| } |
| |
| var posts = empty(); |
| var pres = empty(); |
| var preaction = action(pres); |
| var postaction = action(posts); |
| var preamble = amble(pres); |
| var postamble = amble(posts); |
| |
| function walk_expression(thing) { |
| if (thing) { |
| if (Array.isArray(thing)) { |
| thing.forEach(walk_expression); |
| } else { |
| preamble(thing); |
| walk_expression(thing.expression); |
| if (thing.id === "function") { |
| walk_statement(thing.block); |
| } |
| switch (thing.arity) { |
| case "post": |
| case "pre": |
| warn("unexpected_a", thing); |
| break; |
| case "statement": |
| case "assignment": |
| warn("unexpected_statement_a", thing); |
| break; |
| } |
| postamble(thing); |
| } |
| } |
| } |
| |
| function walk_statement(thing) { |
| if (thing) { |
| if (Array.isArray(thing)) { |
| thing.forEach(walk_statement); |
| } else { |
| preamble(thing); |
| walk_expression(thing.expression); |
| switch (thing.arity) { |
| case "statement": |
| case "assignment": |
| break; |
| case "binary": |
| if (thing.id !== "(") { |
| warn("unexpected_expression_a", thing); |
| } |
| break; |
| default: |
| warn(( |
| thing.id === "(string)" && |
| thing.value === "use strict" |
| ) |
| ? "unexpected_a" |
| : "unexpected_expression_a", thing); |
| } |
| walk_statement(thing.block); |
| walk_statement(thing.else); |
| postamble(thing); |
| } |
| } |
| } |
| |
| function lookup(thing) { |
| if (thing.arity === "variable") { |
| |
| // Look up the variable in the current context. |
| |
| var the_variable = functionage.context[thing.id]; |
| |
| // If it isn't local, search all the other contexts. If there are name |
| // collisions, take the most recent. |
| |
| if (the_variable === undefined) { |
| stack.forEach(function (outer) { |
| var a_variable = outer.context[thing.id]; |
| if ( |
| a_variable !== undefined && |
| a_variable.role !== "label" |
| ) { |
| the_variable = a_variable; |
| } |
| }); |
| |
| // If it isn't in any of those either, perhaps it is a predefined global. |
| // If so, add it to the global context. |
| |
| if (the_variable === undefined) { |
| if (declared_globals[thing.id] === undefined) { |
| warn("undeclared_a", thing); |
| return; |
| } |
| the_variable = { |
| dead: false, |
| function: global, |
| id: thing.id, |
| init: true, |
| role: "variable", |
| used: 0, |
| writable: false |
| }; |
| global.context[thing.id] = the_variable; |
| } |
| the_variable.closure = true; |
| functionage.context[thing.id] = the_variable; |
| } else if (the_variable.role === "label") { |
| warn("label_a", thing); |
| } |
| if (the_variable.dead) { |
| warn("out_of_scope_a", thing); |
| } |
| return the_variable; |
| } |
| } |
| |
| function subactivate(name) { |
| name.init = true; |
| name.dead = false; |
| blockage.live.push(name); |
| } |
| |
| function preaction_function(thing) { |
| if (thing.arity === "statement" && blockage.body !== true) { |
| warn("unexpected_a", thing); |
| } |
| if (thing.level === 1) { |
| if ( |
| module_mode === true || |
| global.strict !== undefined || |
| thing.complex |
| ) { |
| if (thing.id !== "=>" && thing.block.strict !== undefined) { |
| warn("unexpected_a", thing.block.strict); |
| } |
| } else { |
| if (thing.block.strict === undefined) { |
| warn("use_strict", thing); |
| } |
| } |
| } |
| stack.push(functionage); |
| block_stack.push(blockage); |
| functionage = thing; |
| blockage = thing; |
| thing.live = []; |
| if (typeof thing.name === "object") { |
| thing.name.dead = false; |
| thing.name.init = true; |
| } |
| switch (thing.extra) { |
| case "get": |
| if (thing.parameters.length !== 0) { |
| warn("bad_get", thing); |
| } |
| break; |
| case "set": |
| if (thing.parameters.length !== 1) { |
| warn("bad_set", thing); |
| } |
| break; |
| } |
| thing.parameters.forEach(function (name) { |
| walk_expression(name.expression); |
| if (name.id === "{" || name.id === "[") { |
| name.names.forEach(subactivate); |
| } else { |
| name.dead = false; |
| name.init = true; |
| } |
| }); |
| } |
| |
| function bitwise_check(thing) { |
| if (!option.bitwise && bitwiseop[thing.id] === true) { |
| warn("unexpected_a", thing); |
| } |
| if ( |
| thing.id !== "(" && |
| thing.id !== "&&" && |
| thing.id !== "||" && |
| thing.id !== "=" && |
| Array.isArray(thing.expression) && |
| thing.expression.length === 2 && ( |
| relationop[thing.expression[0].id] === true || |
| relationop[thing.expression[1].id] === true |
| ) |
| ) { |
| warn("unexpected_a", thing); |
| } |
| } |
| |
| function pop_block() { |
| blockage.live.forEach(function (name) { |
| name.dead = true; |
| }); |
| delete blockage.live; |
| blockage = block_stack.pop(); |
| } |
| |
| function activate(name) { |
| if (name.expression !== undefined) { |
| walk_expression(name.expression); |
| if (name.id === "{" || name.id === "[") { |
| name.names.forEach(subactivate); |
| } else { |
| name.init = true; |
| } |
| } |
| name.dead = false; |
| blockage.live.push(name); |
| } |
| |
| function action_var(thing) { |
| thing.names.forEach(activate); |
| } |
| |
| preaction("assignment", bitwise_check); |
| preaction("binary", bitwise_check); |
| preaction("binary", function (thing) { |
| if (relationop[thing.id] === true) { |
| var left = thing.expression[0]; |
| var right = thing.expression[1]; |
| if (left.id === "NaN" || right.id === "NaN") { |
| if (option.es6) { |
| warn("number_isNaN", thing); |
| } else { |
| warn("isNaN", thing); |
| } |
| } else if (left.id === "typeof") { |
| if (right.id !== "(string)") { |
| if (right.id !== "typeof") { |
| warn("expected_string_a", right); |
| } |
| } else { |
| var value = right.value; |
| if (value === "symbol") { |
| if (!option.es6) { |
| warn("es6", right, value); |
| } |
| } else if (value === "null" || value === "undefined") { |
| warn("unexpected_typeof_a", right, value); |
| } else if ( |
| value !== "boolean" && |
| value !== "function" && |
| value !== "number" && |
| value !== "object" && |
| value !== "string" |
| ) { |
| warn("expected_type_string_a", right, value); |
| } |
| } |
| } |
| } |
| }); |
| preaction("binary", "==", function (thing) { |
| warn("expected_a_b", thing, "===", "=="); |
| }); |
| preaction("binary", "!=", function (thing) { |
| warn("expected_a_b", thing, "!==", "!="); |
| }); |
| preaction("binary", "=>", preaction_function); |
| preaction("binary", "||", function (thing) { |
| thing.expression.forEach(function (thang) { |
| if (thang.id === "&&" && !thang.wrapped) { |
| warn("and", thang); |
| } |
| }); |
| }); |
| preaction("binary", "(", function (thing) { |
| var left = thing.expression[0]; |
| if ( |
| left.identifier && |
| functionage.context[left.id] === undefined && |
| typeof functionage.name === "object" |
| ) { |
| var parent = functionage.name.function; |
| if (parent) { |
| var left_variable = parent.context[left.id]; |
| if ( |
| left_variable !== undefined && |
| left_variable.dead && |
| left_variable.function === parent && |
| left_variable.calls !== undefined && |
| left_variable.calls[functionage.name.id] !== undefined |
| ) { |
| left_variable.dead = false; |
| } |
| } |
| } |
| }); |
| preaction("binary", "in", function (thing) { |
| warn("infix_in", thing); |
| }); |
| preaction("binary", "instanceof", function (thing) { |
| warn("unexpected_a", thing); |
| }); |
| preaction("binary", ".", function (thing) { |
| if (thing.expression.new) { |
| thing.new = true; |
| } |
| }); |
| preaction("statement", "{", function (thing) { |
| block_stack.push(blockage); |
| blockage = thing; |
| thing.live = []; |
| }); |
| preaction("statement", "for", function (thing) { |
| if (thing.name !== undefined) { |
| var the_variable = lookup(thing.name); |
| if (the_variable !== undefined) { |
| the_variable.init = true; |
| if (!the_variable.writable) { |
| warn("bad_assignment_a", thing.name); |
| } |
| } |
| } |
| walk_statement(thing.initial); |
| }); |
| preaction("statement", "function", preaction_function); |
| preaction("unary", "~", bitwise_check); |
| preaction("unary", "function", preaction_function); |
| preaction("variable", function (thing) { |
| var the_variable = lookup(thing); |
| if (the_variable !== undefined) { |
| thing.variable = the_variable; |
| the_variable.used += 1; |
| } |
| }); |
| |
| function init_variable(name) { |
| var the_variable = lookup(name); |
| if (the_variable !== undefined) { |
| if (the_variable.writable) { |
| the_variable.init = true; |
| return; |
| } |
| } |
| warn("bad_assignment_a", name); |
| } |
| |
| postaction("assignment", function (thing) { |
| |
| // Assignment using = sets the init property of a variable. No other assignment |
| // operator can do this. A = token keeps that variable (or array of variables |
| // in case of destructuring) in its name property. |
| |
| var lvalue = thing.expression[0]; |
| if (thing.id === "=") { |
| if (thing.names !== undefined) { |
| if (Array.isArray(thing.names)) { |
| thing.names.forEach(init_variable); |
| } else { |
| init_variable(thing.names); |
| } |
| } else { |
| if ( |
| lvalue.id === "." && |
| thing.expression[1].id === "undefined" |
| ) { |
| warn( |
| "expected_a_b", |
| lvalue.expression, |
| "delete", |
| "undefined" |
| ); |
| } |
| } |
| } else { |
| if (lvalue.arity === "variable") { |
| if (!lvalue.variable || lvalue.variable.writable !== true) { |
| warn("bad_assignment_a", lvalue); |
| } |
| } |
| var right = syntax[thing.expression[1].id]; |
| if ( |
| right !== undefined && |
| ( |
| right.id === "function" || |
| right.id === "=>" || |
| ( |
| right.constant && |
| right.id !== "(number)" && |
| (right.id !== "(string)" || thing.id !== "+=") |
| ) |
| ) |
| ) { |
| warn("unexpected_a", thing.expression[1]); |
| } |
| } |
| }); |
| |
| function postaction_function(thing) { |
| delete functionage.loop; |
| delete functionage.switch; |
| functionage = stack.pop(); |
| if (thing.wrapped) { |
| warn("unexpected_parens", thing); |
| } |
| if (typeof thing.name === "object") { |
| thing.name.used = 0; |
| } |
| return pop_block(); |
| } |
| |
| postaction("binary", function (thing) { |
| var right; |
| if (relationop[thing.id]) { |
| if ( |
| is_weird(thing.expression[0]) || |
| is_weird(thing.expression[1]) || |
| are_similar(thing.expression[0], thing.expression[1]) || |
| ( |
| thing.expression[0].constant === true && |
| thing.expression[1].constant === true |
| ) |
| ) { |
| warn("weird_relation_a", thing); |
| } |
| } |
| switch (thing.id) { |
| case "+": |
| case "-": |
| right = thing.expression[1]; |
| if ( |
| right.id === thing.id && |
| right.arity === "unary" && |
| !right.wrapped |
| ) { |
| warn("wrap_unary", right); |
| } |
| break; |
| case "=>": |
| case "(": |
| break; |
| case ".": |
| if (thing.expression.id === "RegExp") { |
| warn("weird_expression_a", thing); |
| } |
| break; |
| default: |
| if ( |
| thing.expression[0].constant === true && |
| thing.expression[1].constant === true |
| ) { |
| thing.constant = true; |
| } |
| } |
| }); |
| postaction("binary", "&&", function (thing) { |
| if ( |
| is_weird(thing.expression[0]) || |
| are_similar(thing.expression[0], thing.expression[1]) || |
| thing.expression[0].constant === true || |
| thing.expression[1].constant === true |
| ) { |
| warn("weird_condition_a", thing); |
| } |
| }); |
| postaction("binary", "||", function (thing) { |
| if ( |
| is_weird(thing.expression[0]) || |
| are_similar(thing.expression[0], thing.expression[1]) || |
| thing.expression[0].constant === true |
| ) { |
| warn("weird_condition_a", thing); |
| } |
| }); |
| postaction("binary", "=>", postaction_function); |
| postaction("binary", "(", function (thing) { |
| var left = thing.expression[0]; |
| var the_new; |
| if (left.id === "new") { |
| the_new = left; |
| left = left.expression; |
| } |
| if (left.id === "function") { |
| if (!thing.wrapped) { |
| warn("wrap_immediate", thing); |
| } |
| } else if (left.identifier) { |
| if (the_new !== undefined) { |
| if ( |
| left.id.charAt(0) > "Z" || |
| left.id === "Boolean" || |
| left.id === "Number" || |
| left.id === "String" || |
| (left.id === "Symbol" && option.es6) |
| ) { |
| warn("unexpected_a", the_new); |
| } else if (left.id === "Function") { |
| if (!option.eval) { |
| warn("unexpected_a", left, "new Function"); |
| } |
| } else if (left.id === "Array") { |
| warn("expected_a_b", left, "[]", "new Array"); |
| } else if (left.id === "Object") { |
| warn( |
| "expected_a_b", |
| left, |
| "Object.create(null)", |
| "new Object" |
| ); |
| } |
| } else { |
| if ( |
| left.id.charAt(0) >= "A" && |
| left.id.charAt(0) <= "Z" && |
| left.id !== "Boolean" && |
| left.id !== "Number" && |
| left.id !== "String" && |
| left.id !== "Symbol" |
| ) { |
| warn( |
| "expected_a_before_b", |
| left, |
| "new", |
| artifact(left) |
| ); |
| } |
| } |
| } else if (left.id === ".") { |
| var cack = the_new !== undefined; |
| if (left.expression.id === "Date" && left.name.id === "UTC") { |
| cack = !cack; |
| } |
| if (rx_cap.test(left.name.id) !== cack) { |
| if (the_new !== undefined) { |
| warn("unexpected_a", the_new); |
| } else { |
| warn( |
| "expected_a_before_b", |
| left.expression, |
| "new", |
| left.name.id |
| ); |
| } |
| } |
| if (left.name.id === "getTime") { |
| var l1 = left.expression; |
| if (l1.id === "(") { |
| var l2 = l1.expression; |
| if (l2.length === 1) { |
| var l3 = l2[0]; |
| if (l3.id === "new" && l3.expression.id === "Date") { |
| warn( |
| "expected_a_b", |
| l3, |
| "Date.now()", |
| "new Date().getTime()" |
| ); |
| } |
| } |
| } |
| } |
| } |
| }); |
| postaction("binary", "[", function (thing) { |
| if (thing.expression[0].id === "RegExp") { |
| warn("weird_expression_a", thing); |
| } |
| if (is_weird(thing.expression[1])) { |
| warn("weird_expression_a", thing.expression[1]); |
| } |
| }); |
| postaction("statement", "{", pop_block); |
| postaction("statement", "const", action_var); |
| postaction("statement", "export", top_level_only); |
| postaction("statement", "for", function (thing) { |
| walk_statement(thing.inc); |
| }); |
| postaction("statement", "function", postaction_function); |
| postaction("statement", "import", function (the_thing) { |
| var name = the_thing.name; |
| if (Array.isArray(name)) { |
| name.forEach(function (name) { |
| name.dead = false; |
| name.init = true; |
| blockage.live.push(name); |
| }); |
| } else { |
| name.dead = false; |
| name.init = true; |
| blockage.live.push(name); |
| } |
| return top_level_only(the_thing); |
| }); |
| postaction("statement", "let", action_var); |
| postaction("statement", "try", function (thing) { |
| if (thing.catch !== undefined) { |
| var the_name = thing.catch.name; |
| if (the_name !== undefined) { |
| var the_variable = functionage.context[the_name.id]; |
| the_variable.dead = false; |
| the_variable.init = true; |
| } |
| walk_statement(thing.catch.block); |
| } |
| }); |
| postaction("statement", "var", action_var); |
| postaction("ternary", function (thing) { |
| if ( |
| is_weird(thing.expression[0]) || |
| thing.expression[0].constant === true || |
| are_similar(thing.expression[1], thing.expression[2]) |
| ) { |
| warn("unexpected_a", thing); |
| } else if (are_similar(thing.expression[0], thing.expression[1])) { |
| warn("expected_a_b", thing, "||", "?"); |
| } else if (are_similar(thing.expression[0], thing.expression[2])) { |
| warn("expected_a_b", thing, "&&", "?"); |
| } else if ( |
| thing.expression[1].id === "true" && |
| thing.expression[2].id === "false" |
| ) { |
| warn("expected_a_b", thing, "!!", "?"); |
| } else if ( |
| thing.expression[1].id === "false" && |
| thing.expression[2].id === "true" |
| ) { |
| warn("expected_a_b", thing, "!", "?"); |
| } else if (thing.expression[0].wrapped !== true && ( |
| thing.expression[0].id === "||" || |
| thing.expression[0].id === "&&" |
| )) { |
| warn("wrap_condition", thing.expression[0]); |
| } |
| }); |
| postaction("unary", function (thing) { |
| switch (thing.id) { |
| case "[": |
| case "{": |
| case "function": |
| case "new": |
| break; |
| case "`": |
| if (thing.expression.every(function (thing) { |
| return thing.constant; |
| })) { |
| thing.constant = true; |
| } |
| break; |
| default: |
| if (thing.expression.constant === true) { |
| thing.constant = true; |
| } |
| } |
| }); |
| postaction("unary", "function", postaction_function); |
| |
| function delve(the_function) { |
| Object.keys(the_function.context).forEach(function (id) { |
| if (id !== "ignore") { |
| var name = the_function.context[id]; |
| if (name.function === the_function) { |
| if (name.used === 0 && ( |
| name.role !== "function" || |
| name.function.arity !== "unary" |
| )) { |
| warn("unused_a", name); |
| } else if (!name.init) { |
| warn("uninitialized_a", name); |
| } |
| } |
| } |
| }); |
| } |
| |
| function uninitialized_and_unused() { |
| |
| // Delve into the functions looking for variables that were not initialized |
| // or used. If the file imports or exports, then its global object is also |
| // delved. |
| |
| if (module_mode === true || option.node) { |
| delve(global); |
| } |
| functions.forEach(delve); |
| } |
| |
| // Go through the token list, looking at usage of whitespace. |
| |
| function whitage() { |
| var closer = "(end)"; |
| var free = false; |
| var left = global; |
| var margin = 0; |
| var nr_comments_skipped = 0; |
| var open = true; |
| var qmark = ""; |
| var result; |
| var right; |
| |
| function expected_at(at) { |
| warn( |
| "expected_a_at_b_c", |
| right, |
| artifact(right), |
| fudge + at, |
| artifact_column(right) |
| ); |
| } |
| |
| function at_margin(fit) { |
| var at = margin + fit; |
| if (right.from !== at) { |
| return expected_at(at); |
| } |
| } |
| |
| function no_space_only() { |
| if ( |
| left.id !== "(global)" && |
| left.nr + 1 === right.nr && ( |
| left.line !== right.line || |
| left.thru !== right.from |
| ) |
| ) { |
| warn( |
| "unexpected_space_a_b", |
| right, |
| artifact(left), |
| artifact(right) |
| ); |
| } |
| } |
| |
| function no_space() { |
| if (left.line === right.line) { |
| if (left.thru !== right.from && nr_comments_skipped === 0) { |
| warn( |
| "unexpected_space_a_b", |
| right, |
| artifact(left), |
| artifact(right) |
| ); |
| } |
| } else { |
| if (open) { |
| var at = (free) |
| ? margin |
| : margin + 8; |
| if (right.from < at) { |
| expected_at(at); |
| } |
| } else { |
| if (right.from !== margin + 8) { |
| expected_at(margin + 8); |
| } |
| } |
| } |
| } |
| |
| function one_space_only() { |
| if (left.line !== right.line || left.thru + 1 !== right.from) { |
| warn( |
| "expected_space_a_b", |
| right, |
| artifact(left), |
| artifact(right) |
| ); |
| } |
| } |
| |
| function one_space() { |
| if (left.line === right.line) { |
| if (left.thru + 1 !== right.from && nr_comments_skipped === 0) { |
| warn( |
| "expected_space_a_b", |
| right, |
| artifact(left), |
| artifact(right) |
| ); |
| } |
| } else { |
| if (free) { |
| if (right.from < margin) { |
| expected_at(margin); |
| } |
| } else { |
| if (right.from !== margin + 8) { |
| expected_at(margin + 8); |
| } |
| } |
| } |
| } |
| |
| function unqmark() { |
| |
| // Undo the effects of dangling nested ternary operators. |
| |
| var level = qmark.length; |
| if (level > 0) { |
| margin -= level * 4; |
| } |
| qmark = ""; |
| } |
| |
| stack = []; |
| tokens.forEach(function (the_token) { |
| right = the_token; |
| if (right.id === "(comment)" || right.id === "(end)") { |
| nr_comments_skipped += 1; |
| } else { |
| |
| // If left is an opener and right is not the closer, then push the previous |
| // state. If the token following the opener is on the next line, then this is |
| // an open form. If the tokens are on the same line, then it is a closed form. |
| // Open form is more readable, with each item (statement, argument, parameter, |
| // etc) starting on its own line. Closed form is more compact. Statement blocks |
| // are always in open form. |
| |
| var new_closer = opener[left.id]; |
| if (typeof new_closer === "string") { |
| if (new_closer !== right.id) { |
| stack.push({ |
| closer: closer, |
| free: free, |
| margin: margin, |
| open: open, |
| qmark: qmark |
| }); |
| qmark = ""; |
| closer = new_closer; |
| if (left.line !== right.line) { |
| free = closer === ")" && left.free; |
| open = true; |
| margin += 4; |
| if (right.role === "label") { |
| if (right.from !== 0) { |
| expected_at(0); |
| } |
| } else if (right.switch) { |
| unqmark(); |
| at_margin(-4); |
| } else { |
| at_margin(0); |
| } |
| } else { |
| if (right.statement || right.role === "label") { |
| warn( |
| "expected_line_break_a_b", |
| right, |
| artifact(left), |
| artifact(right) |
| ); |
| } |
| free = false; |
| open = false; |
| no_space_only(); |
| } |
| } else { |
| |
| // If left and right are opener and closer, then the placement of right depends |
| // on the openness. Illegal pairs (like {]) have already been detected. |
| |
| if (left.line === right.line) { |
| no_space(); |
| } else { |
| at_margin(0); |
| } |
| } |
| } else { |
| |
| // If right is a closer, then pop the previous state. |
| |
| if (right.id === closer) { |
| var previous = stack.pop(); |
| margin = previous.margin; |
| if (open && right.id !== ";") { |
| at_margin(0); |
| } else { |
| no_space_only(); |
| } |
| closer = previous.closer; |
| free = previous.free; |
| open = previous.open; |
| qmark = previous.qmark; |
| } else { |
| |
| // Left is not an opener, and right is not a closer. The nature of left and |
| // right will determine the space between them. |
| |
| // If left is , or ; or right is a statement then if open, right must go at the |
| // margin, or if closed, a space between. |
| |
| |
| if (right.switch) { |
| unqmark(); |
| at_margin(-4); |
| } else if (right.role === "label") { |
| if (right.from !== 0) { |
| expected_at(0); |
| } |
| } else if (left.id === ",") { |
| unqmark(); |
| if (!open || ( |
| (free || closer === "]") && |
| left.line === right.line |
| )) { |
| one_space(); |
| } else { |
| at_margin(0); |
| } |
| |
| // If right is a ternary operator, line it up on the margin. Use qmark to |
| // deal with nested ternary operators. |
| |
| } else if (right.arity === "ternary") { |
| if (right.id === "?") { |
| margin += 4; |
| qmark += "?"; |
| } else { |
| result = qmark.match(rx_colons); |
| qmark = result[1] + ":"; |
| margin -= 4 * result[2].length; |
| } |
| at_margin(0); |
| } else if ( |
| right.arity === "binary" && |
| right.id === "(" && |
| free |
| ) { |
| no_space(); |
| } else if ( |
| left.id === "." || |
| left.id === "..." || |
| right.id === "," || |
| right.id === ";" || |
| right.id === ":" || |
| (right.arity === "binary" && ( |
| right.id === "(" || |
| right.id === "[" |
| )) || |
| ( |
| right.arity === "function" && |
| left.id !== "function" |
| ) |
| ) { |
| no_space_only(); |
| } else if (right.id === ".") { |
| if (left.line === right.line) { |
| no_space(); |
| } else { |
| if (!rx_dot.test(qmark)) { |
| qmark += "."; |
| margin += 4; |
| } |
| at_margin(0); |
| } |
| } else if (left.id === ";") { |
| unqmark(); |
| if (open) { |
| at_margin(0); |
| } else { |
| one_space(); |
| } |
| } else if ( |
| left.arity === "ternary" || |
| left.id === "case" || |
| left.id === "catch" || |
| left.id === "else" || |
| left.id === "finally" || |
| left.id === "while" || |
| right.id === "catch" || |
| right.id === "else" || |
| right.id === "finally" || |
| (right.id === "while" && !right.statement) || |
| (left.id === ")" && right.id === "{") |
| ) { |
| one_space_only(); |
| } else if (right.statement === true) { |
| if (open) { |
| at_margin(0); |
| } else { |
| one_space(); |
| } |
| } else if ( |
| left.id === "var" || |
| left.id === "const" || |
| left.id === "let" |
| ) { |
| stack.push({ |
| closer: closer, |
| free: free, |
| margin: margin, |
| open: open, |
| qmark: qmark |
| }); |
| closer = ";"; |
| free = false; |
| open = left.open; |
| qmark = ""; |
| if (open) { |
| margin = margin + 4; |
| at_margin(0); |
| } else { |
| one_space_only(); |
| } |
| } else if ( |
| |
| // There is a space between left and right. |
| |
| spaceop[left.id] === true || |
| spaceop[right.id] === true || |
| ( |
| left.arity === "binary" && |
| (left.id === "+" || left.id === "-") |
| ) || |
| ( |
| right.arity === "binary" && |
| (right.id === "+" || right.id === "-") |
| ) || |
| left.id === "function" || |
| left.id === ":" || |
| ( |
| ( |
| left.identifier || |
| left.id === "(string)" || |
| left.id === "(number)" |
| ) && |
| ( |
| right.identifier || |
| right.id === "(string)" || |
| right.id === "(number)" |
| ) |
| ) || |
| (left.arity === "statement" && right.id !== ";") |
| ) { |
| one_space(); |
| } else if (left.arity === "unary" && left.id !== "`") { |
| no_space_only(); |
| } |
| } |
| } |
| nr_comments_skipped = 0; |
| delete left.calls; |
| delete left.dead; |
| delete left.free; |
| delete left.init; |
| delete left.open; |
| delete left.used; |
| left = right; |
| } |
| }); |
| } |
| |
| // The jslint function itself. |
| |
| return function (source, option_object, global_array) { |
| try { |
| warnings = []; |
| option = option_object || empty(); |
| anon = "anonymous"; |
| block_stack = []; |
| declared_globals = empty(); |
| directive_mode = true; |
| directives = []; |
| early_stop = true; |
| export_mode = false; |
| fudge = (option.fudge) |
| ? 1 |
| : 0; |
| functions = []; |
| global = { |
| id: "(global)", |
| body: true, |
| context: empty(), |
| from: 0, |
| level: 0, |
| line: 0, |
| live: [], |
| loop: 0, |
| switch: 0, |
| thru: 0 |
| }; |
| blockage = global; |
| functionage = global; |
| imports = []; |
| json_mode = false; |
| mega_mode = false; |
| module_mode = false; |
| next_token = global; |
| property = empty(); |
| stack = []; |
| tenure = undefined; |
| token = global; |
| token_nr = 0; |
| var_mode = undefined; |
| populate(declared_globals, standard, false); |
| if (global_array !== undefined) { |
| populate(declared_globals, global_array, false); |
| } |
| Object.keys(option).forEach(function (name) { |
| if (option[name] === true) { |
| var allowed = allowed_option[name]; |
| if (Array.isArray(allowed)) { |
| populate(declared_globals, allowed, false); |
| } |
| } |
| }); |
| tokenize(source); |
| advance(); |
| if (tokens[0].id === "{" || tokens[0].id === "[") { |
| json_mode = true; |
| tree = json_value(); |
| advance("(end)"); |
| } else { |
| |
| // Because browsers encourage combining of script files, the first token might |
| // be a semicolon to defend against a missing semicolon in the preceding file. |
| |
| if (option.browser) { |
| if (next_token.id === ";") { |
| advance(";"); |
| } |
| } else { |
| |
| // If we are not in a browser, then the file form of strict pragma may be used. |
| |
| if ( |
| next_token.value === "use strict" |
| ) { |
| global.strict = next_token; |
| advance("(string)"); |
| advance(";"); |
| } |
| } |
| tree = statements(); |
| advance("(end)"); |
| functionage = global; |
| walk_statement(tree); |
| if (module_mode && global.strict !== undefined) { |
| warn("unexpected_a", global.strict); |
| } |
| uninitialized_and_unused(); |
| if (!option.white) { |
| whitage(); |
| } |
| } |
| if (!option.browser) { |
| directives.forEach(function (comment) { |
| if (comment.directive === "global") { |
| warn("missing_browser", comment); |
| } |
| }); |
| } |
| early_stop = false; |
| } catch (e) { |
| if (e.name !== "JSLintError") { |
| warnings.push(e); |
| } |
| } |
| return { |
| directives: directives, |
| edition: "2016-07-13", |
| functions: functions, |
| global: global, |
| id: "(JSLint)", |
| imports: imports, |
| json: json_mode, |
| lines: lines, |
| module: module_mode === true, |
| ok: warnings.length === 0 && !early_stop, |
| option: option, |
| property: property, |
| stop: early_stop, |
| tokens: tokens, |
| tree: tree, |
| warnings: warnings.sort(function (a, b) { |
| return a.line - b.line || a.column - b.column; |
| }) |
| }; |
| }; |
| }()); |
| |
| /*node module.exports = jslint;*/ |