/jslint.js

http://github.com/douglascrockford/JSLint · JavaScript · 4988 lines · 4235 code · 382 blank · 371 comment · 1009 complexity · f4a6727d4e59e18bbf8023a841c562e3 MD5 · raw file

Large files are truncated click here to view the full file

  1. // jslint.js
  2. // 2020-03-28
  3. // Copyright (c) 2015 Douglas Crockford (www.JSLint.com)
  4. // Permission is hereby granted, free of charge, to any person obtaining a copy
  5. // of this software and associated documentation files (the "Software"), to deal
  6. // in the Software without restriction, including without limitation the rights
  7. // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
  8. // copies of the Software, and to permit persons to whom the Software is
  9. // furnished to do so, subject to the following conditions:
  10. // The above copyright notice and this permission notice shall be included in
  11. // all copies or substantial portions of the Software.
  12. // The Software shall be used for Good, not Evil.
  13. // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
  14. // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
  15. // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
  16. // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
  17. // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
  18. // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
  19. // SOFTWARE.
  20. // jslint(source, option_object, global_array) is a function that takes 3
  21. // arguments. The second two arguments are optional.
  22. // source A text to analyze, a string or an array of strings.
  23. // option_object An object whose keys correspond to option names.
  24. // global_array An array of strings containing global variables that
  25. // the file is allowed readonly access.
  26. // jslint returns an object containing its results. The object contains a lot
  27. // of valuable information. It can be used to generate reports. The object
  28. // contains:
  29. // directives: an array of directive comment tokens.
  30. // edition: the version of JSLint that did the analysis.
  31. // exports: the names exported from the module.
  32. // froms: an array of strings representing each of the imports.
  33. // functions: an array of objects that represent all of the functions
  34. // declared in the file.
  35. // global: an object representing the global object. Its .context property
  36. // is an object containing a property for each global variable.
  37. // id: "(JSLint)"
  38. // json: true if the file is a JSON text.
  39. // lines: an array of strings, the source.
  40. // module: true if an import or export statement was used.
  41. // ok: true if no warnings were generated. This is what you want.
  42. // option: the option argument.
  43. // property: a property object.
  44. // stop: true if JSLint was unable to finish. You don't want this.
  45. // tokens: an array of objects representing the tokens in the file.
  46. // tree: the token objects arranged in a tree.
  47. // warnings: an array of warning objects. A warning object can contain:
  48. // name: "JSLintError"
  49. // column: A column number in the file.
  50. // line: A line number in the file.
  51. // code: A warning code string.
  52. // message: The warning message string.
  53. // a: Exhibit A.
  54. // b: Exhibit B.
  55. // c: Exhibit C.
  56. // d: Exhibit D.
  57. // jslint works in several phases. In any of these phases, errors might be
  58. // found. Sometimes JSLint is able to recover from an error and continue
  59. // parsing. In some cases, it cannot and will stop early. If that should happen,
  60. // repair your code and try again.
  61. // Phases:
  62. // 1. If the source is a single string, split it into an array of strings.
  63. // 2. Turn the source into an array of tokens.
  64. // 3. Furcate the tokens into a parse tree.
  65. // 4. Walk the tree, traversing all of the nodes of the tree. It is a
  66. // recursive traversal. Each node may be processed on the way down
  67. // (preaction) and on the way up (postaction).
  68. // 5. Check the whitespace between the tokens.
  69. // jslint can also examine JSON text. It decides that a file is JSON text if
  70. // the first token is "[" or "{". Processing of JSON text is much simpler than
  71. // the processing of JavaScript programs. Only the first three phases are
  72. // required.
  73. // WARNING: JSLint will hurt your feelings.
  74. /*property
  75. a, and, arity, assign, b, bad_assignment_a, bad_directive_a, bad_get,
  76. bad_module_name_a, bad_option_a, bad_property_a, bad_set, bitwise, block,
  77. body, browser, c, calls, catch, charCodeAt, closer, closure, code, column,
  78. concat, constant, context, convert, couch, create, d, dead, default, devel,
  79. directive, directives, disrupt, dot, duplicate_a, edition, ellipsis, else,
  80. empty_block, escape_mega, eval, every, expected_a, expected_a_at_b_c,
  81. expected_a_b, expected_a_b_from_c_d, expected_a_before_b,
  82. expected_a_next_at_b, expected_digits_after_a, expected_four_digits,
  83. expected_identifier_a, expected_line_break_a_b, expected_regexp_factor_a,
  84. expected_space_a_b, expected_statements_a, expected_string_a,
  85. expected_type_string_a, exports, expression, extra, finally, flag, for,
  86. forEach, free, freeze, freeze_exports, from, froms, fud, fudge,
  87. function_in_loop, functions, g, getset, global, i, id, identifier, import,
  88. inc, indexOf, infix_in, init, initial, isArray, isNaN, join, json, keys,
  89. label, label_a, lbp, led, length, level, line, lines, live, long, loop, m,
  90. margin, match, message, misplaced_a, misplaced_directive_a, missing_browser,
  91. missing_m, module, naked_block, name, names, nested_comment, new, node,
  92. not_label_a, nr, nud, number_isNaN, ok, open, opening, option,
  93. out_of_scope_a, parameters, parent, pop, property, push, quote,
  94. redefinition_a_b, replace, required_a_optional_b, reserved_a, role, search,
  95. shebang, signature, single, slice, some, sort, split, startsWith, statement,
  96. stop, subscript_a, switch, test, this, thru, toString, todo_comment,
  97. tokens, too_long, too_many_digits, tree, try, type, u, unclosed_comment,
  98. unclosed_mega, unclosed_string, undeclared_a, unexpected_a,
  99. unexpected_a_after_b, unexpected_a_before_b, unexpected_at_top_level_a,
  100. unexpected_char_a, unexpected_comment, unexpected_directive_a,
  101. unexpected_expression_a, unexpected_label_a, unexpected_parens,
  102. unexpected_space_a_b, unexpected_statement_a, unexpected_trailing_space,
  103. unexpected_typeof_a, uninitialized_a, unreachable_a,
  104. unregistered_property_a, unsafe, unused_a, use_double, use_open, use_spaces,
  105. used, value, var_loop, var_switch, variable, warning, warnings,
  106. weird_condition_a, weird_expression_a, weird_loop, weird_relation_a, white,
  107. wrap_condition, wrap_immediate, wrap_parameter, wrap_regexp, wrap_unary,
  108. wrapped, writable, y
  109. */
  110. function empty() {
  111. // The empty function produces a new empty object that inherits nothing. This is
  112. // much better than '{}' because confusions around accidental method names like
  113. // 'constructor' are completely avoided.
  114. return Object.create(null);
  115. }
  116. function populate(array, object = empty(), value = true) {
  117. // Augment an object by taking property names from an array of strings.
  118. array.forEach(function (name) {
  119. object[name] = value;
  120. });
  121. return object;
  122. }
  123. const allowed_option = {
  124. // These are the options that are recognized in the option object or that may
  125. // appear in a /*jslint*/ directive. Most options will have a boolean value,
  126. // usually true. Some options will also predefine some number of global
  127. // variables.
  128. bitwise: true,
  129. browser: [
  130. "caches", "CharacterData", "clearInterval", "clearTimeout", "document",
  131. "DocumentType", "DOMException", "Element", "Event", "event", "fetch",
  132. "FileReader", "FontFace", "FormData", "history", "IntersectionObserver",
  133. "localStorage", "location", "MutationObserver", "name", "navigator",
  134. "screen", "sessionStorage", "setInterval", "setTimeout", "Storage",
  135. "TextDecoder", "TextEncoder", "URL", "window", "Worker",
  136. "XMLHttpRequest"
  137. ],
  138. couch: [
  139. "emit", "getRow", "isArray", "log", "provides", "registerType",
  140. "require", "send", "start", "sum", "toJSON"
  141. ],
  142. convert: true,
  143. devel: [
  144. "alert", "confirm", "console", "prompt"
  145. ],
  146. eval: true,
  147. for: true,
  148. fudge: true,
  149. getset: true,
  150. long: true,
  151. node: [
  152. "Buffer", "clearImmediate", "clearInterval", "clearTimeout",
  153. "console", "exports", "module", "process", "require",
  154. "setImmediate", "setInterval", "setTimeout", "TextDecoder",
  155. "TextEncoder", "URL", "URLSearchParams", "__dirname", "__filename"
  156. ],
  157. single: true,
  158. this: true,
  159. white: true
  160. };
  161. const anticondition = populate([
  162. "?", "~", "&", "|", "^", "<<", ">>", ">>>", "+", "-", "*", "/", "%",
  163. "typeof", "(number)", "(string)"
  164. ]);
  165. // These are the bitwise operators.
  166. const bitwiseop = populate([
  167. "~", "^", "^=", "&", "&=", "|", "|=", "<<", "<<=", ">>", ">>=",
  168. ">>>", ">>>="
  169. ]);
  170. const escapeable = populate([
  171. "\\", "/", "`", "b", "f", "n", "r", "t"
  172. ]);
  173. const opener = {
  174. // The open and close pairs.
  175. "(": ")", // paren
  176. "[": "]", // bracket
  177. "{": "}", // brace
  178. "${": "}" // mega
  179. };
  180. // The relational operators.
  181. const relationop = populate([
  182. "!=", "!==", "==", "===", "<", "<=", ">", ">="
  183. ]);
  184. // This is the set of infix operators that require a space on each side.
  185. const spaceop = populate([
  186. "!=", "!==", "%", "%=", "&", "&=", "&&", "*", "*=", "+=", "-=", "/",
  187. "/=", "<", "<=", "<<", "<<=", "=", "==", "===", "=>", ">", ">=",
  188. ">>", ">>=", ">>>", ">>>=", "^", "^=", "|", "|=", "||"
  189. ]);
  190. const standard = [
  191. // These are the globals that are provided by the language standard.
  192. "Array", "ArrayBuffer", "Boolean", "DataView", "Date", "decodeURI",
  193. "decodeURIComponent", "encodeURI", "encodeURIComponent", "Error",
  194. "EvalError", "Float32Array", "Float64Array", "Generator",
  195. "GeneratorFunction", "Int8Array", "Int16Array", "Int32Array", "Intl",
  196. "JSON", "Map", "Math", "Number", "Object", "parseInt", "parseFloat",
  197. "Promise", "Proxy", "RangeError", "ReferenceError", "Reflect", "RegExp",
  198. "Set", "String", "Symbol", "SyntaxError", "System", "TypeError",
  199. "Uint8Array", "Uint8ClampedArray", "Uint16Array", "Uint32Array",
  200. "URIError", "WeakMap", "WeakSet"
  201. ];
  202. const bundle = {
  203. // The bundle contains the raw text messages that are generated by jslint. It
  204. // seems that they are all error messages and warnings. There are no "Atta
  205. // boy!" or "You are so awesome!" messages. There is no positive reinforcement
  206. // or encouragement. This relentless negativity can undermine self-esteem and
  207. // wound the inner child. But if you accept it as sound advice rather than as
  208. // personal criticism, it can make your programs better.
  209. and: "The '&&' subexpression should be wrapped in parens.",
  210. bad_assignment_a: "Bad assignment to '{a}'.",
  211. bad_directive_a: "Bad directive '{a}'.",
  212. bad_get: "A get function takes no parameters.",
  213. bad_module_name_a: "Bad module name '{a}'.",
  214. bad_option_a: "Bad option '{a}'.",
  215. bad_property_a: "Bad property name '{a}'.",
  216. bad_set: "A set function takes one parameter.",
  217. duplicate_a: "Duplicate '{a}'.",
  218. empty_block: "Empty block.",
  219. escape_mega: "Unexpected escapement in mega literal.",
  220. expected_a: "Expected '{a}'.",
  221. expected_a_at_b_c: "Expected '{a}' at column {b}, not column {c}.",
  222. expected_a_b: "Expected '{a}' and instead saw '{b}'.",
  223. expected_a_b_from_c_d: (
  224. "Expected '{a}' to match '{b}' from line {c} and instead saw '{d}'."
  225. ),
  226. expected_a_before_b: "Expected '{a}' before '{b}'.",
  227. expected_a_next_at_b: "Expected '{a}' at column {b} on the next line.",
  228. expected_digits_after_a: "Expected digits after '{a}'.",
  229. expected_four_digits: "Expected four digits after '\\u'.",
  230. expected_identifier_a: "Expected an identifier and instead saw '{a}'.",
  231. expected_line_break_a_b: "Expected a line break between '{a}' and '{b}'.",
  232. expected_regexp_factor_a: "Expected a regexp factor and instead saw '{a}'.",
  233. expected_space_a_b: "Expected one space between '{a}' and '{b}'.",
  234. expected_statements_a: "Expected statements before '{a}'.",
  235. expected_string_a: "Expected a string and instead saw '{a}'.",
  236. expected_type_string_a: "Expected a type string and instead saw '{a}'.",
  237. freeze_exports: (
  238. "Expected 'Object.freeze('. All export values should be frozen."
  239. ),
  240. function_in_loop: "Don't make functions within a loop.",
  241. infix_in: (
  242. "Unexpected 'in'. Compare with undefined, "
  243. + "or use the hasOwnProperty method instead."
  244. ),
  245. label_a: "'{a}' is a statement label.",
  246. misplaced_a: "Place '{a}' at the outermost level.",
  247. misplaced_directive_a: (
  248. "Place the '/*{a}*/' directive before the first statement."
  249. ),
  250. missing_browser: "/*global*/ requires the Assume a browser option.",
  251. missing_m: "Expected 'm' flag on a multiline regular expression.",
  252. naked_block: "Naked block.",
  253. nested_comment: "Nested comment.",
  254. not_label_a: "'{a}' is not a label.",
  255. number_isNaN: "Use Number.isNaN function to compare with NaN.",
  256. out_of_scope_a: "'{a}' is out of scope.",
  257. redefinition_a_b: "Redefinition of '{a}' from line {b}.",
  258. required_a_optional_b: (
  259. "Required parameter '{a}' after optional parameter '{b}'."
  260. ),
  261. reserved_a: "Reserved name '{a}'.",
  262. subscript_a: "['{a}'] is better written in dot notation.",
  263. todo_comment: "Unexpected TODO comment.",
  264. too_long: "Line is longer than 80 characters.",
  265. too_many_digits: "Too many digits.",
  266. unclosed_comment: "Unclosed comment.",
  267. unclosed_mega: "Unclosed mega literal.",
  268. unclosed_string: "Unclosed string.",
  269. undeclared_a: "Undeclared '{a}'.",
  270. unexpected_a: "Unexpected '{a}'.",
  271. unexpected_a_after_b: "Unexpected '{a}' after '{b}'.",
  272. unexpected_a_before_b: "Unexpected '{a}' before '{b}'.",
  273. unexpected_at_top_level_a: "Expected '{a}' to be in a function.",
  274. unexpected_char_a: "Unexpected character '{a}'.",
  275. unexpected_comment: "Unexpected comment.",
  276. unexpected_directive_a: "When using modules, don't use directive '/*{a}'.",
  277. unexpected_expression_a: (
  278. "Unexpected expression '{a}' in statement position."
  279. ),
  280. unexpected_label_a: "Unexpected label '{a}'.",
  281. unexpected_parens: "Don't wrap function literals in parens.",
  282. unexpected_space_a_b: "Unexpected space between '{a}' and '{b}'.",
  283. unexpected_statement_a: (
  284. "Unexpected statement '{a}' in expression position."
  285. ),
  286. unexpected_trailing_space: "Unexpected trailing space.",
  287. unexpected_typeof_a: (
  288. "Unexpected 'typeof'. Use '===' to compare directly with {a}."
  289. ),
  290. uninitialized_a: "Uninitialized '{a}'.",
  291. unreachable_a: "Unreachable '{a}'.",
  292. unregistered_property_a: "Unregistered property name '{a}'.",
  293. unsafe: "Unsafe character '{a}'.",
  294. unused_a: "Unused '{a}'.",
  295. use_double: "Use double quotes, not single quotes.",
  296. use_open: (
  297. "Wrap a ternary expression in parens, "
  298. + "with a line break after the left paren."
  299. ),
  300. use_spaces: "Use spaces, not tabs.",
  301. var_loop: "Don't declare variables in a loop.",
  302. var_switch: "Don't declare variables in a switch.",
  303. weird_condition_a: "Weird condition '{a}'.",
  304. weird_expression_a: "Weird expression '{a}'.",
  305. weird_loop: "Weird loop.",
  306. weird_relation_a: "Weird relation '{a}'.",
  307. wrap_condition: "Wrap the condition in parens.",
  308. wrap_immediate: (
  309. "Wrap an immediate function invocation in parentheses to assist "
  310. + "the reader in understanding that the expression is the result "
  311. + "of a function, and not the function itself."
  312. ),
  313. wrap_parameter: "Wrap the parameter in parens.",
  314. wrap_regexp: "Wrap this regexp in parens to avoid confusion.",
  315. wrap_unary: "Wrap the unary expression in parens."
  316. };
  317. // Regular expression literals:
  318. // supplant {variables}
  319. const rx_supplant = /\{([^{}]*)\}/g;
  320. // carriage return, carriage return linefeed, or linefeed
  321. const rx_crlf = /\n|\r\n?/;
  322. // unsafe characters that are silently deleted by one or more browsers
  323. const rx_unsafe = /[\u0000-\u001f\u007f-\u009f\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/;
  324. // identifier
  325. const rx_identifier = /^([a-zA-Z_$][a-zA-Z0-9_$]*)$/;
  326. const rx_module = /^[a-zA-Z0-9_$:.@\-\/]+$/;
  327. const rx_bad_property = /^_|\$|Sync\$|_$/;
  328. // star slash
  329. const rx_star_slash = /\*\//;
  330. // slash star
  331. const rx_slash_star = /\/\*/;
  332. // slash star or ending slash
  333. const rx_slash_star_or_slash = /\/\*|\/$/;
  334. // uncompleted work comment
  335. const rx_todo = /\b(?:todo|TO\s?DO|HACK)\b/;
  336. // tab
  337. const rx_tab = /\t/g;
  338. // directive
  339. const rx_directive = /^(jslint|property|global)\s+(.*)$/;
  340. const rx_directive_part = /^([a-zA-Z$_][a-zA-Z0-9$_]*)(?::\s*(true|false))?,?\s*(.*)$/;
  341. // token (sorry it is so long)
  342. const rx_token = /^((\s+)|([a-zA-Z_$][a-zA-Z0-9_$]*)|[(){}\[\],:;'"~`]|\?\.?|=(?:==?|>)?|\.+|[*\/][*\/=]?|\+[=+]?|-[=\-]?|[\^%]=?|&[&=]?|\|[|=]?|>{1,3}=?|<<?=?|!(?:!|==?)?|(0|[1-9][0-9]*))(.*)$/;
  343. const rx_digits = /^([0-9]+)(.*)$/;
  344. const rx_hexs = /^([0-9a-fA-F]+)(.*)$/;
  345. const rx_octals = /^([0-7]+)(.*)$/;
  346. const rx_bits = /^([01]+)(.*)$/;
  347. // mega
  348. const rx_mega = /[`\\]|\$\{/;
  349. // JSON number
  350. const rx_JSON_number = /^-?\d+(?:\.\d*)?(?:e[\-+]?\d+)?$/i;
  351. // initial cap
  352. const rx_cap = /^[A-Z]/;
  353. function is_letter(string) {
  354. return (
  355. (string >= "a" && string <= "z\uffff")
  356. || (string >= "A" && string <= "Z\uffff")
  357. );
  358. }
  359. function supplant(string, object) {
  360. return string.replace(rx_supplant, function (found, filling) {
  361. const replacement = object[filling];
  362. return (
  363. replacement !== undefined
  364. ? replacement
  365. : found
  366. );
  367. });
  368. }
  369. let anon; // The guessed name for anonymous functions.
  370. let blockage; // The current block.
  371. let block_stack; // The stack of blocks.
  372. let declared_globals; // The object containing the global declarations.
  373. let directives; // The directive comments.
  374. let directive_mode; // true if directives are still allowed.
  375. let early_stop; // true if JSLint cannot finish.
  376. let exports; // The exported names and values.
  377. let froms; // The array collecting all import-from strings.
  378. let fudge; // true if the natural numbers start with 1.
  379. let functionage; // The current function.
  380. let functions; // The array containing all of the functions.
  381. let global; // The global object; the outermost context.
  382. let json_mode; // true if parsing JSON.
  383. let lines; // The array containing source lines.
  384. let mega_mode; // true if currently parsing a megastring literal.
  385. let module_mode; // true if import or export was used.
  386. let next_token; // The next token to be examined in the parse.
  387. let option; // The options parameter.
  388. let property; // The object containing the tallied property names.
  389. let shebang; // true if a #! was seen on the first line.
  390. let stack; // The stack of functions.
  391. let syntax; // The object containing the parser.
  392. let token; // The current token being examined in the parse.
  393. let token_nr; // The number of the next token.
  394. let tokens; // The array of tokens.
  395. let tenure; // The predefined property registry.
  396. let tree; // The abstract parse tree.
  397. let var_mode; // "var" if using var; "let" if using let.
  398. let warnings; // The array collecting all generated warnings.
  399. // Error reportage functions:
  400. function artifact(the_token) {
  401. // Return a string representing an artifact.
  402. if (the_token === undefined) {
  403. the_token = next_token;
  404. }
  405. return (
  406. (the_token.id === "(string)" || the_token.id === "(number)")
  407. ? String(the_token.value)
  408. : the_token.id
  409. );
  410. }
  411. function artifact_line(the_token) {
  412. // Return the fudged line number of an artifact.
  413. if (the_token === undefined) {
  414. the_token = next_token;
  415. }
  416. return the_token.line + fudge;
  417. }
  418. function artifact_column(the_token) {
  419. // Return the fudged column number of an artifact.
  420. if (the_token === undefined) {
  421. the_token = next_token;
  422. }
  423. return the_token.from + fudge;
  424. }
  425. function warn_at(code, line, column, a, b, c, d) {
  426. // Report an error at some line and column of the program. The warning object
  427. // resembles an exception.
  428. const warning = { // ~~
  429. name: "JSLintError",
  430. column,
  431. line,
  432. code
  433. };
  434. if (a !== undefined) {
  435. warning.a = a;
  436. }
  437. if (b !== undefined) {
  438. warning.b = b;
  439. }
  440. if (c !== undefined) {
  441. warning.c = c;
  442. }
  443. if (d !== undefined) {
  444. warning.d = d;
  445. }
  446. warning.message = supplant(bundle[code] || code, warning);
  447. warnings.push(warning);
  448. return warning;
  449. }
  450. function stop_at(code, line, column, a, b, c, d) {
  451. // Same as warn_at, except that it stops the analysis.
  452. throw warn_at(code, line, column, a, b, c, d);
  453. }
  454. function warn(code, the_token, a, b, c, d) {
  455. // Same as warn_at, except the warning will be associated with a specific token.
  456. // If there is already a warning on this token, suppress the new one. It is
  457. // likely that the first warning will be the most meaningful.
  458. if (the_token === undefined) {
  459. the_token = next_token;
  460. }
  461. if (the_token.warning === undefined) {
  462. the_token.warning = warn_at(
  463. code,
  464. the_token.line,
  465. the_token.from,
  466. a || artifact(the_token),
  467. b,
  468. c,
  469. d
  470. );
  471. return the_token.warning;
  472. }
  473. }
  474. function stop(code, the_token, a, b, c, d) {
  475. // Similar to warn and stop_at. If the token already had a warning, that
  476. // warning will be replaced with this new one. It is likely that the stopping
  477. // warning will be the more meaningful.
  478. if (the_token === undefined) {
  479. the_token = next_token;
  480. }
  481. delete the_token.warning;
  482. throw warn(code, the_token, a, b, c, d);
  483. }
  484. // Tokenize:
  485. function tokenize(source) {
  486. // tokenize takes a source and produces from it an array of token objects.
  487. // JavaScript is notoriously difficult to tokenize because of the horrible
  488. // interactions between automatic semicolon insertion, regular expression
  489. // literals, and now megastring literals. JSLint benefits from eliminating
  490. // automatic semicolon insertion and nested megastring literals, which allows
  491. // full tokenization to precede parsing.
  492. // If the source is not an array, then it is split into lines at the
  493. // carriage return/linefeed.
  494. lines = (
  495. Array.isArray(source)
  496. ? source
  497. : source.split(rx_crlf)
  498. );
  499. tokens = [];
  500. let char; // a popular character
  501. let column = 0; // the column number of the next character
  502. let first; // the first token
  503. let from; // the starting column number of the token
  504. let line = -1; // the line number of the next character
  505. let nr = 0; // the next token number
  506. let previous = global; // the previous token including comments
  507. let prior = global; // the previous token excluding comments
  508. let mega_from; // the starting column of megastring
  509. let mega_line; // the starting line of megastring
  510. let regexp_seen; // regular expression literal seen on this line
  511. let snippet; // a piece of string
  512. let source_line = ""; // the remaining line source string
  513. let whole_line = ""; // the whole line source string
  514. if (lines[0].startsWith("#!")) {
  515. line = 0;
  516. shebang = true;
  517. }
  518. function next_line() {
  519. // Put the next line of source in source_line. If the line contains tabs,
  520. // replace them with spaces and give a warning. Also warn if the line contains
  521. // unsafe characters or is too damn long.
  522. let at;
  523. if (
  524. !option.long
  525. && whole_line.length > 80
  526. && !json_mode
  527. && first
  528. && !regexp_seen
  529. ) {
  530. warn_at("too_long", line, 80);
  531. }
  532. column = 0;
  533. line += 1;
  534. regexp_seen = false;
  535. source_line = lines[line];
  536. whole_line = source_line || "";
  537. if (source_line !== undefined) {
  538. at = source_line.search(rx_tab);
  539. if (at >= 0) {
  540. if (!option.white) {
  541. warn_at("use_spaces", line, at + 1);
  542. }
  543. source_line = source_line.replace(rx_tab, " ");
  544. }
  545. at = source_line.search(rx_unsafe);
  546. if (at >= 0) {
  547. warn_at(
  548. "unsafe",
  549. line,
  550. column + at,
  551. "U+" + source_line.charCodeAt(at).toString(16)
  552. );
  553. }
  554. if (!option.white && source_line.slice(-1) === " ") {
  555. warn_at(
  556. "unexpected_trailing_space",
  557. line,
  558. source_line.length - 1
  559. );
  560. }
  561. }
  562. return source_line;
  563. }
  564. // Most tokens, including the identifiers, operators, and punctuators, can be
  565. // found with a regular expression. Regular expressions cannot correctly match
  566. // regular expression literals, so we will match those the hard way. String
  567. // literals and number literals can be matched by regular expressions, but they
  568. // don't provide good warnings. The functions snip, next_char, prev_char,
  569. // some_digits, and escape help in the parsing of literals.
  570. function snip() {
  571. // Remove the last character from snippet.
  572. snippet = snippet.slice(0, -1);
  573. }
  574. function next_char(match) {
  575. // Get the next character from the source line. Remove it from the source_line,
  576. // and append it to the snippet. Optionally check that the previous character
  577. // matched an expected value.
  578. if (match !== undefined && char !== match) {
  579. return stop_at(
  580. (
  581. char === ""
  582. ? "expected_a"
  583. : "expected_a_b"
  584. ),
  585. line,
  586. column - 1,
  587. match,
  588. char
  589. );
  590. }
  591. if (source_line) {
  592. char = source_line[0];
  593. source_line = source_line.slice(1);
  594. snippet += char;
  595. } else {
  596. char = "";
  597. snippet += " ";
  598. }
  599. column += 1;
  600. return char;
  601. }
  602. function back_char() {
  603. // Back up one character by moving a character from the end of the snippet to
  604. // the front of the source_line.
  605. if (snippet) {
  606. char = snippet.slice(-1);
  607. source_line = char + source_line;
  608. column -= 1;
  609. snip();
  610. } else {
  611. char = "";
  612. }
  613. return char;
  614. }
  615. function some_digits(rx, quiet) {
  616. const result = source_line.match(rx);
  617. if (result) {
  618. char = result[1];
  619. column += char.length;
  620. source_line = result[2];
  621. snippet += char;
  622. } else {
  623. char = "";
  624. if (!quiet) {
  625. warn_at(
  626. "expected_digits_after_a",
  627. line,
  628. column,
  629. snippet
  630. );
  631. }
  632. }
  633. return char.length;
  634. }
  635. function escape(extra) {
  636. next_char("\\");
  637. if (escapeable[char] === true) {
  638. return next_char();
  639. }
  640. if (char === "") {
  641. return stop_at("unclosed_string", line, column);
  642. }
  643. if (char === "u") {
  644. if (next_char("u") === "{") {
  645. if (json_mode) {
  646. warn_at("unexpected_a", line, column - 1, char);
  647. }
  648. if (some_digits(rx_hexs) > 5) {
  649. warn_at("too_many_digits", line, column - 1);
  650. }
  651. if (next_char() !== "}") {
  652. stop_at("expected_a_before_b", line, column, "}", char);
  653. }
  654. return next_char();
  655. }
  656. back_char();
  657. if (some_digits(rx_hexs, true) < 4) {
  658. warn_at("expected_four_digits", line, column - 1);
  659. }
  660. return;
  661. }
  662. if (extra && extra.indexOf(char) >= 0) {
  663. return next_char();
  664. }
  665. warn_at("unexpected_a_before_b", line, column - 2, "\\", char);
  666. }
  667. function make(id, value, identifier) {
  668. // Make the token object and append it to the tokens list.
  669. const the_token = {
  670. from,
  671. id,
  672. identifier: Boolean(identifier),
  673. line,
  674. nr,
  675. thru: column
  676. };
  677. tokens[nr] = the_token;
  678. nr += 1;
  679. // Directives must appear before the first statement.
  680. if (id !== "(comment)" && id !== ";") {
  681. directive_mode = false;
  682. }
  683. // If the token is to have a value, give it one.
  684. if (value !== undefined) {
  685. the_token.value = value;
  686. }
  687. // If this token is an identifier that touches a preceding number, or
  688. // a "/", comment, or regular expression literal that touches a preceding
  689. // comment or regular expression literal, then give a missing space warning.
  690. // This warning is not suppressed by option.white.
  691. if (
  692. previous.line === line
  693. && previous.thru === from
  694. && (id === "(comment)" || id === "(regexp)" || id === "/")
  695. && (previous.id === "(comment)" || previous.id === "(regexp)")
  696. ) {
  697. warn(
  698. "expected_space_a_b",
  699. the_token,
  700. artifact(previous),
  701. artifact(the_token)
  702. );
  703. }
  704. if (previous.id === "." && id === "(number)") {
  705. warn("expected_a_before_b", previous, "0", ".");
  706. }
  707. if (prior.id === "." && the_token.identifier) {
  708. the_token.dot = true;
  709. }
  710. // The previous token is used to detect adjacency problems.
  711. previous = the_token;
  712. // The prior token is a previous token that was not a comment. The prior token
  713. // is used to disambiguate "/", which can mean division or regular expression
  714. // literal.
  715. if (previous.id !== "(comment)") {
  716. prior = previous;
  717. }
  718. return the_token;
  719. }
  720. function parse_directive(the_comment, body) {
  721. // JSLint recognizes three directives that can be encoded in comments. This
  722. // function processes one item, and calls itself recursively to process the
  723. // next one.
  724. const result = body.match(rx_directive_part);
  725. if (result) {
  726. let allowed;
  727. const name = result[1];
  728. const value = result[2];
  729. if (the_comment.directive === "jslint") {
  730. allowed = allowed_option[name];
  731. if (
  732. typeof allowed === "boolean"
  733. || typeof allowed === "object"
  734. ) {
  735. if (
  736. value === ""
  737. || value === "true"
  738. || value === undefined
  739. ) {
  740. option[name] = true;
  741. if (Array.isArray(allowed)) {
  742. populate(allowed, declared_globals, false);
  743. }
  744. } else if (value === "false") {
  745. option[name] = false;
  746. } else {
  747. warn("bad_option_a", the_comment, name + ":" + value);
  748. }
  749. } else {
  750. warn("bad_option_a", the_comment, name);
  751. }
  752. } else if (the_comment.directive === "property") {
  753. if (tenure === undefined) {
  754. tenure = empty();
  755. }
  756. tenure[name] = true;
  757. } else if (the_comment.directive === "global") {
  758. if (value) {
  759. warn("bad_option_a", the_comment, name + ":" + value);
  760. }
  761. declared_globals[name] = false;
  762. module_mode = the_comment;
  763. }
  764. return parse_directive(the_comment, result[3]);
  765. }
  766. if (body) {
  767. return stop("bad_directive_a", the_comment, body);
  768. }
  769. }
  770. function comment(snippet) {
  771. // Make a comment object. Comments are not allowed in JSON text. Comments can
  772. // include directives and notices of incompletion.
  773. const the_comment = make("(comment)", snippet);
  774. if (Array.isArray(snippet)) {
  775. snippet = snippet.join(" ");
  776. }
  777. if (!option.devel && rx_todo.test(snippet)) {
  778. warn("todo_comment", the_comment);
  779. }
  780. const result = snippet.match(rx_directive);
  781. if (result) {
  782. if (!directive_mode) {
  783. warn_at("misplaced_directive_a", line, from, result[1]);
  784. } else {
  785. the_comment.directive = result[1];
  786. parse_directive(the_comment, result[2]);
  787. }
  788. directives.push(the_comment);
  789. }
  790. return the_comment;
  791. }
  792. function regexp() {
  793. // Parse a regular expression literal.
  794. let multi_mode = false;
  795. let result;
  796. let value;
  797. regexp_seen = true;
  798. function quantifier() {
  799. // Match an optional quantifier.
  800. if (char === "?" || char === "*" || char === "+") {
  801. next_char();
  802. } else if (char === "{") {
  803. if (some_digits(rx_digits, true) === 0) {
  804. warn_at("expected_a", line, column, "0");
  805. }
  806. if (next_char() === ",") {
  807. some_digits(rx_digits, true);
  808. next_char();
  809. }
  810. next_char("}");
  811. } else {
  812. return;
  813. }
  814. if (char === "?") {
  815. next_char("?");
  816. }
  817. }
  818. function subklass() {
  819. // Match a character in a character class.
  820. if (char === "\\") {
  821. escape("BbDdSsWw-[]^");
  822. return true;
  823. }
  824. if (
  825. char === ""
  826. || char === "["
  827. || char === "]"
  828. || char === "/"
  829. || char === "^"
  830. || char === "-"
  831. ) {
  832. return false;
  833. }
  834. if (char === " ") {
  835. warn_at("expected_a_b", line, column, "\\u0020", " ");
  836. } else if (char === "`" && mega_mode) {
  837. warn_at("unexpected_a", line, column, "`");
  838. }
  839. next_char();
  840. return true;
  841. }
  842. function ranges() {
  843. // Match a range of subclasses.
  844. if (subklass()) {
  845. if (char === "-") {
  846. next_char("-");
  847. if (!subklass()) {
  848. return stop_at(
  849. "unexpected_a",
  850. line,
  851. column - 1,
  852. "-"
  853. );
  854. }
  855. }
  856. return ranges();
  857. }
  858. }
  859. function klass() {
  860. // Match a class.
  861. next_char("[");
  862. if (char === "^") {
  863. next_char("^");
  864. }
  865. (function classy() {
  866. ranges();
  867. if (char !== "]" && char !== "") {
  868. warn_at(
  869. "expected_a_before_b",
  870. line,
  871. column,
  872. "\\",
  873. char
  874. );
  875. next_char();
  876. return classy();
  877. }
  878. }());
  879. next_char("]");
  880. }
  881. function choice() {
  882. function group() {
  883. // Match a group that starts with left paren.
  884. next_char("(");
  885. if (char === "?") {
  886. next_char("?");
  887. if (char === "=" || char === "!") {
  888. next_char();
  889. } else {
  890. next_char(":");
  891. }
  892. } else if (char === ":") {
  893. warn_at("expected_a_before_b", line, column, "?", ":");
  894. }
  895. choice();
  896. next_char(")");
  897. }
  898. function factor() {
  899. if (
  900. char === ""
  901. || char === "/"
  902. || char === "]"
  903. || char === ")"
  904. ) {
  905. return false;
  906. }
  907. if (char === "(") {
  908. group();
  909. return true;
  910. }
  911. if (char === "[") {
  912. klass();
  913. return true;
  914. }
  915. if (char === "\\") {
  916. escape("BbDdSsWw^${}[]():=!.|*+?");
  917. return true;
  918. }
  919. if (
  920. char === "?"
  921. || char === "+"
  922. || char === "*"
  923. || char === "}"
  924. || char === "{"
  925. ) {
  926. warn_at(
  927. "expected_a_before_b",
  928. line,
  929. column - 1,
  930. "\\",
  931. char
  932. );
  933. } else if (char === "`") {
  934. if (mega_mode) {
  935. warn_at("unexpected_a", line, column - 1, "`");
  936. }
  937. } else if (char === " ") {
  938. warn_at(
  939. "expected_a_b",
  940. line,
  941. column - 1,
  942. "\\s",
  943. " "
  944. );
  945. } else if (char === "$") {
  946. if (source_line[0] !== "/") {
  947. multi_mode = true;
  948. }
  949. } else if (char === "^") {
  950. if (snippet !== "^") {
  951. multi_mode = true;
  952. }
  953. }
  954. next_char();
  955. return true;
  956. }
  957. function sequence(follow) {
  958. if (factor()) {
  959. quantifier();
  960. return sequence(true);
  961. }
  962. if (!follow) {
  963. warn_at("expected_regexp_factor_a", line, column, char);
  964. }
  965. }
  966. // Match a choice (a sequence that can be followed by | and another choice).
  967. sequence();
  968. if (char === "|") {
  969. next_char("|");
  970. return choice();
  971. }
  972. }
  973. // Scan the regexp literal. Give a warning if the first character is = because
  974. // /= looks like a division assignment operator.
  975. snippet = "";
  976. next_char();
  977. if (char === "=") {
  978. warn_at("expected_a_before_b", line, column, "\\", "=");
  979. }
  980. choice();
  981. // Make sure there is a closing slash.
  982. snip();
  983. value = snippet;
  984. next_char("/");
  985. // Process dangling flag letters.
  986. const allowed = {
  987. g: true,
  988. i: true,
  989. m: true,
  990. u: true,
  991. y: true
  992. };
  993. const flag = empty();
  994. (function make_flag() {
  995. if (is_letter(char)) {
  996. if (allowed[char] !== true) {
  997. warn_at("unexpected_a", line, column, char);
  998. }
  999. allowed[char] = false;
  1000. flag[char] = true;
  1001. next_char();
  1002. return make_flag();
  1003. }
  1004. }());
  1005. back_char();
  1006. if (char === "/" || char === "*") {
  1007. return stop_at("unexpected_a", line, from, char);
  1008. }
  1009. result = make("(regexp)", char);
  1010. result.flag = flag;
  1011. result.value = value;
  1012. if (multi_mode && !flag.m) {
  1013. warn_at("missing_m", line, column);
  1014. }
  1015. return result;
  1016. }
  1017. function string(quote) {
  1018. // Make a string token.
  1019. let the_token;
  1020. snippet = "";
  1021. next_char();
  1022. return (function next() {
  1023. if (char === quote) {
  1024. snip();
  1025. the_token = make("(string)", snippet);
  1026. the_token.quote = quote;
  1027. return the_token;
  1028. }
  1029. if (char === "") {
  1030. return stop_at("unclosed_string", line, column);
  1031. }
  1032. if (char === "\\") {
  1033. escape(quote);
  1034. } else if (char === "`") {
  1035. if (mega_mode) {
  1036. warn_at("unexpected_a", line, column, "`");
  1037. }
  1038. next_char("`");
  1039. } else {
  1040. next_char();
  1041. }
  1042. return next();
  1043. }());
  1044. }
  1045. function frack() {
  1046. if (char === ".") {
  1047. some_digits(rx_digits);
  1048. next_char();
  1049. }
  1050. if (char === "E" || char === "e") {
  1051. next_char();
  1052. if (char !== "+" && char !== "-") {
  1053. back_char();
  1054. }
  1055. some_digits(rx_digits);
  1056. next_char();
  1057. }
  1058. }
  1059. function number() {
  1060. if (snippet === "0") {
  1061. next_char();
  1062. if (char === ".") {
  1063. frack();
  1064. } else if (char === "b") {
  1065. some_digits(rx_bits);
  1066. next_char();
  1067. } else if (char === "o") {
  1068. some_digits(rx_octals);
  1069. next_char();
  1070. } else if (char === "x") {
  1071. some_digits(rx_hexs);
  1072. next_char();
  1073. }
  1074. } else {
  1075. next_char();
  1076. frack();
  1077. }
  1078. // If the next character after a number is a digit or letter, then something
  1079. // unexpected is going on.
  1080. if (
  1081. (char >= "0" && char <= "9")
  1082. || (char >= "a" && char <= "z")
  1083. || (char >= "A" && char <= "Z")
  1084. ) {
  1085. return stop_at(
  1086. "unexpected_a_after_b",
  1087. line,
  1088. column - 1,
  1089. snippet.slice(-1),
  1090. snippet.slice(0, -1)
  1091. );
  1092. }
  1093. back_char();
  1094. return make("(number)", snippet);
  1095. }
  1096. function lex() {
  1097. let array;
  1098. let i = 0;
  1099. let j = 0;
  1100. let last;
  1101. let result;
  1102. let the_token;
  1103. // This should properly be a tail recursive function, but sadly, conformant
  1104. // implementations of ES6 are still rare. This is the ideal code:
  1105. // if (!source_line) {
  1106. // source_line = next_line();
  1107. // from = 0;
  1108. // return (
  1109. // source_line === undefined
  1110. // ? (
  1111. // mega_mode
  1112. // ? stop_at("unclosed_mega", mega_line, mega_from)
  1113. // : make("(end)")
  1114. // )
  1115. // : lex()
  1116. // );
  1117. // }
  1118. // Unfortunately, incompetent JavaScript engines will sometimes fail to execute
  1119. // it correctly. So for now, we do it the old fashioned way.
  1120. while (!source_line) {
  1121. source_line = next_line();
  1122. from = 0;
  1123. if (source_line === undefined) {
  1124. return (
  1125. mega_mode
  1126. ? stop_at("unclosed_mega", mega_line, mega_from)
  1127. : make("(end)")
  1128. );
  1129. }
  1130. }
  1131. from = column;
  1132. result = source_line.match(rx_token);
  1133. // result[1] token
  1134. // result[2] whitespace
  1135. // result[3] identifier
  1136. // result[4] number
  1137. // result[5] rest
  1138. if (!result) {
  1139. return stop_at(
  1140. "unexpected_char_a",
  1141. line,
  1142. column,
  1143. source_line[0]
  1144. );
  1145. }
  1146. snippet = result[1];
  1147. column += snippet.length;
  1148. source_line = result[5];
  1149. // Whitespace was matched. Call lex again to get more.
  1150. if (result[2]) {
  1151. return lex();
  1152. }
  1153. // The token is an identifier.
  1154. if (result[3]) {
  1155. return make(snippet, undefined, true);
  1156. }
  1157. // The token is a number.
  1158. if (result[4]) {
  1159. return number(snippet);
  1160. }
  1161. // The token is a string.
  1162. if (snippet === "\"") {
  1163. return string(snippet);
  1164. }
  1165. if (snippet === "'") {
  1166. if (!option.single) {
  1167. warn_at("use_double", line, column);
  1168. }
  1169. return string(snippet);
  1170. }
  1171. // The token is a megastring. We don't allow any kind of mega nesting.
  1172. if (snippet === "`") {
  1173. if (mega_mode) {
  1174. return stop_at("expected_a_b", line, column, "}", "`");
  1175. }
  1176. snippet = "";
  1177. mega_from = from;
  1178. mega_line = line;
  1179. mega_mode = true;
  1180. // Parsing a mega literal is tricky. First make a ` token.
  1181. make("`");
  1182. from += 1;
  1183. // Then loop, building up a string, possibly from many lines, until seeing
  1184. // the end of file, a closing `, or a ${ indicting an expression within the
  1185. // string.
  1186. (function part() {
  1187. const at = source_line.search(rx_mega);
  1188. // If neither ` nor ${ is seen, then the whole line joins the snippet.
  1189. if (at < 0) {
  1190. snippet += source_line + "\n";
  1191. return (
  1192. next_line() === undefined
  1193. ? stop_at("unclosed_mega", mega_line, mega_from)
  1194. : part()
  1195. );
  1196. }
  1197. // if either ` or ${ was found, then the preceding joins the snippet to become
  1198. // a string token.
  1199. snippet += source_line.slice(0, at);
  1200. column += at;
  1201. source_line = source_line.slice(at);
  1202. if (source_line[0] === "\\") {
  1203. stop_at("escape_mega", line, at);
  1204. }
  1205. make("(string)", snippet).quote = "`";
  1206. snippet = "";
  1207. // If ${, then make tokens that will become part of an expression until
  1208. // a } token is made.
  1209. if (source_line[0] === "$") {
  1210. column += 2;
  1211. make("${");
  1212. source_line = source_line.slice(2);
  1213. (function expr() {
  1214. const id = lex().id;
  1215. if (id === "{") {
  1216. return stop_at(
  1217. "expected_a_b",
  1218. line,
  1219. column,
  1220. "}",
  1221. "{"
  1222. );
  1223. }
  1224. if (id !== "}") {
  1225. return expr();
  1226. }
  1227. }());
  1228. return part();
  1229. }
  1230. }());
  1231. source_line = source_line.slice(1);
  1232. column += 1;
  1233. mega_mode = false;
  1234. return make("`");
  1235. }
  1236. // The token is a // comment.
  1237. if (snippet === "//") {
  1238. snippet = source_line;
  1239. source_line = "";
  1240. the_token = comment(snippet);
  1241. if (mega_mode) {
  1242. warn("unexpected_comment", the_token, "`");
  1243. }
  1244. return the_token;
  1245. }
  1246. // The token is a /* comment.
  1247. if (snippet === "/*") {
  1248. array = [];
  1249. if (source_line[0] === "/") {
  1250. warn_at("unexpected_a", line, column + i, "/");
  1251. }
  1252. (function next() {
  1253. if (source_line > "") {
  1254. i = source_line.search(rx_star_slash);
  1255. if (i >= 0) {
  1256. return;
  1257. }
  1258. j = source_line.search(rx_slash_star);
  1259. if (j >= 0) {
  1260. warn_at("nested_comment", line, column + j);
  1261. }
  1262. }
  1263. array.push(source_line);
  1264. source_line = next_line();
  1265. if (source_line === undefined) {
  1266. return stop_at("unclosed_comment", line, column);
  1267. }
  1268. return next();
  1269. }());
  1270. snippet = source_line.slice(0, i);
  1271. j = snippet.search(rx_slash_star_or_slash);
  1272. if (j >= 0) {
  1273. warn_at("nested_comment", line, column + j);
  1274. }
  1275. array.push(snippet);
  1276. column += i + 2;
  1277. source_line = source_line.slice(i + 2);
  1278. return comment(array);
  1279. }
  1280. // The token is a slash.
  1281. if (snippet === "/") {
  1282. // The / can be a division operator or the beginning of a regular expression
  1283. // literal. It is not possible to know which without doing a complete parse.
  1284. // We want to complete the tokenization before we begin to parse, so we will
  1285. // estimate. This estimator can fail in some cases. For example, it cannot
  1286. // know if "}" is ending a block or ending an object literal, so it can
  1287. // behave incorrectly in that case; it is not meaningful to divide an
  1288. // object, so it is likely that we can get away with it. We avoided the worst
  1289. // cases by eliminating automatic semicolon insertion.
  1290. if (prior.identifier) {