miyaguchipreneel
Version:
JavaScript implementation for MiyaguchiPreneel compression function
1,589 lines (1,390 loc) • 336 kB
JavaScript
// #!/usr/bin/env node
// JSLint
// The Unlicense
//
// This is free and unencumbered software released into the public domain.
//
// Anyone is free to copy, modify, publish, use, compile, sell, or
// distribute this software, either in source code form or as a compiled
// binary, for any purpose, commercial or non-commercial, and by any
// means.
//
// In jurisdictions that recognize copyright laws, the author or authors
// of this software dedicate any and all copyright interest in the
// software to the public domain. We make this dedication for the benefit
// of the public at large and to the detriment of our heirs and
// successors. We intend this dedication to be an overt act of
// relinquishment in perpetuity of all present and future rights to this
// software under copyright law.
//
// 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 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.
//
// For more information, please refer to <https://unlicense.org/>
// jslint(source, option_dict, global_list) is a function that takes 3
// arguments. The second two arguments are optional.
// source A text to analyze.
// option_dict An object whose keys correspond to option names.
// global_list 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.
// exports: the names exported from the module.
// froms: an array of strings representing each of the imports.
// functions: an array of objects that represent all 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)"
// 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:
// PHASE 1. Split <source> by newlines into <line_list>.
// PHASE 2. Lex <line_list> into <token_list>.
// PHASE 3. Parse <token_list> into <token_tree> using the Pratt-parser.
// PHASE 4. Walk <token_tree>, traversing all 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).
// PHASE 5. Check whitespace between tokens in <token_list>.
// 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.
/*jslint beta, node*/
/*property
JSLINT_BETA, NODE_V8_COVERAGE, a, all, argv, arity, artifact,
assertErrorThrownAsync, assertJsonEqual, assertOrThrow, assign, async, b,
beta, bitwise, block, body, browser, c, calls, catch, catch_list,
catch_stack, causes, char, children, clear, closer, closure, code, column,
concat, consoleError, console_error, console_log, constant, context,
convert, count, coverageDir, create, cwd, d, dead, debugInline, default,
delta, devel, directive, directive_ignore_line, directive_list, directives,
dirname, disrupt, dot, edition, elem_list, ellipsis, else, end, endOffset,
endsWith, entries, env, error, eval, every, example_list, excludeList, exec,
execArgv, exit, exitCode, export_dict, exports, expression, extra, fart,
file, fileList, fileURLToPath, filter, finally, flag, floor, for, forEach,
formatted_message, free, freeze, from, froms, fsWriteFileWithParents,
fud_stmt, functionName, function_list, function_stack, functions, get,
getset, github_repo, globExclude, global, global_dict, global_list,
holeList, htmlEscape, id, identifier, import, import_list, import_meta_url,
inc, includeList, indent2, index, indexOf, init, initial, isArray,
isBlockCoverage, isHole, isNaN, is_equal, is_weird, join, jslint,
jslint_apidoc, jslint_assert, jslint_charset_ascii, jslint_cli,
jslint_edition, jslint_phase1_split, jslint_phase2_lex, jslint_phase3_parse,
jslint_phase4_walk, jslint_phase5_whitage, jslint_report, json,
jstestDescribe, jstestIt, jstestOnExit, keys, label, lbp, led_infix, length,
level, line, lineList, line_list, line_offset, line_source, lines,
linesCovered, linesTotal, live, log, long, loop, m, map, margin, match, max,
message, meta, min, mkdir, modeCoverageIgnoreFile, modeIndex, mode_cli,
mode_conditional, mode_json, mode_module, mode_noop, mode_property,
mode_shebang, mode_stop, module, moduleFsInit, moduleName, module_list,
name, names, node, nomen, noop, now, nr, nud_prefix,
objectDeepCopyWithKeysSorted, ok, on, open, opening, option, option_dict,
order, package_name, padEnd, padStart, parameters, parent, parentIi, parse,
pathname, pathnameList, platform, pop, processArgv, process_argv,
process_env, process_exit, promises, property, property_dict, push, quote,
ranges, readFile, readdir, readonly, recursive, reduce, repeat, replace,
resolve, result, reverse, role, round, scriptId, search, set, shebang,
shift, signature, single, slice, some, sort, source, spawn, splice, split,
stack, stack_trace, start, startOffset, startsWith, statement,
statement_prv, stdio, stop, stop_at, stringify, subscript, switch,
syntax_dict, tenure, test, test_cause, test_internal_error, this, thru,
toLocaleString, toString, token, token_global, token_list, token_nxt,
token_tree, tokens, trace, tree, trim, trimEnd, trimRight, try, type,
unlink, unordered, unshift, url, used, v8CoverageListMerge,
v8CoverageReportCreate, value, variable, version, versions, warn, warn_at,
warning, warning_list, warnings, white, wrapped, writeFile
*/
// init debugInline
let debugInline = (function () {
let __consoleError = function () {
return;
};
function debug(...argv) {
// This function will print <argv> to stderr and then return <argv>[0].
__consoleError("\n\ndebugInline");
__consoleError(...argv);
__consoleError("\n");
return argv[0];
}
debug(); // Coverage-hack.
__consoleError = console.error; //jslint-ignore-line
return debug;
}());
let jslint_charset_ascii = (
"\u0000\u0001\u0002\u0003\u0004\u0005\u0006\u0007"
+ "\b\t\n\u000b\f\r\u000e\u000f"
+ "\u0010\u0011\u0012\u0013\u0014\u0015\u0016\u0017"
+ "\u0018\u0019\u001a\u001b\u001c\u001d\u001e\u001f"
+ " !\"#$%&'()*+,-./0123456789:;<=>?"
+ "@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_"
+ "`abcdefghijklmnopqrstuvwxyz{|}~\u007f"
);
let jslint_edition = "v2023.1.29";
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}=?"
+ "|<<?=?"
+ "|!(?:!|==?)?"
// PR-351 - Add BigInt support.
// PR-390 - Add numeric-separator support.
+ "|((?:0_?|[1-9][0-9_]*)n?)"
+ ")"
+ "(.*)$"
);
let jslint_rgx_url_search_window_jslint = (
/[&?]window_jslint=1(?:$|&)/m
);
let jslint_rgx_weird_property = (
/^_|\$|Sync$|_$/m
);
let jstestCountFailed = 0;
let jstestCountTotal = 0;
let jstestItCount = 0;
let jstestItList = [];
let jstestTimeStart;
let moduleChildProcess;
let moduleFs;
let moduleFsInitResolveList;
let modulePath;
let moduleUrl;
async function assertErrorThrownAsync(asyncFunc, regexp) {
// This function will assert calling <asyncFunc> throws an error.
let err;
try {
await asyncFunc();
} catch (errCaught) {
err = errCaught;
}
assertOrThrow(err, "No error thrown.");
assertOrThrow(
regexp === undefined || new RegExp(regexp).test(err.message),
err
);
}
function assertJsonEqual(aa, bb, message) {
// This function will assert JSON.stringify(<aa>) === JSON.stringify(<bb>).
aa = JSON.stringify(objectDeepCopyWithKeysSorted(aa), undefined, 1);
bb = JSON.stringify(objectDeepCopyWithKeysSorted(bb), undefined, 1);
if (aa !== bb) {
throw new Error(
"\n" + aa + "\n!==\n" + bb
+ (
typeof message === "string"
? " - " + message
: message
? " - " + JSON.stringify(message)
: ""
)
);
}
}
function assertOrThrow(condition, message) {
// This function will throw <message> if <condition> is falsy.
if (!condition) {
throw (
(!message || typeof message === "string")
? new Error(String(message).slice(0, 2048))
: message
);
}
}
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);
}
async function fsWriteFileWithParents(pathname, data) {
// This function will write <data> to <pathname> and lazy-mkdirp if necessary.
await moduleFsInit();
// Try writing to pathname.
try {
await moduleFs.promises.writeFile(pathname, data);
} catch (ignore) {
// Lazy mkdirp.
await moduleFs.promises.mkdir(modulePath.dirname(pathname), {
recursive: true
});
// Retry writing to pathname.
await moduleFs.promises.writeFile(pathname, data);
}
console.error("wrote file " + pathname);
}
function globExclude({
excludeList = [],
includeList = [],
pathnameList = []
}) {
// This function will
// 1. Exclude pathnames in <pathnameList> that don't match glob-patterns in
// <includeList>.
// 2. Exclude pathnames in <pathnameList> that match glob-patterns in
// <excludeList>.
function globAssertNotWeird(list, name) {
// This function will check if <list> of strings contain weird characters.
[
[
"\n", (
/^.*?([\u0000-\u0007\r]).*/gm
)
],
[
"\r", (
/^.*?([\n]).*/gm
)
]
].forEach(function ([
separator, rgx
]) {
list.join(separator).replace(rgx, function (match0, char) {
throw new Error(
"Weird character "
+ JSON.stringify(char)
+ " found in " + name + " "
+ JSON.stringify(match0)
);
});
});
}
function globToRegexp(pattern) {
// This function will translate glob <pattern> to javascript-regexp,
// which javascript can then use to "glob" pathnames.
let ii = 0;
let isClass = false;
let strClass = "";
let strRegex = "";
pattern = pattern.replace((
/\/\/+/g
), "/");
pattern = pattern.replace((
/\*\*\*+/g
), "**");
pattern.replace((
/\\\\|\\\[|\\\]|\[|\]|./g
), function (match0) {
switch (match0) {
case "[":
if (isClass) {
strClass += "[";
return;
}
strClass += "\u0000";
strRegex += "\u0000";
isClass = true;
return;
case "]":
if (isClass) {
isClass = false;
return;
}
strRegex += "]";
return;
default:
if (isClass) {
strClass += match0;
return;
}
strRegex += match0;
}
return "";
});
strClass += "\u0000";
// An expression "[!...]" matches a single character, namely any character that
// is not matched by the expression obtained by removing the first '!' from it.
// (Thus, "[!a-]" matches any single character except 'a', and '-'.)
strClass = strClass.replace((
/\u0000!/g
), "\u0000^");
// One may include '-' in its literal meaning by making it the first or last
// character between the brackets.
strClass = strClass.replace((
/\u0000-/g
), "\u0000\\-");
strClass = strClass.replace((
/-\u0000/g
), "\\-\u0000");
// Escape brackets '[', ']' in character class.
strClass = strClass.replace((
/[\[\]]/g
), "\\$&");
// https://stackoverflow.com/questions/3561493
// /is-there-a-regexp-escape-function-in-javascript
// $()*+-./?[\]^{|}
strRegex = strRegex.replace((
// Ignore [-/].
/[$()*+.?\[\\\]\^{|}]/g
), "\\$&");
// Expand wildcard '**/*'.
strRegex = strRegex.replace((
/\\\*\\\*\/(?:\\\*)+/g
), ".*?");
// Expand wildcard '**'.
strRegex = strRegex.replace((
/(^|\/)\\\*\\\*(\/|$)/gm
), "$1.*?$2");
// Expand wildcard '*'.
strRegex = strRegex.replace((
/(?:\\\*)+/g
), "[^\\/]*?");
// Expand wildcard '?'.
strRegex = strRegex.replace((
/\\\?/g
), "[^\\/]");
// Expand directory-with-trailing-slash '.../'.
strRegex = strRegex.replace((
/\/$/gm
), "\\/.*?");
// Merge strClass into strRegex.
ii = 0;
strClass = strClass.split("\u0000");
strRegex = strRegex.replace((
/\u0000/g
), function () {
ii += 1;
if (strClass[ii] === "") {
return "";
}
return "[" + strClass[ii] + "]";
});
// Change strRegex from string to regexp.
strRegex = new RegExp("^" + strRegex + "$", "gm");
return strRegex;
}
// Validate excludeList, includeList, pathnameList.
globAssertNotWeird(excludeList, "pattern");
globAssertNotWeird(includeList, "pattern");
globAssertNotWeird(pathnameList, "pathname");
// Optimization
// Concat pathnames into a single, newline-separated string,
// whose pathnames can all be filtered with a single, regexp-pass.
pathnameList = pathnameList.join("\n");
// 1. Exclude pathnames in <pathnameList> that don't match glob-patterns in
// <includeList>.
if (includeList.length > 0) {
includeList = includeList.map(globToRegexp);
includeList.forEach(function (pattern) {
pathnameList = pathnameList.replace(pattern, "\u0000$&");
});
pathnameList = pathnameList.replace((
/^[^\u0000].*/gm
), "");
pathnameList = pathnameList.replace((
/^\u0000+/gm
), "");
}
// 2. Exclude pathnames in <pathnameList> that match glob-patterns in
// <excludeList>.
excludeList = excludeList.map(globToRegexp);
excludeList.forEach(function (pattern) {
pathnameList = pathnameList.replace(pattern, "");
});
// Split newline-separated pathnames back to list.
pathnameList = pathnameList.split("\n").filter(function (elem) {
return elem;
});
return {
excludeList,
includeList,
pathnameList
};
}
function htmlEscape(str) {
// This function will make <str> html-safe by escaping & < >.
return String(str).replace((
/&/g
), "&").replace((
/</g
), "<").replace((
/>/g
), ">");
}
function jslint(
source = "", // A text to analyze.
option_dict = empty(), // An object whose keys correspond to option
// ... names.
global_list = [] // An array of strings containing global
// ... variables that the file is allowed
// ... readonly access.
) {
// The jslint function itself.
let catch_list = []; // The array containing all catch-blocks.
let catch_stack = [ // The stack of catch-blocks.
{
context: empty()
}
];
let cause_dict = empty(); // The object of test-causes.
let directive_list = []; // The directive comments.
let export_dict = empty(); // The exported names and values.
let function_list = []; // The array containing all functions.
let function_stack = []; // The stack of functions.
let global_dict = empty(); // The object containing the global
// ... declarations.
let import_list = []; // The array collecting all import-from strings.
let line_list = String( // The array containing source lines.
"\n" + source
).split(jslint_rgx_crlf).map(function (line_source) {
return {
line_source
};
});
let mode_stop = false; // true if JSLint cannot finish.
let property_dict = empty(); // The object containing the tallied
// ... property names.
let state = empty(); // jslint state-object to be passed between
// jslint functions.
let syntax_dict = empty(); // The object containing the parser.
let tenure = empty(); // The predefined property registry.
let token_global = { // The global object; the outermost context.
async: 0,
body: true,
context: empty(),
finally: 0,
from: 0,
id: "(global)",
level: 0,
line: jslint_fudge,
live: [],
loop: 0,
switch: 0,
thru: 0,
try: 0
};
let token_list = []; // The array of tokens.
let warning_list = []; // The array collecting all generated warnings.
// Error reportage functions:
function artifact(the_token) {
// Return a string representing an artifact.
the_token = the_token || state.token_nxt;
return (
(the_token.id === "(string)" || the_token.id === "(number)")
? String(the_token.value)
: the_token.id
);
}
function is_equal(aa, bb) {
// test_cause:
// ["0&&0", "is_equal", "", "", 0]
test_cause("");
// Probably deadcode.
// if (aa === bb) {
// return true;
// }
jslint_assert(!(aa === bb), `Expected !(aa === bb).`);
if (Array.isArray(aa)) {
return (
Array.isArray(bb)
&& aa.length === bb.length
&& aa.every(function (value, index) {
// test_cause:
// ["`${0}`&&`${0}`", "is_equal", "recurse_isArray", "", 0]
// ["`${0}`&&`${1}`", "is_equal", "recurse_isArray", "", 0]
test_cause("recurse_isArray");
return is_equal(value, bb[index]);
})
);
}
// Probably deadcode.
// if (Array.isArray(bb)) {
// return false;
// }
jslint_assert(!Array.isArray(bb), `Expected !Array.isArray(bb).`);
switch (aa.id === bb.id && aa.id) {
case "(number)":
case "(string)":
return aa.value === bb.value;
// PR-394 - Bugfix
// Fix jslint falsely believing megastring literals `0` and `1` are similar.
case "`":
if (!is_equal(aa.value, bb.value)) {
return false;
}
break;
}
if (is_weird(aa) || is_weird(bb)) {
// test_cause:
// ["aa(/./)||{}", "is_equal", "false", "", 0]
test_cause("false");
return false;
}
if (aa.arity === bb.arity && aa.id === bb.id) {
if (aa.id === "." || aa.id === "?.") {
// test_cause:
// ["aa.bb&&aa.bb", "is_equal", "recurse_arity_id", "", 0]
// ["aa?.bb&&aa?.bb", "is_equal", "recurse_arity_id", "", 0]
test_cause("recurse_arity_id");
return (
is_equal(aa.expression, bb.expression)
&& is_equal(aa.name, bb.name)
);
}
if (aa.arity === "unary") {
// test_cause:
// ["+0&&+0", "is_equal", "recurse_unary", "", 0]
test_cause("recurse_unary");
return is_equal(aa.expression, bb.expression);
}
if (aa.arity === "binary") {
// test_cause:
// ["aa[0]&&aa[0]", "is_equal", "recurse_binary", "", 0]
test_cause("recurse_binary");
return (
aa.id !== "("
&& is_equal(aa.expression[0], bb.expression[0])
&& is_equal(aa.expression[1], bb.expression[1])
);
}
if (aa.arity === "ternary") {
// test_cause:
// ["aa=(``?``:``)&&(``?``:``)", "is_equal", "recurse_ternary", "", 0]
test_cause("recurse_ternary");
return (
is_equal(aa.expression[0], bb.expression[0])
&& is_equal(aa.expression[1], bb.expression[1])
&& is_equal(aa.expression[2], bb.expression[2])
);
}
// Probably deadcode.
// if (aa.arity === "function" || aa.arity === "regexp") {
// return false;
// }
jslint_assert(
!(aa.arity === "function" || aa.arity === "regexp"),
`Expected !(aa.arity === "function" || aa.arity === "regexp").`
);
// test_cause:
// ["undefined&&undefined", "is_equal", "true", "", 0]
test_cause("true");
return true;
}
// test_cause:
// ["null&&undefined", "is_equal", "false", "", 0]
test_cause("false");
return false;
}
function is_weird(thing) {
switch (thing.id) {
case "(regexp)":
return true;
case "=>":
return true;
case "[":
return thing.arity === "unary";
case "function":
return true;
case "{":
return true;
default:
return false;
}
}
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.
the_token = the_token || state.token_nxt;
delete the_token.warning;
throw warn(code, the_token, a, b, c, d);
}
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 test_cause(code, aa, column) {
// This function will instrument <cause> to <cause_dict> for test-purposes.
if (option_dict.test_cause) {
cause_dict[JSON.stringify([
String(new Error().stack).replace((
/^ at (?:file|stop|stop_at|test_cause|warn|warn_at)\b.*?\n/gm
), "").match(
/\n at ((?:Object\.\w+?_)?\w+?) /
)[1].replace((
/^Object\./
), ""),
code,
String(
(aa === undefined || aa === token_global)
? ""
: aa
),
column || 0
])] = true;
}
}
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.
let the_warning;
the_token = the_token || state.token_nxt;
the_warning = warn_at(
code,
the_token.line,
(the_token.from || 0) + jslint_fudge,
a || artifact(the_token),
b,
c,
d
);
// Issue #408
// Warnings that should be ignored sometimes suppress legitimate warnings.
if (the_warning.directive_ignore_line) {
return the_warning;
}
// 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.warning) {
warning_list.pop();
return the_warning;
}
the_token.warning = the_warning;
return the_warning;
}
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.
let mm;
let warning = Object.assign(empty(), {
a,
b,
c,
code,
// Fudge column numbers in warning message.
column: column || jslint_fudge,
d,
line,
line_source: "",
name: "JSLintError"
}, line_list[line]);
warning.column = Math.max(
Math.min(warning.column, warning.line_source.length),
jslint_fudge
);
test_cause(code, b || a, warning.column);
switch (code) {
// 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.
case "and":
mm = `The '&&' subexpression should be wrapped in parens.`;
break;
case "bad_assignment_a":
mm = `Bad assignment to '${a}'.`;
break;
case "bad_directive_a":
mm = `Bad directive '${a}'.`;
break;
case "bad_get":
mm = `A get function takes no parameters.`;
break;
case "bad_module_name_a":
mm = `Bad module name '${a}'.`;
break;
case "bad_option_a":
mm = `Bad option '${a}'.`;
break;
case "bad_set":
mm = `A set function takes one parameter.`;
break;
case "duplicate_a":
mm = `Duplicate '${a}'.`;
break;
case "empty_block":
mm = `Empty block.`;
break;
case "expected_a":
mm = `Expected '${a}'.`;
break;
case "expected_a_at_b_c":
mm = `Expected '${a}' at column ${b}, not column ${c}.`;
break;
case "expected_a_b":
mm = `Expected '${a}' and instead saw '${b}'.`;
break;
case "expected_a_b_before_c_d":
mm = `Expected ${a} '${b}' to be ordered before ${c} '${d}'.`;
break;
case "expected_a_b_from_c_d":
mm = (
`Expected '${a}' to match '${b}' from line ${c}`
+ ` and instead saw '${d}'.`
);
break;
case "expected_a_before_b":
mm = `Expected '${a}' before '${b}'.`;
break;
case "expected_digits_after_a":
mm = `Expected digits after '${a}'.`;
break;
case "expected_four_digits":
mm = `Expected four digits after '\\u'.`;
break;
case "expected_identifier_a":
mm = `Expected an identifier and instead saw '${a}'.`;
break;
case "expected_line_break_a_b":
mm = `Expected a line break between '${a}' and '${b}'.`;
break;
case "expected_regexp_factor_a":
mm = `Expected a regexp factor and instead saw '${a}'.`;
break;
case "expected_space_a_b":
mm = `Expected one space between '${a}' and '${b}'.`;
break;
case "expected_statements_a":
mm = `Expected statements before '${a}'.`;
break;
case "expected_string_a":
mm = `Expected a string and instead saw '${a}'.`;
break;
case "expected_type_string_a":
mm = `Expected a type string and instead saw '${a}'.`;
break;
case "freeze_exports":
mm = (
`Expected 'Object.freeze('. All export values should be frozen.`
);
break;
// PR-378 - Relax warning "function_in_loop".
//
// case "function_in_loop":
// mm = `Don't create functions within a loop.`;
// break;
// PR-390 - Add numeric-separator check.
case "illegal_num_separator":
mm = `Illegal numeric separator '_' at column ${column}.`;
break;
case "infix_in":
mm = (
`Unexpected 'in'. Compare with undefined,`
+ ` or use the hasOwnProperty method instead.`
);
break;
case "label_a":
mm = `'${a}' is a statement label.`;
break;
case "misplaced_a":
mm = `Place '${a}' at the outermost level.`;
break;
case "misplaced_directive_a":
mm = `Place the '/*${a}*/' directive before the first statement.`;
break;
case "missing_await_statement":
mm = `Expected await statement in async function.`;
break;
// PR-347 - Disable warning "missing_browser".
//
// case "missing_browser":
// mm = `/*global*/ requires the Assume a browser option.`;
// break;
case "missing_m":
mm = `Expected 'm' flag on a multiline regular expression.`;
break;
case "naked_block":
mm = `Naked block.`;
break;
case "nested_comment":
mm = `Nested comment.`;
break;
case "not_label_a":
mm = `'${a}' is not a label.`;
break;
case "number_isNaN":
mm = `Use Number.isNaN function to compare with NaN.`;
break;
case "out_of_scope_a":
mm = `'${a}' is out of scope.`;
break;
case "redefinition_a_b":
mm = `Redefinition of '${a}' from line ${b}.`;
break;
case "redefinition_global_a_b":
mm = `Redefinition of global ${a} variable '${b}'.`;
break;
case "required_a_optional_b":
mm = `Required parameter '${a}' after optional parameter '${b}'.`;
break;
case "reserved_a":
mm = `Reserved name '${a}'.`;
break;
case "subscript_a":
mm = `['${a}'] is better written in dot notation.`;
break;
case "todo_comment":
mm = `Unexpected TODO comment.`;
break;
case "too_long":
mm = `Line is longer than 80 characters.`;
break;
case "too_many_digits":
mm = `Too many digits.`;
break;
case "unclosed_comment":
mm = `Unclosed comment.`;
break;
case "unclosed_disable":
mm = (
`Directive '/*jslint-disable*/' was not closed`
+ ` with '/*jslint-enable*/'.`
);
break;
case "unclosed_mega":
mm = `Unclosed mega literal.`;
break;
case "unclosed_string":
mm = `Unclosed string.`;
break;
case "undeclared_a":
mm = `Undeclared '${a}'.`;
break;
case "unexpected_a":
mm = `Unexpected '${a}'.`;
break;
case "unexpected_a_after_b":
mm = `Unexpected '${a}' after '${b}'.`;
break;
case "unexpected_a_before_b":
mm = `Unexpected '${a}' before '${b}'.`;
break;
case "unexpected_at_top_level_a":
mm = `Expected '${a}' to be in a function.`;
break;
case "unexpected_char_a":
mm = `Unexpected character '${a}'.`;
break;
case "unexpected_comment":
mm = `Unexpected comment.`;
break;
// PR-347 - Disable warning "unexpected_directive_a".
//
// case "unexpected_directive_a":
// mm = `When using modules, don't use directive '/\u002a${a}'.`;
// break;
case "unexpected_expression_a":
mm = `Unexpected expression '${a}' in statement position.`;
break;
case "unexpected_label_a":
mm = `Unexpected label '${a}'.`;
break;
case "unexpected_parens":
mm = `Don't wrap function literals in parens.`;
break;
case "unexpected_space_a_b":
mm = `Unexpected space between '${a}' and '${b}'.`;
break;
case "unexpected_statement_a":
mm = `Unexpected statement '${a}' in expression position.`;
break;
case "unexpected_trailing_space":
mm = `Unexpected trailing space.`;
break;
case "unexpected_typeof_a":
mm = (
`Unexpected 'typeof'. Use '===' to compare directly with ${a}.`
);
break;
case "uninitialized_a":
mm = `Uninitialized '${a}'.`;
break;
case "unopened_enable":
mm = (
`Directive '/*jslint-enable*/' was not opened`
+ ` with '/*jslint-disable*/'.`
);
break;
case "unreachable_a":
mm = `Unreachable '${a}'.`;
break;
case "unregistered_property_a":
mm = `Unregistered property name '${a}'.`;
break;
case "unused_a":
mm = `Unused '${a}'.`;
break;
case "use_double":
mm = `Use double quotes, not single quotes.`;
break;
// PR-386 - Fix issue #382 - Make fart-related warnings more readable.
case "use_function_not_fart":
mm = (
`Use 'function (...)', not '(...) =>' when arrow functions`
+ ` become too complex.`
);
break;
case "use_open":
mm = (
`Wrap a ternary expression in parens,`
+ ` with a line break after the left paren.`
);
break;
case "use_spaces":
mm = `Use spaces, not tabs.`;
break;
case "var_on_top":
mm = `Move variable declaration to top of function or script.`;
break;
case "var_switch":
mm = `Don't declare variables in a switch.`;
break;
case "weird_condition_a":
mm = `Weird condition '${a}'.`;
break;
case "weird_expression_a":
mm = `Weird expression '${a}'.`;
break;
case "weird_loop":
mm = `Weird loop.`;
break;
case "weird_property_a":
mm = `Weird property name '${a}'.`;
break;
case "weird_relation_a":
mm = `Weird relation '${a}'.`;
break;
case "wrap_condition":
mm = `Wrap the condition in parens.`;
break;
// PR-386 - Fix issue #382 - Make fart-related warnings more readable.
case "wrap_fart_parameter":
mm = `Wrap the parameter before '=>' in parens.`;
break;
case "wrap_immediate":
mm = (
`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.`
);
break;
case "wrap_regexp":
mm = `Wrap this regexp in parens to avoid confusion.`;
break;
case "wrap_unary":
mm = `Wrap the unary expression in parens.`;
break;
}
// Validate mm.
jslint_assert(mm, code);
warning.message = mm;
// PR-242 - Include stack_trace for jslint to debug itself for errors.
if (option_dict.trace) {
warning.stack_trace = new Error().stack;
}
if (warning.directive_ignore_line) {
// test_cause:
// ["0 //jslint-ignore-line", "semicolon", "directive_ignore_line", "", 0]
test_cause("directive_ignore_line");
return warning;
}
warning_list.push(warning);
return warning;
}
try {
// 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.
option_dict = Object.assign(empty(), option_dict);
Object.assign(state, {
artifact,
catch_list,
catch_stack,
directive_list,
export_dict,
function_list,
function_stack,
global_dict,
global_list,
import_list,
is_equal,
is_weird,
line_list,
mode_json: false, // true if parsing JSON.
mode_module: false, // true if import or export was used.
mode_property: false, // true if directive /*property*/ is
// ... used.
mode_shebang: false, // true if #! is seen on the first line.
option_dict,
property_dict,
source,
stop,
stop_at,
syntax_dict,
tenure,
test_cause,
token_global,
token_list,
token_nxt: token_global,
warn,
warn_at,
warning_list
});
// PHASE 1. Split <source> by newlines into <line_list>.
jslint_phase1_split(state);
jslint_assert(catch_stack.length === 1, `catch_stack.length === 1.`);
jslint_assert(
function_stack.length === 0,
`function_stack.length === 0.`
);
// PHASE 2. Lex <line_list> into <token_list>.
jslint_phase2_lex(state);
jslint_assert(catch_stack.length === 1, `catch_stack.length === 1.`);
jslint_assert(
function_stack.length === 0,
`function_stack.length === 0.`
);
// PHASE 3. Parse <token_list> into <token_tree> using the Pratt-parser.
jslint_phase3_parse(state);
jslint_assert(catch_stack.length === 1, `catch_stack.length === 1.`);
jslint_assert(
function_stack.length === 0,
`function_stack.length === 0.`
);
// PHASE 4. Walk <token_tree>, traversing all 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).
if (!state.mode_json) {
jslint_phase4_walk(state);
}
jslint_assert(catch_stack.length === 1, `catch_stack.length === 1.`);
jslint_assert(
function_stack.length === 0,
`function_stack.length === 0.`
);
// PHASE 5. Check whitespace between tokens in <token_list>.
if (!state.mode_json && warning_list.length === 0) {
jslint_phase5_whitage(state);
}
jslint_assert(catch_stack.length === 1, `catch_stack.length === 1.`);
jslint_assert(
function_stack.length === 0,
`function_stack.length === 0.`
);
// PR-347 - Disable warning "missing_browser".
//
// if (!option_dict.browser) {
// directive_list.forEach(function (comment) {
// if (comment.directive === "global") {
//
// // test_cause:
// // ["/*global aa*/", "jslint", "missing_browser", "(comment)", 1]
//
// warn("missing_browser", comment);
// }
// });
// }
if (option_dict.test_internal_error) {
jslint_assert(undefined, "test_internal_error");
}
} catch (err) {
mode_stop = true;
err.message = "[JSLint was unable to finish] " + err.message;
err.mode_stop = true;
if (err.name !== "JSLintError") {
Object.assign(err, {
column: jslint_fudge,
line: jslint_fudge,
line_source: "",
stack_trace: err.stack
});
}
if (warning_list.indexOf(err) === -1) {
warning_list.push(err);
}
}
// Sort warning_list by mode_stop first, line, column respectively.
warning_list.sort(function (aa, bb) {
return (
Boolean(bb.mode_stop) - Boolean(aa.mode_stop)
|| aa.line - bb.line
|| aa.column - bb.column
);
// Update each warning with formatted_message ready-for-use by jslint_cli.
}).map(function ({
column,
line,
line_source,
message,
stack_trace = ""
}, ii, list) {
list[ii].formatted_message = String(
String(ii + 1).padStart(2, " ")
+ ". \u001b[31m" + message + "\u001b[39m"
+ " \u001b[90m\/\/ line " + line + ", column " + column
+ "\u001b[39m\n"
+ (" " + line_source.trim()).slice(0, 72) + "\n"
+ stack_trace
).trimRight();
});
return {
causes: cause_dict,
directives: directive_list,
edition: jslint_edition,
exports: export_dict,
froms: import_list,
functions: function_list,
global: token_global,
id: "(JSLint)",
json: state.mode_json,
lines: line_list,
module: state.mode_module === true,
ok: warning_list.length === 0 && !mode_stop,
option: option_dict,
property: property_dict,
shebang: (
state.mode_shebang
? line_list[jslint_fudge].line_source
: undefined
),
stop: mode_stop,
tokens: token_list,
tree: state.token_tree,
warnings: warning_list
};
}
// PR-362 - Add API Doc.
async function jslint_apidoc({
example_list,
github_repo,
module_list,
package_name,
pathname,
version
}) {
// This function will create API Doc from <module_list>.
let elem_ii = 0;
let html;
function elem_create(moduleObj, key, moduleName) {
// This function will create a sub API Doc from elem <moduleObj>[<key>].
let example = "N/A";
let id = encodeURIComponent("apidoc.elem." + moduleName + "." + key);
let name;
let signature;
let source;
name = htmlEscape((typeof moduleObj[key]) + " " + key);
if (typeof moduleObj[key] !== "function") {
return {
name,
signature: (`
<a class="apidocElementLiA" href="#${id}">
${name}
</a>
`),
source: (`
<li>
<h2>
<a href="#${id}" id="${id}">
${name}
</a>
</h2>
</li>
`)
};
}
// init source
source = htmlEscape(trim_start(moduleObj[key].toString()));
// init signature
source = source.replace((
/(\([\S\s]*?\)) \{/
), function (match0, match1) {
signature = htmlEscape(
match1.replace((
/ *?\/\*[\S\s]*?\*\/ */g
), "").replace((
/ *?\/\/.*/g
), "").replace((
/\n{2,}/g
), "\n")
);
return match0;
});
// init comment
source = source.replace((
/\n(?:\/\/.*?\n)+\n/
), "<span class=\"apidocCodeCommentSpan\">$&</span>");
// init example
example_list.some(function (example2) {
example2.replace(
new RegExp((
"((?:\\n.*?){8}(function )?)\\b"
+ key
+ "(\\((?:.*?\\n){8})"
), "g"),
function (ignore, header, isDeclaration, footer) {
if (!isDeclaration) {
example = "..." + trim_start(
htmlEscape(header)
+ "<span class=\"apidocCodeKeywordSpan\">"
+ htmlEscape(key)
+ "</span>"
+ htmlEscape(footer)
).trimEnd() + "\n...";
}
return "";
}
);
return example !== "N/A";
});
if (source.length > 2048) {
source = source.slice(0, 2048) + "...\n}\n";
}
return {
name,
signature: (`
<a class="apidocElementLiA" href="#${id}">
${name}<span class="apidocSignatureSpan">${signature}</span>
</a>
`),
source: (`
<li>
<h2>
<a href="#${id}" id="${id}">
${name}<span class="apidocSignatureSpan">${signature}</span>
</a>
</h2>
</li>
<li>Description and source-code:<pre class="apidocCodePre">${source}</pre></li>
<li>Example usage:<pre class="apidocCodePre">${example}</pre></li>
`)
};
}
function trim_start(str) {
// This function will normalize whitespace before <str>.
let whitespace = "";
str.trim().replace((
/^ */gm
), function (match0) {
if (whitespace === "" || match0.length < whitespace.length) {
whitespace = match0;
}
return "";
});
str = str.replace(new RegExp("^" + whitespace, "gm"), "");
return str;
}
await moduleFsInit();
// Html-escape params.
github_repo = htmlEscape(github_repo);
package_name = htmlEscape(package_name);
version = htmlEscape(version);
// Init example_list.
example_list = await Promise.all(example_list.map(async function (file) {
// This function will read example from given file.
let result = await moduleFs.promises.readFile(file, "utf8");
result = (
"\n\n\n\n\n\n\n\n"
// bug-workaround - truncate example to manageable size
+ result.slice(0, 524288)
+ "\n\n\n\n\n\n\n\n"
);
result = result.replace((
/\r\n*/g
), "\n");
return result;
}));
// init module_list
module_list = await Promise.all(module_list.map(async function ({