2023-03-20 19:52:55 +01:00
|
|
|
import katex from "katex";
|
2020-07-25 02:02:35 +02:00
|
|
|
import _ from "lodash";
|
2020-07-23 22:41:45 +02:00
|
|
|
|
2014-01-27 23:20:50 +01:00
|
|
|
// Parsing routine that can be dropped in to message parsing
|
|
|
|
// and formats code blocks
|
|
|
|
//
|
|
|
|
// This supports arbitrarily nested code blocks as well as
|
|
|
|
// auto-completing code blocks missing a trailing close.
|
|
|
|
|
|
|
|
// See backend fenced_code.py:71 for associated regexp
|
2020-07-15 00:34:28 +02:00
|
|
|
const fencestr =
|
2021-05-10 07:02:14 +02:00
|
|
|
"^(~{3,}|`{3,})" + // Opening fence
|
2020-07-15 00:34:28 +02:00
|
|
|
"[ ]*" + // Spaces
|
|
|
|
"(" +
|
|
|
|
"\\{?\\.?" +
|
|
|
|
"([a-zA-Z0-9_+-./#]*)" + // Language
|
|
|
|
"\\}?" +
|
|
|
|
")" +
|
|
|
|
"[ ]*" + // Spaces
|
|
|
|
"(" +
|
|
|
|
"\\{?\\.?" +
|
|
|
|
"([^~`]*)" + // Header (see fenced_code.py)
|
|
|
|
"\\}?" +
|
|
|
|
")" +
|
|
|
|
"$";
|
2019-11-02 00:06:25 +01:00
|
|
|
const fence_re = new RegExp(fencestr);
|
2014-01-27 23:20:50 +01:00
|
|
|
|
|
|
|
// Default stashing function does nothing
|
2019-11-02 00:06:25 +01:00
|
|
|
let stash_func = function (text) {
|
2014-01-27 23:20:50 +01:00
|
|
|
return text;
|
|
|
|
};
|
|
|
|
|
2020-09-26 06:02:29 +02:00
|
|
|
// We fill up the actual values when initializing.
|
|
|
|
let pygments_data = {};
|
|
|
|
|
|
|
|
export function initialize(generated_pygments_data) {
|
|
|
|
pygments_data = generated_pygments_data.langs;
|
|
|
|
}
|
|
|
|
|
2020-09-06 11:52:11 +02:00
|
|
|
export function wrap_code(code, lang) {
|
|
|
|
let header = '<div class="codehilite"><pre><span></span><code>';
|
|
|
|
// Mimics the backend logic of adding a data-attribute (data-code-language)
|
|
|
|
// to know what Pygments language was used to highlight this code block.
|
2020-09-26 06:02:29 +02:00
|
|
|
//
|
|
|
|
// NOTE: Clients like zulip-mobile wouldn't receive the pygments data since that comes from outside
|
|
|
|
// the `/shared` folder. To handle such a case we check if pygments data is empty and fallback to
|
|
|
|
// using the default header if it is.
|
|
|
|
if (lang !== undefined && lang !== "" && Object.keys(pygments_data).length > 0) {
|
2022-01-24 09:27:28 +01:00
|
|
|
const code_language = pygments_data[lang]?.pretty_name ?? lang;
|
2020-11-03 00:44:17 +01:00
|
|
|
header = `<div class="codehilite" data-code-language="${_.escape(
|
|
|
|
code_language,
|
|
|
|
)}"><pre><span></span><code>`;
|
2020-09-06 11:52:11 +02:00
|
|
|
}
|
2014-01-27 23:20:50 +01:00
|
|
|
// Trim trailing \n until there's just one left
|
|
|
|
// This mirrors how pygments handles code input
|
2020-10-19 04:49:17 +02:00
|
|
|
return header + _.escape(code.replace(/^\n+|\n+$/g, "")) + "\n</code></pre></div>";
|
2020-07-23 22:41:45 +02:00
|
|
|
}
|
2014-01-27 23:20:50 +01:00
|
|
|
|
|
|
|
function wrap_quote(text) {
|
2020-10-30 20:10:29 +01:00
|
|
|
const paragraphs = text.split("\n");
|
2019-11-02 00:06:25 +01:00
|
|
|
const quoted_paragraphs = [];
|
js: Automatically convert _.each to for…of.
This commit was automatically generated by the following script,
followed by lint --fix and a few small manual lint-related cleanups.
import * as babelParser from "recast/parsers/babel";
import * as recast from "recast";
import * as tsParser from "recast/parsers/typescript";
import { builders as b, namedTypes as n } from "ast-types";
import { Context } from "ast-types/lib/path-visitor";
import K from "ast-types/gen/kinds";
import { NodePath } from "ast-types/lib/node-path";
import assert from "assert";
import fs from "fs";
import path from "path";
import process from "process";
const checkExpression = (node: n.Node): node is K.ExpressionKind =>
n.Expression.check(node);
const checkStatement = (node: n.Node): node is K.StatementKind =>
n.Statement.check(node);
for (const file of process.argv.slice(2)) {
console.log("Parsing", file);
const ast = recast.parse(fs.readFileSync(file, { encoding: "utf8" }), {
parser: path.extname(file) === ".ts" ? tsParser : babelParser,
});
let changed = false;
let inLoop = false;
let replaceReturn = false;
const visitLoop = (...args: string[]) =>
function(this: Context, path: NodePath) {
for (const arg of args) {
this.visit(path.get(arg));
}
const old = { inLoop };
inLoop = true;
this.visit(path.get("body"));
inLoop = old.inLoop;
return false;
};
recast.visit(ast, {
visitDoWhileStatement: visitLoop("test"),
visitExpressionStatement(path) {
const { expression, comments } = path.node;
let valueOnly;
if (
n.CallExpression.check(expression) &&
n.MemberExpression.check(expression.callee) &&
!expression.callee.computed &&
n.Identifier.check(expression.callee.object) &&
expression.callee.object.name === "_" &&
n.Identifier.check(expression.callee.property) &&
["each", "forEach"].includes(expression.callee.property.name) &&
[2, 3].includes(expression.arguments.length) &&
checkExpression(expression.arguments[0]) &&
(n.FunctionExpression.check(expression.arguments[1]) ||
n.ArrowFunctionExpression.check(expression.arguments[1])) &&
[1, 2].includes(expression.arguments[1].params.length) &&
n.Identifier.check(expression.arguments[1].params[0]) &&
((valueOnly = expression.arguments[1].params[1] === undefined) ||
n.Identifier.check(expression.arguments[1].params[1])) &&
(expression.arguments[2] === undefined ||
n.ThisExpression.check(expression.arguments[2]))
) {
const old = { inLoop, replaceReturn };
inLoop = false;
replaceReturn = true;
this.visit(
path
.get("expression")
.get("arguments")
.get(1)
.get("body")
);
inLoop = old.inLoop;
replaceReturn = old.replaceReturn;
const [right, { body, params }] = expression.arguments;
const loop = b.forOfStatement(
b.variableDeclaration("let", [
b.variableDeclarator(
valueOnly ? params[0] : b.arrayPattern([params[1], params[0]])
),
]),
valueOnly
? right
: b.callExpression(
b.memberExpression(right, b.identifier("entries")),
[]
),
checkStatement(body) ? body : b.expressionStatement(body)
);
loop.comments = comments;
path.replace(loop);
changed = true;
}
this.traverse(path);
},
visitForStatement: visitLoop("init", "test", "update"),
visitForInStatement: visitLoop("left", "right"),
visitForOfStatement: visitLoop("left", "right"),
visitFunction(path) {
this.visit(path.get("params"));
const old = { replaceReturn };
replaceReturn = false;
this.visit(path.get("body"));
replaceReturn = old.replaceReturn;
return false;
},
visitReturnStatement(path) {
if (replaceReturn) {
assert(!inLoop); // could use labeled continue if this ever fires
const { argument, comments } = path.node;
if (argument === null) {
const s = b.continueStatement();
s.comments = comments;
path.replace(s);
} else {
const s = b.expressionStatement(argument);
s.comments = comments;
path.replace(s, b.continueStatement());
}
return false;
}
this.traverse(path);
},
visitWhileStatement: visitLoop("test"),
});
if (changed) {
console.log("Writing", file);
fs.writeFileSync(file, recast.print(ast).code, { encoding: "utf8" });
}
}
Signed-off-by: Anders Kaseorg <anders@zulipchat.com>
2020-02-06 06:19:47 +01:00
|
|
|
|
2014-01-27 23:20:50 +01:00
|
|
|
// Prefix each quoted paragraph with > at the
|
|
|
|
// beginning of each line
|
js: Automatically convert _.each to for…of.
This commit was automatically generated by the following script,
followed by lint --fix and a few small manual lint-related cleanups.
import * as babelParser from "recast/parsers/babel";
import * as recast from "recast";
import * as tsParser from "recast/parsers/typescript";
import { builders as b, namedTypes as n } from "ast-types";
import { Context } from "ast-types/lib/path-visitor";
import K from "ast-types/gen/kinds";
import { NodePath } from "ast-types/lib/node-path";
import assert from "assert";
import fs from "fs";
import path from "path";
import process from "process";
const checkExpression = (node: n.Node): node is K.ExpressionKind =>
n.Expression.check(node);
const checkStatement = (node: n.Node): node is K.StatementKind =>
n.Statement.check(node);
for (const file of process.argv.slice(2)) {
console.log("Parsing", file);
const ast = recast.parse(fs.readFileSync(file, { encoding: "utf8" }), {
parser: path.extname(file) === ".ts" ? tsParser : babelParser,
});
let changed = false;
let inLoop = false;
let replaceReturn = false;
const visitLoop = (...args: string[]) =>
function(this: Context, path: NodePath) {
for (const arg of args) {
this.visit(path.get(arg));
}
const old = { inLoop };
inLoop = true;
this.visit(path.get("body"));
inLoop = old.inLoop;
return false;
};
recast.visit(ast, {
visitDoWhileStatement: visitLoop("test"),
visitExpressionStatement(path) {
const { expression, comments } = path.node;
let valueOnly;
if (
n.CallExpression.check(expression) &&
n.MemberExpression.check(expression.callee) &&
!expression.callee.computed &&
n.Identifier.check(expression.callee.object) &&
expression.callee.object.name === "_" &&
n.Identifier.check(expression.callee.property) &&
["each", "forEach"].includes(expression.callee.property.name) &&
[2, 3].includes(expression.arguments.length) &&
checkExpression(expression.arguments[0]) &&
(n.FunctionExpression.check(expression.arguments[1]) ||
n.ArrowFunctionExpression.check(expression.arguments[1])) &&
[1, 2].includes(expression.arguments[1].params.length) &&
n.Identifier.check(expression.arguments[1].params[0]) &&
((valueOnly = expression.arguments[1].params[1] === undefined) ||
n.Identifier.check(expression.arguments[1].params[1])) &&
(expression.arguments[2] === undefined ||
n.ThisExpression.check(expression.arguments[2]))
) {
const old = { inLoop, replaceReturn };
inLoop = false;
replaceReturn = true;
this.visit(
path
.get("expression")
.get("arguments")
.get(1)
.get("body")
);
inLoop = old.inLoop;
replaceReturn = old.replaceReturn;
const [right, { body, params }] = expression.arguments;
const loop = b.forOfStatement(
b.variableDeclaration("let", [
b.variableDeclarator(
valueOnly ? params[0] : b.arrayPattern([params[1], params[0]])
),
]),
valueOnly
? right
: b.callExpression(
b.memberExpression(right, b.identifier("entries")),
[]
),
checkStatement(body) ? body : b.expressionStatement(body)
);
loop.comments = comments;
path.replace(loop);
changed = true;
}
this.traverse(path);
},
visitForStatement: visitLoop("init", "test", "update"),
visitForInStatement: visitLoop("left", "right"),
visitForOfStatement: visitLoop("left", "right"),
visitFunction(path) {
this.visit(path.get("params"));
const old = { replaceReturn };
replaceReturn = false;
this.visit(path.get("body"));
replaceReturn = old.replaceReturn;
return false;
},
visitReturnStatement(path) {
if (replaceReturn) {
assert(!inLoop); // could use labeled continue if this ever fires
const { argument, comments } = path.node;
if (argument === null) {
const s = b.continueStatement();
s.comments = comments;
path.replace(s);
} else {
const s = b.expressionStatement(argument);
s.comments = comments;
path.replace(s, b.continueStatement());
}
return false;
}
this.traverse(path);
},
visitWhileStatement: visitLoop("test"),
});
if (changed) {
console.log("Writing", file);
fs.writeFileSync(file, recast.print(ast).code, { encoding: "utf8" });
}
}
Signed-off-by: Anders Kaseorg <anders@zulipchat.com>
2020-02-06 06:19:47 +01:00
|
|
|
for (const paragraph of paragraphs) {
|
2020-07-15 01:29:15 +02:00
|
|
|
const lines = paragraph.split("\n");
|
2020-10-30 20:10:29 +01:00
|
|
|
quoted_paragraphs.push(lines.map((line) => "> " + line).join("\n"));
|
js: Automatically convert _.each to for…of.
This commit was automatically generated by the following script,
followed by lint --fix and a few small manual lint-related cleanups.
import * as babelParser from "recast/parsers/babel";
import * as recast from "recast";
import * as tsParser from "recast/parsers/typescript";
import { builders as b, namedTypes as n } from "ast-types";
import { Context } from "ast-types/lib/path-visitor";
import K from "ast-types/gen/kinds";
import { NodePath } from "ast-types/lib/node-path";
import assert from "assert";
import fs from "fs";
import path from "path";
import process from "process";
const checkExpression = (node: n.Node): node is K.ExpressionKind =>
n.Expression.check(node);
const checkStatement = (node: n.Node): node is K.StatementKind =>
n.Statement.check(node);
for (const file of process.argv.slice(2)) {
console.log("Parsing", file);
const ast = recast.parse(fs.readFileSync(file, { encoding: "utf8" }), {
parser: path.extname(file) === ".ts" ? tsParser : babelParser,
});
let changed = false;
let inLoop = false;
let replaceReturn = false;
const visitLoop = (...args: string[]) =>
function(this: Context, path: NodePath) {
for (const arg of args) {
this.visit(path.get(arg));
}
const old = { inLoop };
inLoop = true;
this.visit(path.get("body"));
inLoop = old.inLoop;
return false;
};
recast.visit(ast, {
visitDoWhileStatement: visitLoop("test"),
visitExpressionStatement(path) {
const { expression, comments } = path.node;
let valueOnly;
if (
n.CallExpression.check(expression) &&
n.MemberExpression.check(expression.callee) &&
!expression.callee.computed &&
n.Identifier.check(expression.callee.object) &&
expression.callee.object.name === "_" &&
n.Identifier.check(expression.callee.property) &&
["each", "forEach"].includes(expression.callee.property.name) &&
[2, 3].includes(expression.arguments.length) &&
checkExpression(expression.arguments[0]) &&
(n.FunctionExpression.check(expression.arguments[1]) ||
n.ArrowFunctionExpression.check(expression.arguments[1])) &&
[1, 2].includes(expression.arguments[1].params.length) &&
n.Identifier.check(expression.arguments[1].params[0]) &&
((valueOnly = expression.arguments[1].params[1] === undefined) ||
n.Identifier.check(expression.arguments[1].params[1])) &&
(expression.arguments[2] === undefined ||
n.ThisExpression.check(expression.arguments[2]))
) {
const old = { inLoop, replaceReturn };
inLoop = false;
replaceReturn = true;
this.visit(
path
.get("expression")
.get("arguments")
.get(1)
.get("body")
);
inLoop = old.inLoop;
replaceReturn = old.replaceReturn;
const [right, { body, params }] = expression.arguments;
const loop = b.forOfStatement(
b.variableDeclaration("let", [
b.variableDeclarator(
valueOnly ? params[0] : b.arrayPattern([params[1], params[0]])
),
]),
valueOnly
? right
: b.callExpression(
b.memberExpression(right, b.identifier("entries")),
[]
),
checkStatement(body) ? body : b.expressionStatement(body)
);
loop.comments = comments;
path.replace(loop);
changed = true;
}
this.traverse(path);
},
visitForStatement: visitLoop("init", "test", "update"),
visitForInStatement: visitLoop("left", "right"),
visitForOfStatement: visitLoop("left", "right"),
visitFunction(path) {
this.visit(path.get("params"));
const old = { replaceReturn };
replaceReturn = false;
this.visit(path.get("body"));
replaceReturn = old.replaceReturn;
return false;
},
visitReturnStatement(path) {
if (replaceReturn) {
assert(!inLoop); // could use labeled continue if this ever fires
const { argument, comments } = path.node;
if (argument === null) {
const s = b.continueStatement();
s.comments = comments;
path.replace(s);
} else {
const s = b.expressionStatement(argument);
s.comments = comments;
path.replace(s, b.continueStatement());
}
return false;
}
this.traverse(path);
},
visitWhileStatement: visitLoop("test"),
});
if (changed) {
console.log("Writing", file);
fs.writeFileSync(file, recast.print(ast).code, { encoding: "utf8" });
}
}
Signed-off-by: Anders Kaseorg <anders@zulipchat.com>
2020-02-06 06:19:47 +01:00
|
|
|
}
|
|
|
|
|
2020-10-30 20:10:29 +01:00
|
|
|
return quoted_paragraphs.join("\n");
|
2014-01-27 23:20:50 +01:00
|
|
|
}
|
|
|
|
|
2017-03-20 16:56:39 +01:00
|
|
|
function wrap_tex(tex) {
|
|
|
|
try {
|
2021-05-02 00:06:30 +02:00
|
|
|
return "<p>" + katex.renderToString(tex, {displayMode: true}) + "</p>";
|
2020-10-07 10:18:48 +02:00
|
|
|
} catch {
|
2021-05-02 00:06:30 +02:00
|
|
|
return '<p><span class="tex-error">' + _.escape(tex) + "</span></p>";
|
2017-03-20 16:56:39 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-04-04 22:14:34 +02:00
|
|
|
function wrap_spoiler(header, text, stash_func) {
|
|
|
|
const header_div_open_html = '<div class="spoiler-block"><div class="spoiler-header">';
|
|
|
|
const end_header_start_content_html = '</div><div class="spoiler-content" aria-hidden="true">';
|
2020-07-15 01:29:15 +02:00
|
|
|
const footer_html = "</div></div>";
|
2020-04-04 22:14:34 +02:00
|
|
|
|
2021-01-23 02:46:57 +01:00
|
|
|
const output = [
|
|
|
|
stash_func(header_div_open_html),
|
|
|
|
header,
|
|
|
|
stash_func(end_header_start_content_html),
|
|
|
|
text,
|
|
|
|
stash_func(footer_html),
|
|
|
|
];
|
2020-04-04 22:14:34 +02:00
|
|
|
return output.join("\n\n");
|
|
|
|
}
|
|
|
|
|
2020-07-23 22:41:45 +02:00
|
|
|
export function set_stash_func(stash_handler) {
|
2014-01-27 23:20:50 +01:00
|
|
|
stash_func = stash_handler;
|
2020-07-23 22:41:45 +02:00
|
|
|
}
|
2014-01-27 23:20:50 +01:00
|
|
|
|
2020-07-23 22:41:45 +02:00
|
|
|
export function process_fenced_code(content) {
|
2020-07-15 01:29:15 +02:00
|
|
|
const input = content.split("\n");
|
2019-11-02 00:06:25 +01:00
|
|
|
const output = [];
|
|
|
|
const handler_stack = [];
|
|
|
|
let consume_line;
|
2014-01-27 23:20:50 +01:00
|
|
|
|
2020-04-04 22:14:34 +02:00
|
|
|
function handler_for_fence(output_lines, fence, lang, header) {
|
2014-01-27 23:20:50 +01:00
|
|
|
// lang is ignored except for 'quote', as we
|
|
|
|
// don't do syntax highlighting yet
|
2021-05-06 21:49:45 +02:00
|
|
|
const lines = [];
|
|
|
|
if (lang === "quote") {
|
|
|
|
return {
|
|
|
|
handle_line(line) {
|
|
|
|
if (line === fence) {
|
|
|
|
this.done();
|
|
|
|
} else {
|
|
|
|
consume_line(lines, line);
|
|
|
|
}
|
|
|
|
},
|
2020-04-04 22:14:34 +02:00
|
|
|
|
2021-05-06 21:49:45 +02:00
|
|
|
done() {
|
|
|
|
const text = wrap_quote(lines.join("\n"));
|
|
|
|
output_lines.push("", text, "");
|
|
|
|
handler_stack.pop();
|
|
|
|
},
|
|
|
|
};
|
|
|
|
}
|
|
|
|
|
|
|
|
if (lang === "math") {
|
2016-12-02 21:34:35 +01:00
|
|
|
return {
|
2020-07-20 22:18:43 +02:00
|
|
|
handle_line(line) {
|
2016-12-02 21:34:35 +01:00
|
|
|
if (line === fence) {
|
|
|
|
this.done();
|
|
|
|
} else {
|
2021-05-06 21:49:45 +02:00
|
|
|
lines.push(line);
|
2016-12-02 21:34:35 +01:00
|
|
|
}
|
|
|
|
},
|
|
|
|
|
2020-07-20 22:18:43 +02:00
|
|
|
done() {
|
2021-05-06 21:49:45 +02:00
|
|
|
const text = wrap_tex(lines.join("\n"));
|
2019-11-02 00:06:25 +01:00
|
|
|
const placeholder = stash_func(text, true);
|
2021-01-23 02:46:57 +01:00
|
|
|
output_lines.push("", placeholder, "");
|
2016-12-02 21:34:35 +01:00
|
|
|
handler_stack.pop();
|
2017-01-12 00:17:43 +01:00
|
|
|
},
|
2016-12-02 21:34:35 +01:00
|
|
|
};
|
2021-05-06 21:49:45 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
if (lang === "spoiler") {
|
|
|
|
return {
|
|
|
|
handle_line(line) {
|
|
|
|
if (line === fence) {
|
|
|
|
this.done();
|
|
|
|
} else {
|
|
|
|
lines.push(line);
|
|
|
|
}
|
|
|
|
},
|
|
|
|
|
|
|
|
done() {
|
|
|
|
const text = wrap_spoiler(header, lines.join("\n"), stash_func);
|
|
|
|
output_lines.push("", text, "");
|
|
|
|
handler_stack.pop();
|
|
|
|
},
|
|
|
|
};
|
|
|
|
}
|
|
|
|
|
|
|
|
return {
|
|
|
|
handle_line(line) {
|
|
|
|
if (line === fence) {
|
|
|
|
this.done();
|
|
|
|
} else {
|
|
|
|
lines.push(line.trimEnd());
|
|
|
|
}
|
|
|
|
},
|
|
|
|
|
|
|
|
done() {
|
|
|
|
const text = wrap_code(lines.join("\n"), lang);
|
|
|
|
// insert safe HTML that is passed through the parsing
|
|
|
|
const placeholder = stash_func(text, true);
|
|
|
|
output_lines.push("", placeholder, "");
|
|
|
|
handler_stack.pop();
|
|
|
|
},
|
|
|
|
};
|
2014-01-27 23:20:50 +01:00
|
|
|
}
|
|
|
|
|
2021-12-28 18:19:57 +01:00
|
|
|
function default_handler() {
|
2014-01-27 23:20:50 +01:00
|
|
|
return {
|
2020-07-20 22:18:43 +02:00
|
|
|
handle_line(line) {
|
2014-01-27 23:20:50 +01:00
|
|
|
consume_line(output, line);
|
|
|
|
},
|
2020-07-20 22:18:43 +02:00
|
|
|
done() {
|
2014-01-27 23:20:50 +01:00
|
|
|
handler_stack.pop();
|
2017-01-12 00:17:43 +01:00
|
|
|
},
|
2014-01-27 23:20:50 +01:00
|
|
|
};
|
|
|
|
}
|
|
|
|
|
|
|
|
consume_line = function consume_line(output_lines, line) {
|
2019-11-02 00:06:25 +01:00
|
|
|
const match = fence_re.exec(line);
|
2014-01-27 23:20:50 +01:00
|
|
|
if (match) {
|
2019-11-02 00:06:25 +01:00
|
|
|
const fence = match[1];
|
|
|
|
const lang = match[3];
|
2020-04-04 22:14:34 +02:00
|
|
|
const header = match[5];
|
|
|
|
const handler = handler_for_fence(output_lines, fence, lang, header);
|
2014-01-27 23:20:50 +01:00
|
|
|
handler_stack.push(handler);
|
|
|
|
} else {
|
|
|
|
output_lines.push(line);
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
2021-12-28 18:19:57 +01:00
|
|
|
const current_handler = default_handler();
|
2014-01-27 23:20:50 +01:00
|
|
|
handler_stack.push(current_handler);
|
|
|
|
|
js: Automatically convert _.each to for…of.
This commit was automatically generated by the following script,
followed by lint --fix and a few small manual lint-related cleanups.
import * as babelParser from "recast/parsers/babel";
import * as recast from "recast";
import * as tsParser from "recast/parsers/typescript";
import { builders as b, namedTypes as n } from "ast-types";
import { Context } from "ast-types/lib/path-visitor";
import K from "ast-types/gen/kinds";
import { NodePath } from "ast-types/lib/node-path";
import assert from "assert";
import fs from "fs";
import path from "path";
import process from "process";
const checkExpression = (node: n.Node): node is K.ExpressionKind =>
n.Expression.check(node);
const checkStatement = (node: n.Node): node is K.StatementKind =>
n.Statement.check(node);
for (const file of process.argv.slice(2)) {
console.log("Parsing", file);
const ast = recast.parse(fs.readFileSync(file, { encoding: "utf8" }), {
parser: path.extname(file) === ".ts" ? tsParser : babelParser,
});
let changed = false;
let inLoop = false;
let replaceReturn = false;
const visitLoop = (...args: string[]) =>
function(this: Context, path: NodePath) {
for (const arg of args) {
this.visit(path.get(arg));
}
const old = { inLoop };
inLoop = true;
this.visit(path.get("body"));
inLoop = old.inLoop;
return false;
};
recast.visit(ast, {
visitDoWhileStatement: visitLoop("test"),
visitExpressionStatement(path) {
const { expression, comments } = path.node;
let valueOnly;
if (
n.CallExpression.check(expression) &&
n.MemberExpression.check(expression.callee) &&
!expression.callee.computed &&
n.Identifier.check(expression.callee.object) &&
expression.callee.object.name === "_" &&
n.Identifier.check(expression.callee.property) &&
["each", "forEach"].includes(expression.callee.property.name) &&
[2, 3].includes(expression.arguments.length) &&
checkExpression(expression.arguments[0]) &&
(n.FunctionExpression.check(expression.arguments[1]) ||
n.ArrowFunctionExpression.check(expression.arguments[1])) &&
[1, 2].includes(expression.arguments[1].params.length) &&
n.Identifier.check(expression.arguments[1].params[0]) &&
((valueOnly = expression.arguments[1].params[1] === undefined) ||
n.Identifier.check(expression.arguments[1].params[1])) &&
(expression.arguments[2] === undefined ||
n.ThisExpression.check(expression.arguments[2]))
) {
const old = { inLoop, replaceReturn };
inLoop = false;
replaceReturn = true;
this.visit(
path
.get("expression")
.get("arguments")
.get(1)
.get("body")
);
inLoop = old.inLoop;
replaceReturn = old.replaceReturn;
const [right, { body, params }] = expression.arguments;
const loop = b.forOfStatement(
b.variableDeclaration("let", [
b.variableDeclarator(
valueOnly ? params[0] : b.arrayPattern([params[1], params[0]])
),
]),
valueOnly
? right
: b.callExpression(
b.memberExpression(right, b.identifier("entries")),
[]
),
checkStatement(body) ? body : b.expressionStatement(body)
);
loop.comments = comments;
path.replace(loop);
changed = true;
}
this.traverse(path);
},
visitForStatement: visitLoop("init", "test", "update"),
visitForInStatement: visitLoop("left", "right"),
visitForOfStatement: visitLoop("left", "right"),
visitFunction(path) {
this.visit(path.get("params"));
const old = { replaceReturn };
replaceReturn = false;
this.visit(path.get("body"));
replaceReturn = old.replaceReturn;
return false;
},
visitReturnStatement(path) {
if (replaceReturn) {
assert(!inLoop); // could use labeled continue if this ever fires
const { argument, comments } = path.node;
if (argument === null) {
const s = b.continueStatement();
s.comments = comments;
path.replace(s);
} else {
const s = b.expressionStatement(argument);
s.comments = comments;
path.replace(s, b.continueStatement());
}
return false;
}
this.traverse(path);
},
visitWhileStatement: visitLoop("test"),
});
if (changed) {
console.log("Writing", file);
fs.writeFileSync(file, recast.print(ast).code, { encoding: "utf8" });
}
}
Signed-off-by: Anders Kaseorg <anders@zulipchat.com>
2020-02-06 06:19:47 +01:00
|
|
|
for (const line of input) {
|
2022-01-24 09:05:06 +01:00
|
|
|
const handler = handler_stack.at(-1);
|
2014-01-27 23:20:50 +01:00
|
|
|
handler.handle_line(line);
|
js: Automatically convert _.each to for…of.
This commit was automatically generated by the following script,
followed by lint --fix and a few small manual lint-related cleanups.
import * as babelParser from "recast/parsers/babel";
import * as recast from "recast";
import * as tsParser from "recast/parsers/typescript";
import { builders as b, namedTypes as n } from "ast-types";
import { Context } from "ast-types/lib/path-visitor";
import K from "ast-types/gen/kinds";
import { NodePath } from "ast-types/lib/node-path";
import assert from "assert";
import fs from "fs";
import path from "path";
import process from "process";
const checkExpression = (node: n.Node): node is K.ExpressionKind =>
n.Expression.check(node);
const checkStatement = (node: n.Node): node is K.StatementKind =>
n.Statement.check(node);
for (const file of process.argv.slice(2)) {
console.log("Parsing", file);
const ast = recast.parse(fs.readFileSync(file, { encoding: "utf8" }), {
parser: path.extname(file) === ".ts" ? tsParser : babelParser,
});
let changed = false;
let inLoop = false;
let replaceReturn = false;
const visitLoop = (...args: string[]) =>
function(this: Context, path: NodePath) {
for (const arg of args) {
this.visit(path.get(arg));
}
const old = { inLoop };
inLoop = true;
this.visit(path.get("body"));
inLoop = old.inLoop;
return false;
};
recast.visit(ast, {
visitDoWhileStatement: visitLoop("test"),
visitExpressionStatement(path) {
const { expression, comments } = path.node;
let valueOnly;
if (
n.CallExpression.check(expression) &&
n.MemberExpression.check(expression.callee) &&
!expression.callee.computed &&
n.Identifier.check(expression.callee.object) &&
expression.callee.object.name === "_" &&
n.Identifier.check(expression.callee.property) &&
["each", "forEach"].includes(expression.callee.property.name) &&
[2, 3].includes(expression.arguments.length) &&
checkExpression(expression.arguments[0]) &&
(n.FunctionExpression.check(expression.arguments[1]) ||
n.ArrowFunctionExpression.check(expression.arguments[1])) &&
[1, 2].includes(expression.arguments[1].params.length) &&
n.Identifier.check(expression.arguments[1].params[0]) &&
((valueOnly = expression.arguments[1].params[1] === undefined) ||
n.Identifier.check(expression.arguments[1].params[1])) &&
(expression.arguments[2] === undefined ||
n.ThisExpression.check(expression.arguments[2]))
) {
const old = { inLoop, replaceReturn };
inLoop = false;
replaceReturn = true;
this.visit(
path
.get("expression")
.get("arguments")
.get(1)
.get("body")
);
inLoop = old.inLoop;
replaceReturn = old.replaceReturn;
const [right, { body, params }] = expression.arguments;
const loop = b.forOfStatement(
b.variableDeclaration("let", [
b.variableDeclarator(
valueOnly ? params[0] : b.arrayPattern([params[1], params[0]])
),
]),
valueOnly
? right
: b.callExpression(
b.memberExpression(right, b.identifier("entries")),
[]
),
checkStatement(body) ? body : b.expressionStatement(body)
);
loop.comments = comments;
path.replace(loop);
changed = true;
}
this.traverse(path);
},
visitForStatement: visitLoop("init", "test", "update"),
visitForInStatement: visitLoop("left", "right"),
visitForOfStatement: visitLoop("left", "right"),
visitFunction(path) {
this.visit(path.get("params"));
const old = { replaceReturn };
replaceReturn = false;
this.visit(path.get("body"));
replaceReturn = old.replaceReturn;
return false;
},
visitReturnStatement(path) {
if (replaceReturn) {
assert(!inLoop); // could use labeled continue if this ever fires
const { argument, comments } = path.node;
if (argument === null) {
const s = b.continueStatement();
s.comments = comments;
path.replace(s);
} else {
const s = b.expressionStatement(argument);
s.comments = comments;
path.replace(s, b.continueStatement());
}
return false;
}
this.traverse(path);
},
visitWhileStatement: visitLoop("test"),
});
if (changed) {
console.log("Writing", file);
fs.writeFileSync(file, recast.print(ast).code, { encoding: "utf8" });
}
}
Signed-off-by: Anders Kaseorg <anders@zulipchat.com>
2020-02-06 06:19:47 +01:00
|
|
|
}
|
2014-01-27 23:20:50 +01:00
|
|
|
|
|
|
|
// Clean up all trailing blocks by letting them
|
|
|
|
// insert closing fences
|
|
|
|
while (handler_stack.length !== 0) {
|
2022-01-24 09:05:06 +01:00
|
|
|
const handler = handler_stack.at(-1);
|
2014-01-27 23:20:50 +01:00
|
|
|
handler.done();
|
|
|
|
}
|
|
|
|
|
2022-01-24 09:05:06 +01:00
|
|
|
if (output.length > 2 && output.at(-2) !== "") {
|
2020-07-15 01:29:15 +02:00
|
|
|
output.push("");
|
2014-01-27 23:20:50 +01:00
|
|
|
}
|
|
|
|
|
2020-07-15 01:29:15 +02:00
|
|
|
return output.join("\n");
|
2020-07-23 22:41:45 +02:00
|
|
|
}
|
2014-01-27 23:20:50 +01:00
|
|
|
|
2019-08-26 09:04:34 +02:00
|
|
|
const fence_length_re = /^ {0,3}(`{3,})/gm;
|
2020-07-23 22:41:45 +02:00
|
|
|
export function get_unused_fence(content) {
|
2019-08-26 09:04:34 +02:00
|
|
|
// we only return ``` fences, not ~~~.
|
|
|
|
let length = 3;
|
|
|
|
let match;
|
|
|
|
fence_length_re.lastIndex = 0;
|
|
|
|
while ((match = fence_length_re.exec(content)) !== null) {
|
|
|
|
length = Math.max(length, match[1].length + 1);
|
|
|
|
}
|
2020-07-15 01:29:15 +02:00
|
|
|
return "`".repeat(length);
|
2020-07-23 22:41:45 +02:00
|
|
|
}
|