mirror of https://github.com/sveltejs/svelte
chore: watch for messages changes in dev generate script (#15950)
* chore: watch for messages changes in dev generate script * no need for this to be async * tweak * guard * only create one timeout --------- Co-authored-by: Rich Harris <rich.harris@vercel.com>pull/15956/head
parent
21bf947ca8
commit
b7b393d50f
@ -1,409 +1,441 @@
|
|||||||
// @ts-check
|
// @ts-check
|
||||||
|
import process from 'node:process';
|
||||||
import fs from 'node:fs';
|
import fs from 'node:fs';
|
||||||
import * as acorn from 'acorn';
|
import * as acorn from 'acorn';
|
||||||
import { walk } from 'zimmerframe';
|
import { walk } from 'zimmerframe';
|
||||||
import * as esrap from 'esrap';
|
import * as esrap from 'esrap';
|
||||||
|
|
||||||
/** @type {Record<string, Record<string, { messages: string[], details: string | null }>>} */
|
|
||||||
const messages = {};
|
|
||||||
const seen = new Set();
|
|
||||||
|
|
||||||
const DIR = '../../documentation/docs/98-reference/.generated';
|
const DIR = '../../documentation/docs/98-reference/.generated';
|
||||||
fs.rmSync(DIR, { force: true, recursive: true });
|
|
||||||
fs.mkdirSync(DIR);
|
|
||||||
|
|
||||||
for (const category of fs.readdirSync('messages')) {
|
const watch = process.argv.includes('-w');
|
||||||
if (category.startsWith('.')) continue;
|
|
||||||
|
|
||||||
messages[category] = {};
|
function run() {
|
||||||
|
/** @type {Record<string, Record<string, { messages: string[], details: string | null }>>} */
|
||||||
|
const messages = {};
|
||||||
|
const seen = new Set();
|
||||||
|
|
||||||
for (const file of fs.readdirSync(`messages/${category}`)) {
|
fs.rmSync(DIR, { force: true, recursive: true });
|
||||||
if (!file.endsWith('.md')) continue;
|
fs.mkdirSync(DIR);
|
||||||
|
|
||||||
const markdown = fs
|
for (const category of fs.readdirSync('messages')) {
|
||||||
.readFileSync(`messages/${category}/${file}`, 'utf-8')
|
if (category.startsWith('.')) continue;
|
||||||
.replace(/\r\n/g, '\n');
|
|
||||||
|
|
||||||
const sorted = [];
|
messages[category] = {};
|
||||||
|
|
||||||
for (const match of markdown.matchAll(/## ([\w]+)\n\n([^]+?)(?=$|\n\n## )/g)) {
|
for (const file of fs.readdirSync(`messages/${category}`)) {
|
||||||
const [_, code, text] = match;
|
if (!file.endsWith('.md')) continue;
|
||||||
|
|
||||||
if (seen.has(code)) {
|
const markdown = fs
|
||||||
throw new Error(`Duplicate message code ${category}/${code}`);
|
.readFileSync(`messages/${category}/${file}`, 'utf-8')
|
||||||
}
|
.replace(/\r\n/g, '\n');
|
||||||
|
|
||||||
sorted.push({ code, _ });
|
const sorted = [];
|
||||||
|
|
||||||
const sections = text.trim().split('\n\n');
|
for (const match of markdown.matchAll(/## ([\w]+)\n\n([^]+?)(?=$|\n\n## )/g)) {
|
||||||
const details = [];
|
const [_, code, text] = match;
|
||||||
|
|
||||||
while (!sections[sections.length - 1].startsWith('> ')) {
|
if (seen.has(code)) {
|
||||||
details.unshift(/** @type {string} */ (sections.pop()));
|
throw new Error(`Duplicate message code ${category}/${code}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
sorted.push({ code, _ });
|
||||||
|
|
||||||
if (sections.length === 0) {
|
const sections = text.trim().split('\n\n');
|
||||||
throw new Error('No message text');
|
const details = [];
|
||||||
|
|
||||||
|
while (!sections[sections.length - 1].startsWith('> ')) {
|
||||||
|
details.unshift(/** @type {string} */ (sections.pop()));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (sections.length === 0) {
|
||||||
|
throw new Error('No message text');
|
||||||
|
}
|
||||||
|
|
||||||
|
seen.add(code);
|
||||||
|
messages[category][code] = {
|
||||||
|
messages: sections.map((section) => section.replace(/^> /gm, '').replace(/^>\n/gm, '\n')),
|
||||||
|
details: details.join('\n\n')
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
seen.add(code);
|
sorted.sort((a, b) => (a.code < b.code ? -1 : 1));
|
||||||
messages[category][code] = {
|
|
||||||
messages: sections.map((section) => section.replace(/^> /gm, '').replace(/^>\n/gm, '\n')),
|
fs.writeFileSync(
|
||||||
details: details.join('\n\n')
|
`messages/${category}/${file}`,
|
||||||
};
|
sorted.map((x) => x._.trim()).join('\n\n') + '\n'
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
sorted.sort((a, b) => (a.code < b.code ? -1 : 1));
|
|
||||||
fs.writeFileSync(
|
fs.writeFileSync(
|
||||||
`messages/${category}/${file}`,
|
`${DIR}/${category}.md`,
|
||||||
sorted.map((x) => x._.trim()).join('\n\n') + '\n'
|
'<!-- This file is generated by scripts/process-messages/index.js. Do not edit! -->\n\n' +
|
||||||
|
Object.entries(messages[category])
|
||||||
|
.map(([code, { messages, details }]) => {
|
||||||
|
const chunks = [
|
||||||
|
`### ${code}`,
|
||||||
|
...messages.map((message) => '```\n' + message + '\n```')
|
||||||
|
];
|
||||||
|
|
||||||
|
if (details) {
|
||||||
|
chunks.push(details);
|
||||||
|
}
|
||||||
|
|
||||||
|
return chunks.join('\n\n');
|
||||||
|
})
|
||||||
|
.sort()
|
||||||
|
.join('\n\n') +
|
||||||
|
'\n'
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
fs.writeFileSync(
|
|
||||||
`${DIR}/${category}.md`,
|
|
||||||
'<!-- This file is generated by scripts/process-messages/index.js. Do not edit! -->\n\n' +
|
|
||||||
Object.entries(messages[category])
|
|
||||||
.map(([code, { messages, details }]) => {
|
|
||||||
const chunks = [`### ${code}`, ...messages.map((message) => '```\n' + message + '\n```')];
|
|
||||||
|
|
||||||
if (details) {
|
|
||||||
chunks.push(details);
|
|
||||||
}
|
|
||||||
|
|
||||||
return chunks.join('\n\n');
|
|
||||||
})
|
|
||||||
.sort()
|
|
||||||
.join('\n\n') +
|
|
||||||
'\n'
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param {string} name
|
|
||||||
* @param {string} dest
|
|
||||||
*/
|
|
||||||
function transform(name, dest) {
|
|
||||||
const source = fs
|
|
||||||
.readFileSync(new URL(`./templates/${name}.js`, import.meta.url), 'utf-8')
|
|
||||||
.replace(/\r\n/g, '\n');
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @type {Array<{
|
* @param {string} name
|
||||||
* type: string;
|
* @param {string} dest
|
||||||
* value: string;
|
|
||||||
* start: number;
|
|
||||||
* end: number
|
|
||||||
* }>}
|
|
||||||
*/
|
*/
|
||||||
const comments = [];
|
function transform(name, dest) {
|
||||||
|
const source = fs
|
||||||
let ast = acorn.parse(source, {
|
.readFileSync(new URL(`./templates/${name}.js`, import.meta.url), 'utf-8')
|
||||||
ecmaVersion: 'latest',
|
.replace(/\r\n/g, '\n');
|
||||||
sourceType: 'module',
|
|
||||||
onComment: (block, value, start, end) => {
|
|
||||||
if (block && /\n/.test(value)) {
|
|
||||||
let a = start;
|
|
||||||
while (a > 0 && source[a - 1] !== '\n') a -= 1;
|
|
||||||
|
|
||||||
let b = a;
|
|
||||||
while (/[ \t]/.test(source[b])) b += 1;
|
|
||||||
|
|
||||||
const indentation = source.slice(a, b);
|
|
||||||
value = value.replace(new RegExp(`^${indentation}`, 'gm'), '');
|
|
||||||
}
|
|
||||||
|
|
||||||
comments.push({ type: block ? 'Block' : 'Line', value, start, end });
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
ast = walk(ast, null, {
|
/**
|
||||||
_(node, { next }) {
|
* @type {Array<{
|
||||||
let comment;
|
* type: string;
|
||||||
|
* value: string;
|
||||||
|
* start: number;
|
||||||
|
* end: number
|
||||||
|
* }>}
|
||||||
|
*/
|
||||||
|
const comments = [];
|
||||||
|
|
||||||
|
let ast = acorn.parse(source, {
|
||||||
|
ecmaVersion: 'latest',
|
||||||
|
sourceType: 'module',
|
||||||
|
onComment: (block, value, start, end) => {
|
||||||
|
if (block && /\n/.test(value)) {
|
||||||
|
let a = start;
|
||||||
|
while (a > 0 && source[a - 1] !== '\n') a -= 1;
|
||||||
|
|
||||||
|
let b = a;
|
||||||
|
while (/[ \t]/.test(source[b])) b += 1;
|
||||||
|
|
||||||
|
const indentation = source.slice(a, b);
|
||||||
|
value = value.replace(new RegExp(`^${indentation}`, 'gm'), '');
|
||||||
|
}
|
||||||
|
|
||||||
while (comments[0] && comments[0].start < node.start) {
|
comments.push({ type: block ? 'Block' : 'Line', value, start, end });
|
||||||
comment = comments.shift();
|
|
||||||
// @ts-expect-error
|
|
||||||
(node.leadingComments ||= []).push(comment);
|
|
||||||
}
|
}
|
||||||
|
});
|
||||||
|
|
||||||
next();
|
ast = walk(ast, null, {
|
||||||
|
_(node, { next }) {
|
||||||
if (comments[0]) {
|
let comment;
|
||||||
const slice = source.slice(node.end, comments[0].start);
|
|
||||||
|
|
||||||
if (/^[,) \t]*$/.test(slice)) {
|
while (comments[0] && comments[0].start < node.start) {
|
||||||
|
comment = comments.shift();
|
||||||
// @ts-expect-error
|
// @ts-expect-error
|
||||||
node.trailingComments = [comments.shift()];
|
(node.leadingComments ||= []).push(comment);
|
||||||
}
|
}
|
||||||
}
|
|
||||||
},
|
|
||||||
// @ts-expect-error
|
|
||||||
Identifier(node, context) {
|
|
||||||
if (node.name === 'CODES') {
|
|
||||||
return {
|
|
||||||
type: 'ArrayExpression',
|
|
||||||
elements: Object.keys(messages[name]).map((code) => ({
|
|
||||||
type: 'Literal',
|
|
||||||
value: code
|
|
||||||
}))
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
if (comments.length > 0) {
|
|
||||||
// @ts-expect-error
|
|
||||||
(ast.trailingComments ||= []).push(...comments);
|
|
||||||
}
|
|
||||||
|
|
||||||
const category = messages[name];
|
|
||||||
|
|
||||||
// find the `export function CODE` node
|
|
||||||
const index = ast.body.findIndex((node) => {
|
|
||||||
if (
|
|
||||||
node.type === 'ExportNamedDeclaration' &&
|
|
||||||
node.declaration &&
|
|
||||||
node.declaration.type === 'FunctionDeclaration'
|
|
||||||
) {
|
|
||||||
return node.declaration.id.name === 'CODE';
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
if (index === -1) throw new Error(`missing export function CODE in ${name}.js`);
|
|
||||||
|
|
||||||
const template_node = ast.body[index];
|
next();
|
||||||
ast.body.splice(index, 1);
|
|
||||||
|
|
||||||
for (const code in category) {
|
if (comments[0]) {
|
||||||
const { messages } = category[code];
|
const slice = source.slice(node.end, comments[0].start);
|
||||||
/** @type {string[]} */
|
|
||||||
const vars = [];
|
|
||||||
|
|
||||||
const group = messages.map((text, i) => {
|
if (/^[,) \t]*$/.test(slice)) {
|
||||||
for (const match of text.matchAll(/%(\w+)%/g)) {
|
// @ts-expect-error
|
||||||
const name = match[1];
|
node.trailingComments = [comments.shift()];
|
||||||
if (!vars.includes(name)) {
|
}
|
||||||
vars.push(match[1]);
|
}
|
||||||
|
},
|
||||||
|
// @ts-expect-error
|
||||||
|
Identifier(node, context) {
|
||||||
|
if (node.name === 'CODES') {
|
||||||
|
return {
|
||||||
|
type: 'ArrayExpression',
|
||||||
|
elements: Object.keys(messages[name]).map((code) => ({
|
||||||
|
type: 'Literal',
|
||||||
|
value: code
|
||||||
|
}))
|
||||||
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return {
|
|
||||||
text,
|
|
||||||
vars: vars.slice()
|
|
||||||
};
|
|
||||||
});
|
});
|
||||||
|
|
||||||
/** @type {import('estree').Expression} */
|
if (comments.length > 0) {
|
||||||
let message = { type: 'Literal', value: '' };
|
// @ts-expect-error
|
||||||
let prev_vars;
|
(ast.trailingComments ||= []).push(...comments);
|
||||||
|
}
|
||||||
|
|
||||||
for (let i = 0; i < group.length; i += 1) {
|
const category = messages[name];
|
||||||
const { text, vars } = group[i];
|
|
||||||
|
|
||||||
if (vars.length === 0) {
|
// find the `export function CODE` node
|
||||||
message = {
|
const index = ast.body.findIndex((node) => {
|
||||||
type: 'Literal',
|
if (
|
||||||
value: text
|
node.type === 'ExportNamedDeclaration' &&
|
||||||
};
|
node.declaration &&
|
||||||
prev_vars = vars;
|
node.declaration.type === 'FunctionDeclaration'
|
||||||
continue;
|
) {
|
||||||
|
return node.declaration.id.name === 'CODE';
|
||||||
}
|
}
|
||||||
|
});
|
||||||
|
|
||||||
const parts = text.split(/(%\w+%)/);
|
if (index === -1) throw new Error(`missing export function CODE in ${name}.js`);
|
||||||
|
|
||||||
/** @type {import('estree').Expression[]} */
|
const template_node = ast.body[index];
|
||||||
const expressions = [];
|
ast.body.splice(index, 1);
|
||||||
|
|
||||||
/** @type {import('estree').TemplateElement[]} */
|
for (const code in category) {
|
||||||
const quasis = [];
|
const { messages } = category[code];
|
||||||
|
/** @type {string[]} */
|
||||||
|
const vars = [];
|
||||||
|
|
||||||
for (let i = 0; i < parts.length; i += 1) {
|
const group = messages.map((text, i) => {
|
||||||
const part = parts[i];
|
for (const match of text.matchAll(/%(\w+)%/g)) {
|
||||||
if (i % 2 === 0) {
|
const name = match[1];
|
||||||
const str = part.replace(/(`|\${)/g, '\\$1');
|
if (!vars.includes(name)) {
|
||||||
quasis.push({
|
vars.push(match[1]);
|
||||||
type: 'TemplateElement',
|
}
|
||||||
value: { raw: str, cooked: str },
|
|
||||||
tail: i === parts.length - 1
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
expressions.push({
|
|
||||||
type: 'Identifier',
|
|
||||||
name: part.slice(1, -1)
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
return {
|
||||||
|
text,
|
||||||
|
vars: vars.slice()
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
/** @type {import('estree').Expression} */
|
/** @type {import('estree').Expression} */
|
||||||
const expression = {
|
let message = { type: 'Literal', value: '' };
|
||||||
type: 'TemplateLiteral',
|
let prev_vars;
|
||||||
expressions,
|
|
||||||
quasis
|
for (let i = 0; i < group.length; i += 1) {
|
||||||
};
|
const { text, vars } = group[i];
|
||||||
|
|
||||||
if (prev_vars) {
|
if (vars.length === 0) {
|
||||||
if (vars.length === prev_vars.length) {
|
message = {
|
||||||
throw new Error('Message overloads must have new parameters');
|
type: 'Literal',
|
||||||
|
value: text
|
||||||
|
};
|
||||||
|
prev_vars = vars;
|
||||||
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
message = {
|
const parts = text.split(/(%\w+%)/);
|
||||||
type: 'ConditionalExpression',
|
|
||||||
test: {
|
|
||||||
type: 'Identifier',
|
|
||||||
name: vars[prev_vars.length]
|
|
||||||
},
|
|
||||||
consequent: expression,
|
|
||||||
alternate: message
|
|
||||||
};
|
|
||||||
} else {
|
|
||||||
message = expression;
|
|
||||||
}
|
|
||||||
|
|
||||||
prev_vars = vars;
|
/** @type {import('estree').Expression[]} */
|
||||||
}
|
const expressions = [];
|
||||||
|
|
||||||
const clone = walk(/** @type {import('estree').Node} */ (template_node), null, {
|
/** @type {import('estree').TemplateElement[]} */
|
||||||
// @ts-expect-error Block is a block comment, which is not recognised
|
const quasis = [];
|
||||||
Block(node, context) {
|
|
||||||
if (!node.value.includes('PARAMETER')) return;
|
|
||||||
|
|
||||||
const value = /** @type {string} */ (node.value)
|
|
||||||
.split('\n')
|
|
||||||
.map((line) => {
|
|
||||||
if (line === ' * MESSAGE') {
|
|
||||||
return messages[messages.length - 1]
|
|
||||||
.split('\n')
|
|
||||||
.map((line) => ` * ${line}`)
|
|
||||||
.join('\n');
|
|
||||||
}
|
|
||||||
|
|
||||||
if (line.includes('PARAMETER')) {
|
for (let i = 0; i < parts.length; i += 1) {
|
||||||
return vars
|
const part = parts[i];
|
||||||
.map((name, i) => {
|
if (i % 2 === 0) {
|
||||||
const optional = i >= group[0].vars.length;
|
const str = part.replace(/(`|\${)/g, '\\$1');
|
||||||
|
quasis.push({
|
||||||
|
type: 'TemplateElement',
|
||||||
|
value: { raw: str, cooked: str },
|
||||||
|
tail: i === parts.length - 1
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
expressions.push({
|
||||||
|
type: 'Identifier',
|
||||||
|
name: part.slice(1, -1)
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return optional
|
/** @type {import('estree').Expression} */
|
||||||
? ` * @param {string | undefined | null} [${name}]`
|
const expression = {
|
||||||
: ` * @param {string} ${name}`;
|
type: 'TemplateLiteral',
|
||||||
})
|
expressions,
|
||||||
.join('\n');
|
quasis
|
||||||
}
|
};
|
||||||
|
|
||||||
return line;
|
if (prev_vars) {
|
||||||
})
|
if (vars.length === prev_vars.length) {
|
||||||
.filter((x) => x !== '')
|
throw new Error('Message overloads must have new parameters');
|
||||||
.join('\n');
|
}
|
||||||
|
|
||||||
if (value !== node.value) {
|
message = {
|
||||||
return { ...node, value };
|
type: 'ConditionalExpression',
|
||||||
|
test: {
|
||||||
|
type: 'Identifier',
|
||||||
|
name: vars[prev_vars.length]
|
||||||
|
},
|
||||||
|
consequent: expression,
|
||||||
|
alternate: message
|
||||||
|
};
|
||||||
|
} else {
|
||||||
|
message = expression;
|
||||||
}
|
}
|
||||||
},
|
|
||||||
FunctionDeclaration(node, context) {
|
|
||||||
if (node.id.name !== 'CODE') return;
|
|
||||||
|
|
||||||
const params = [];
|
prev_vars = vars;
|
||||||
|
}
|
||||||
|
|
||||||
for (const param of node.params) {
|
const clone = walk(/** @type {import('estree').Node} */ (template_node), null, {
|
||||||
if (param.type === 'Identifier' && param.name === 'PARAMETER') {
|
// @ts-expect-error Block is a block comment, which is not recognised
|
||||||
params.push(...vars.map((name) => ({ type: 'Identifier', name })));
|
Block(node, context) {
|
||||||
} else {
|
if (!node.value.includes('PARAMETER')) return;
|
||||||
params.push(param);
|
|
||||||
|
const value = /** @type {string} */ (node.value)
|
||||||
|
.split('\n')
|
||||||
|
.map((line) => {
|
||||||
|
if (line === ' * MESSAGE') {
|
||||||
|
return messages[messages.length - 1]
|
||||||
|
.split('\n')
|
||||||
|
.map((line) => ` * ${line}`)
|
||||||
|
.join('\n');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (line.includes('PARAMETER')) {
|
||||||
|
return vars
|
||||||
|
.map((name, i) => {
|
||||||
|
const optional = i >= group[0].vars.length;
|
||||||
|
|
||||||
|
return optional
|
||||||
|
? ` * @param {string | undefined | null} [${name}]`
|
||||||
|
: ` * @param {string} ${name}`;
|
||||||
|
})
|
||||||
|
.join('\n');
|
||||||
|
}
|
||||||
|
|
||||||
|
return line;
|
||||||
|
})
|
||||||
|
.filter((x) => x !== '')
|
||||||
|
.join('\n');
|
||||||
|
|
||||||
|
if (value !== node.value) {
|
||||||
|
return { ...node, value };
|
||||||
}
|
}
|
||||||
}
|
},
|
||||||
|
FunctionDeclaration(node, context) {
|
||||||
|
if (node.id.name !== 'CODE') return;
|
||||||
|
|
||||||
|
const params = [];
|
||||||
|
|
||||||
return /** @type {import('estree').FunctionDeclaration} */ ({
|
for (const param of node.params) {
|
||||||
.../** @type {import('estree').FunctionDeclaration} */ (context.next()),
|
if (param.type === 'Identifier' && param.name === 'PARAMETER') {
|
||||||
params,
|
params.push(...vars.map((name) => ({ type: 'Identifier', name })));
|
||||||
id: {
|
} else {
|
||||||
...node.id,
|
params.push(param);
|
||||||
name: code
|
}
|
||||||
}
|
}
|
||||||
});
|
|
||||||
},
|
|
||||||
TemplateLiteral(node, context) {
|
|
||||||
/** @type {import('estree').TemplateElement} */
|
|
||||||
let quasi = {
|
|
||||||
type: 'TemplateElement',
|
|
||||||
value: {
|
|
||||||
...node.quasis[0].value
|
|
||||||
},
|
|
||||||
tail: node.quasis[0].tail
|
|
||||||
};
|
|
||||||
|
|
||||||
/** @type {import('estree').TemplateLiteral} */
|
return /** @type {import('estree').FunctionDeclaration} */ ({
|
||||||
let out = {
|
.../** @type {import('estree').FunctionDeclaration} */ (context.next()),
|
||||||
type: 'TemplateLiteral',
|
params,
|
||||||
quasis: [quasi],
|
id: {
|
||||||
expressions: []
|
...node.id,
|
||||||
};
|
name: code
|
||||||
|
}
|
||||||
|
});
|
||||||
|
},
|
||||||
|
TemplateLiteral(node, context) {
|
||||||
|
/** @type {import('estree').TemplateElement} */
|
||||||
|
let quasi = {
|
||||||
|
type: 'TemplateElement',
|
||||||
|
value: {
|
||||||
|
...node.quasis[0].value
|
||||||
|
},
|
||||||
|
tail: node.quasis[0].tail
|
||||||
|
};
|
||||||
|
|
||||||
for (let i = 0; i < node.expressions.length; i += 1) {
|
/** @type {import('estree').TemplateLiteral} */
|
||||||
const q = structuredClone(node.quasis[i + 1]);
|
let out = {
|
||||||
const e = node.expressions[i];
|
type: 'TemplateLiteral',
|
||||||
|
quasis: [quasi],
|
||||||
|
expressions: []
|
||||||
|
};
|
||||||
|
|
||||||
if (e.type === 'Literal' && e.value === 'CODE') {
|
for (let i = 0; i < node.expressions.length; i += 1) {
|
||||||
quasi.value.raw += code + q.value.raw;
|
const q = structuredClone(node.quasis[i + 1]);
|
||||||
continue;
|
const e = node.expressions[i];
|
||||||
}
|
|
||||||
|
|
||||||
if (e.type === 'Identifier' && e.name === 'MESSAGE') {
|
if (e.type === 'Literal' && e.value === 'CODE') {
|
||||||
if (message.type === 'Literal') {
|
quasi.value.raw += code + q.value.raw;
|
||||||
const str = /** @type {string} */ (message.value).replace(/(`|\${)/g, '\\$1');
|
|
||||||
quasi.value.raw += str + q.value.raw;
|
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (message.type === 'TemplateLiteral') {
|
if (e.type === 'Identifier' && e.name === 'MESSAGE') {
|
||||||
const m = structuredClone(message);
|
if (message.type === 'Literal') {
|
||||||
quasi.value.raw += m.quasis[0].value.raw;
|
const str = /** @type {string} */ (message.value).replace(/(`|\${)/g, '\\$1');
|
||||||
out.quasis.push(...m.quasis.slice(1));
|
quasi.value.raw += str + q.value.raw;
|
||||||
out.expressions.push(...m.expressions);
|
continue;
|
||||||
quasi = m.quasis[m.quasis.length - 1];
|
}
|
||||||
quasi.value.raw += q.value.raw;
|
|
||||||
continue;
|
if (message.type === 'TemplateLiteral') {
|
||||||
|
const m = structuredClone(message);
|
||||||
|
quasi.value.raw += m.quasis[0].value.raw;
|
||||||
|
out.quasis.push(...m.quasis.slice(1));
|
||||||
|
out.expressions.push(...m.expressions);
|
||||||
|
quasi = m.quasis[m.quasis.length - 1];
|
||||||
|
quasi.value.raw += q.value.raw;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
out.quasis.push((quasi = q));
|
||||||
|
out.expressions.push(/** @type {import('estree').Expression} */ (context.visit(e)));
|
||||||
}
|
}
|
||||||
|
|
||||||
out.quasis.push((quasi = q));
|
return out;
|
||||||
out.expressions.push(/** @type {import('estree').Expression} */ (context.visit(e)));
|
},
|
||||||
|
Literal(node) {
|
||||||
|
if (node.value === 'CODE') {
|
||||||
|
return {
|
||||||
|
type: 'Literal',
|
||||||
|
value: code
|
||||||
|
};
|
||||||
|
}
|
||||||
|
},
|
||||||
|
Identifier(node) {
|
||||||
|
if (node.name !== 'MESSAGE') return;
|
||||||
|
return message;
|
||||||
}
|
}
|
||||||
|
});
|
||||||
|
|
||||||
return out;
|
// @ts-expect-error
|
||||||
},
|
ast.body.push(clone);
|
||||||
Literal(node) {
|
}
|
||||||
if (node.value === 'CODE') {
|
|
||||||
return {
|
const module = esrap.print(ast);
|
||||||
type: 'Literal',
|
|
||||||
value: code
|
|
||||||
};
|
|
||||||
}
|
|
||||||
},
|
|
||||||
Identifier(node) {
|
|
||||||
if (node.name !== 'MESSAGE') return;
|
|
||||||
return message;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
// @ts-expect-error
|
fs.writeFileSync(
|
||||||
ast.body.push(clone);
|
dest,
|
||||||
|
`/* This file is generated by scripts/process-messages/index.js. Do not edit! */\n\n` +
|
||||||
|
module.code,
|
||||||
|
'utf-8'
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
const module = esrap.print(ast);
|
transform('compile-errors', 'src/compiler/errors.js');
|
||||||
|
transform('compile-warnings', 'src/compiler/warnings.js');
|
||||||
|
|
||||||
fs.writeFileSync(
|
transform('client-warnings', 'src/internal/client/warnings.js');
|
||||||
dest,
|
transform('client-errors', 'src/internal/client/errors.js');
|
||||||
`/* This file is generated by scripts/process-messages/index.js. Do not edit! */\n\n` +
|
transform('server-errors', 'src/internal/server/errors.js');
|
||||||
module.code,
|
transform('shared-errors', 'src/internal/shared/errors.js');
|
||||||
'utf-8'
|
transform('shared-warnings', 'src/internal/shared/warnings.js');
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
transform('compile-errors', 'src/compiler/errors.js');
|
if (watch) {
|
||||||
transform('compile-warnings', 'src/compiler/warnings.js');
|
let running = false;
|
||||||
|
let timeout;
|
||||||
|
|
||||||
|
fs.watch('messages', { recursive: true }, (type, file) => {
|
||||||
|
if (running) {
|
||||||
|
timeout ??= setTimeout(() => {
|
||||||
|
running = false;
|
||||||
|
timeout = null;
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
running = true;
|
||||||
|
|
||||||
|
// eslint-disable-next-line no-console
|
||||||
|
console.log('Regenerating messages...');
|
||||||
|
run();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
transform('client-warnings', 'src/internal/client/warnings.js');
|
run();
|
||||||
transform('client-errors', 'src/internal/client/errors.js');
|
|
||||||
transform('server-errors', 'src/internal/server/errors.js');
|
|
||||||
transform('shared-errors', 'src/internal/shared/errors.js');
|
|
||||||
transform('shared-warnings', 'src/internal/shared/warnings.js');
|
|
||||||
|
Loading…
Reference in new issue