diff --git a/CHANGELOG.md b/CHANGELOG.md index 395848843..c05373b6d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,8 +1,6 @@ # Changelog # Todo -- jslint - add numeric-separators support -- jslint - move regexp-literals to module-level so they are explicitly cached, to improve performance - cli - remove cli-option `--mode-vim-plugin` - coverage - add macros `/*coverage-disable*/` and `/*coverage-enable*/`. - coverage - support globbing `*` in cli-options `--exclude` and `--include` @@ -17,6 +15,8 @@ - perf - improve performance by hoisting inlined regexps out of loops and subfunctions # v2022.3.1-beta +- jslint - add numeric-separator support +- jslint - move regexp-literals to module-level so they are explicitly cached, to improve performance - ci - add check for package.json.fileCount # v2022.2.20 diff --git a/jslint.mjs b/jslint.mjs index 324089fc4..0701ee422 100644 --- a/jslint.mjs +++ b/jslint.mjs @@ -172,6 +172,91 @@ let jslint_export; // The jslint object to be exported. let jslint_fudge = 1; // Fudge starting line and starting // ... column to 1. let jslint_import_meta_url = ""; // import.meta.url used by cli. +let jslint_rgx_cap = ( + /^[A-Z]/ +); +let jslint_rgx_crlf = ( + /\n|\r\n?/ +); +let jslint_rgx_digits_bits = ( + /^[01_]*/ +); +let jslint_rgx_digits_decimals = ( + /^[0-9_]*/ +); +let jslint_rgx_digits_hexs = ( + /^[0-9A-F_]*/i +); +let jslint_rgx_digits_octals = ( + /^[0-7_]*/ +); +let jslint_rgx_directive = ( + /^(jslint|property|global)\s+(.*)$/ +); +let jslint_rgx_directive_part = ( + /([a-zA-Z$_][a-zA-Z0-9$_]*)(?::\s*(true|false))?,?\s*|$/g +); +let jslint_rgx_identifier = ( + /^([a-zA-Z_$][a-zA-Z0-9_$]*)$/ +); +let jslint_rgx_json_number = ( + +// https://datatracker.ietf.org/doc/html/rfc7159#section-6 +// number = [ minus ] int [ frac ] [ exp ] + + /^-?(?:0|[1-9]\d*)(?:\.\d*)?(?:[eE][\-+]?\d+)?$/ +); +let jslint_rgx_mega = ( + +// Vim-hack - vim-editor has trouble parsing naked '`' in regexp + + /[\u0060\\]|\$\{/ +); +let jslint_rgx_module = ( + /^[a-zA-Z0-9_$:.@\-\/]+$/ +); +let jslint_rgx_numeric_separator_illegal = ( + /__|_$|_n$/m +); +let jslint_rgx_slash_star_or_slash = ( + /\/\*|\/$/ +); +let jslint_rgx_tab = ( + /\t/g +); +let jslint_rgx_todo = ( + /\b(?:todo|TO\s?DO|HACK)\b/ +); +let jslint_rgx_token = new RegExp( + "^(" + + "(\\s+)" + + "|([a-zA-Z_$][a-zA-Z0-9_$]*)" + + "|[(){}\\[\\],:;'\"~\\`]" + + "|\\?[?.]?" + + "|=(?:==?|>)?" + + "|\\.+" + + "|\\*[*\\/=]?" + + "|\\/[*\\/]?" + + "|\\+[=+]?" + + "|-[=\\-]?" + + "|[\\^%]=?" + + "|&[&=]?" + + "|\\" + + "|[|=]?" + + "|>{1,3}=?" + + "|<)?" - + "|\\.+" - + "|\\*[*\\/=]?" - + "|\\/[*\\/]?" - + "|\\+[=+]?" - + "|-[=\\-]?" - + "|[\\^%]=?" - + "|&[&=]?" - + "|\\" - + "|[|=]?" - + "|>{1,3}=?" - + "|< 5) { + if (read_digits("x", undefined) > 5) { // test_cause: // ["\"\\u{123456}\"", "char_after_escape", "too_many_digits", "", 11] @@ -1973,7 +2037,7 @@ function jslint_phase2_lex(state) { return char_after(); } char_before(); - if (read_digits("x", true) < 4) { + if (read_digits("x", mode_digits_empty_string) < 4) { // test_cause: // ["\"\\u0\"", "char_after_escape", "expected_four_digits", "", 5] @@ -2008,6 +2072,26 @@ function jslint_phase2_lex(state) { return char; } + function check_numeric_separator(digits, column) { + +// This function will check for illegal numeric-separator in . + + digits.replace(( + jslint_rgx_numeric_separator_illegal + ), function (ignore, ii) { + +// test_cause: +// ["0x0_0_;", "check_numeric_separator", "illegal_num_separator", "", 6] +// ["0x0_0__0;", "check_numeric_separator", "illegal_num_separator", "", 6] +// ["aa=1_2_;", "check_numeric_separator", "illegal_num_separator", "", 7] +// ["aa=1_2__3;", "check_numeric_separator", "illegal_num_separator", "", 7] +// ["aa=1_2_n;", "check_numeric_separator", "illegal_num_separator", "", 7] + + warn_at("illegal_num_separator", line, column + ii + 1); + return ""; + }); + } + function lex_comment() { let body; let ii = 0; @@ -2046,12 +2130,12 @@ function jslint_phase2_lex(state) { // Lex/loop through each line until "*/". while (true) { - // rx_star_slash + // jslint_rgx_star_slash ii = line_source.indexOf("*/"); if (ii >= 0) { break; } - // rx_slash_star + // jslint_rgx_slash_star ii = line_source.indexOf("/*"); if (ii >= 0) { @@ -2071,8 +2155,7 @@ function jslint_phase2_lex(state) { } } jj = line_source.slice(0, ii).search( - // rx_slash_star_or_slash - /\/\*|\/$/ + jslint_rgx_slash_star_or_slash ); if (jj >= 0) { @@ -2090,13 +2173,7 @@ function jslint_phase2_lex(state) { // Uncompleted work comment. - if ( - !option_dict.devel - && ( - // rx_todo - /\b(?:todo|TO\s?DO|HACK)\b/ - ).test(snippet) - ) { + if (!option_dict.devel && jslint_rgx_todo.test(snippet)) { // test_cause: // ["//todo", "lex_comment", "todo_comment", "(comment)", 1] //jslint-quiet @@ -2108,10 +2185,7 @@ function jslint_phase2_lex(state) { [ the_comment.directive, body - ] = Array.from(snippet.match( - // rx_directive - /^(jslint|property|global)\s+(.*)$/ - ) || []).slice(1); + ] = Array.from(snippet.match(jslint_rgx_directive) || []).slice(1); if (the_comment.directive === undefined) { return the_comment; } @@ -2133,10 +2207,12 @@ function jslint_phase2_lex(state) { // Lex/loop through each directive in /*...*/ ii = 0; - body.replace(( - // rx_directive_part - /([a-zA-Z$_][a-zA-Z0-9$_]*)(?::\s*(true|false))?,?\s*|$/g - ), function (match0, key, val, jj) { + body.replace(jslint_rgx_directive_part, function ( + match0, + key, + val, + jj + ) { if (ii !== jj) { // test_cause: @@ -2210,13 +2286,7 @@ function jslint_phase2_lex(state) { // string. while (true) { - match = line_source.match( - -// Vim-hack - vim-editor has trouble parsing '`' in regexp - - // rx_mega - /[\u0060\\]|\$\{/ - ) || { + match = line_source.match(jslint_rgx_mega) || { "0": "", index: 0 }; @@ -2293,12 +2363,16 @@ function jslint_phase2_lex(state) { function lex_number() { let prefix = snippet; + +// PR-390 - Add numeric-separator check. + + check_numeric_separator(prefix, column - prefix.length); char_after(); switch (prefix === "0" && char) { case "b": case "o": case "x": - read_digits(char); + read_digits(char, mode_digits_numeric_separator); // PR-351 - Ignore BigInt suffix 'n'. @@ -2308,14 +2382,14 @@ function jslint_phase2_lex(state) { break; default: if (char === ".") { - read_digits("d"); + read_digits("d", mode_digits_numeric_separator); } if (char === "E" || char === "e") { char_after(char); if (char !== "+" && char !== "-") { char_before(); } - read_digits("d"); + read_digits("d", mode_digits_numeric_separator); } } @@ -2585,7 +2659,7 @@ function jslint_phase2_lex(state) { } break; case "{": - if (read_digits("d", true) === 0) { + if (read_digits("d", mode_digits_empty_string) === 0) { // test_cause: // ["aa=/aa{/", "lex_regexp_group", "expected_a_before_b", ",", 8] @@ -2598,7 +2672,7 @@ function jslint_phase2_lex(state) { // ["aa=/.{,/", "lex_regexp_group", "comma", "", 0] test_cause("comma"); - read_digits("d", true); + read_digits("d", mode_digits_empty_string); } if (char_after("}") === "?") { @@ -2891,7 +2965,7 @@ function jslint_phase2_lex(state) { } } from = column; - match = line_source.match(rx_token); + match = line_source.match(jslint_rgx_token); // match[1] token // match[2] whitespace @@ -3274,41 +3348,48 @@ import moduleHttps from "https"; return true; } - function read_digits(base, quiet) { + function read_digits(base, mode) { let digits = line_source.match( base === "b" - ? ( - // rx_bits - /^[01]*/ - ) + ? jslint_rgx_digits_bits : base === "o" - ? ( - // rx_octals - /^[0-7]*/ - ) + ? jslint_rgx_digits_octals : base === "x" - ? ( - // rx_hexs - /^[0-9A-F]*/i - ) - : ( - // rx_digits - /^[0-9]*/ - ) + ? jslint_rgx_digits_hexs + : jslint_rgx_digits_decimals )[0]; - let length = digits.length; - if (!quiet && length === 0) { + if ( + (mode !== mode_digits_empty_string && digits.length === 0) + || digits[0] === "_" + ) { // test_cause: // ["0x", "read_digits", "expected_digits_after_a", "0x", 2] +// ["0x_", "read_digits", "expected_digits_after_a", "0x", 2] warn_at("expected_digits_after_a", line, column, snippet); } - column += length; - line_source = line_source.slice(length); + +// PR-390 - Add numeric-separator check. + + if (mode === mode_digits_numeric_separator) { + check_numeric_separator(digits, column); + } else if (digits.indexOf("_") >= 0) { + +// test_cause: +// ["\"\\u{1_2}\"", "read_digits", "illegal_num_separator", "", 6] + + warn_at( + "illegal_num_separator", + line, + column + digits.indexOf("_") + 1 + ); + } + column += digits.length; + line_source = line_source.slice(digits.length); snippet += digits; char_after(); - return length; + return digits.length; } function read_line() { @@ -3379,6 +3460,7 @@ import moduleHttps from "https"; test_cause("line_disable"); line_source = ""; } + // jslint_rgx_tab if (line_source.indexOf("\t") >= 0) { if (!option_dict.white) { @@ -3387,10 +3469,7 @@ import moduleHttps from "https"; warn_at("use_spaces", line, line_source.indexOf("\t") + 1); } - line_source = line_source.replace(( - // rx_tab - /\t/g - ), " "); + line_source = line_source.replace(jslint_rgx_tab, " "); } if (!option_dict.white && line_source.endsWith(" ")) { @@ -3564,9 +3643,6 @@ function jslint_phase3_parse(state) { let catchage = catch_stack[0]; // The current catch-block. let functionage = token_global; // The current function. let mode_var; // "var" if using var; "let" if using let. - let rx_identifier = ( - /^([a-zA-Z_$][a-zA-Z0-9_$]*)$/ - ); let token_ii = 0; // The number of the next token. let token_now = token_global; // The current token being examined in // ... the parse. @@ -3583,7 +3659,7 @@ function jslint_phase3_parse(state) { anon = token_now.id; } else if ( token_now.id === "(string)" - && rx_identifier.test(token_now.value) + && jslint_rgx_identifier.test(token_now.value) ) { anon = token_now.value; } @@ -4233,7 +4309,7 @@ function jslint_phase3_parse(state) { let the_subscript = parse_expression(0); if (the_subscript.id === "(string)" || the_subscript.id === "`") { name = survey(the_subscript); - if (rx_identifier.test(name)) { + if (jslint_rgx_identifier.test(name)) { // test_cause: // ["aa[`aa`]", "infix_lbracket", "subscript_a", "aa", 4] @@ -4594,13 +4670,7 @@ function jslint_phase3_parse(state) { let negative; switch (token_nxt.id) { case "(number)": - if (!( - -// https://datatracker.ietf.org/doc/html/rfc7159#section-6 -// number = [ minus ] int [ frac ] [ exp ] - - /^-?(?:0|[1-9]\d*?)(?:\.\d*?)?(?:[eE][+\-]?\d+?)?$/ - ).test(token_nxt.value)) { + if (!jslint_rgx_json_number.test(token_nxt.value)) { // test_cause: // ["[-.0]", "parse_json", "unexpected_a", ".", 3] @@ -6100,10 +6170,7 @@ function jslint_phase3_parse(state) { advance("from"); advance("(string)"); the_import.import = token_now; - if (!( - // rx_module - /^[a-zA-Z0-9_$:.@\-\/]+$/ - ).test(token_now.value)) { + if (!jslint_rgx_module.test(token_now.value)) { // test_cause: // ["import aa from \"!aa\"", "stmt_import", "bad_module_name_a", "!aa", 16] @@ -6714,13 +6781,13 @@ function jslint_phase3_parse(state) { if (id === "(string)") { id = name.value; - if (!rx_identifier.test(id)) { + if (!jslint_rgx_identifier.test(id)) { return id; } } else if (id === "`") { if (name.value.length === 1) { id = name.value[0].value; - if (!rx_identifier.test(id)) { + if (!jslint_rgx_identifier.test(id)) { return id; } } @@ -6753,10 +6820,7 @@ function jslint_phase3_parse(state) { } else if ( !option_dict.name && name.identifier - && ( - // rx_weird_property - /^_|\$|Sync$|_$/m - ).test(id) + && jslint_rgx_weird_property.test(id) ) { // test_cause: @@ -7510,10 +7574,7 @@ function jslint_phase4_walk(state) { test_cause("cack"); cack = !cack; } - if (( - // rx_cap - /^[A-Z]/ - ).test(left.name.id) !== cack) { + if (jslint_rgx_cap.test(left.name.id) !== cack) { if (the_new !== undefined) { // test_cause: diff --git a/test.mjs b/test.mjs index f33e8d493..5beb7284d 100644 --- a/test.mjs +++ b/test.mjs @@ -391,6 +391,16 @@ jstestDescribe(( "let aa = 0o0;", "let aa = 0x0;" ], + +// PR-390 - Add numeric-separator support. + + numeric_separator: [ + "let aa = 0.0_0_0;", + "let aa = 0b0_1111_1111n;\n", + "let aa = 0o0_1234_1234n;\n", + "let aa = 0x0_1234_1234n;\n", + "let aa = 1_234_234.1_234_234E1_234_234;" + ], optional_chaining: [ "let aa = aa?.bb?.cc;" ],