2021-08-09 02:00:43 +00:00
|
|
|
const gettextFuncs = new Set([
|
|
|
|
'_',
|
|
|
|
'N_',
|
|
|
|
'C_',
|
|
|
|
'NC_',
|
|
|
|
'dcgettext',
|
|
|
|
'dgettext',
|
|
|
|
'dngettext',
|
|
|
|
'dpgettext',
|
|
|
|
'gettext',
|
|
|
|
'ngettext',
|
|
|
|
'pgettext',
|
|
|
|
]);
|
|
|
|
|
|
|
|
function dirname(file) {
|
|
|
|
const split = file.split('/');
|
|
|
|
split.pop();
|
|
|
|
return split.join('/');
|
|
|
|
}
|
|
|
|
|
|
|
|
const scriptDir = dirname(import.meta.url);
|
|
|
|
const root = dirname(scriptDir);
|
|
|
|
|
|
|
|
const excludedFiles = new Set();
|
|
|
|
const foundFiles = new Set()
|
|
|
|
|
|
|
|
function addExcludes(filename) {
|
|
|
|
const contents = os.file.readFile(filename);
|
|
|
|
const lines = contents.split('\n')
|
|
|
|
.filter(l => l && !l.startsWith('#'));
|
|
|
|
lines.forEach(line => excludedFiles.add(line));
|
|
|
|
}
|
|
|
|
|
|
|
|
addExcludes(`${root}/po/POTFILES.in`);
|
|
|
|
addExcludes(`${root}/po/POTFILES.skip`);
|
|
|
|
|
|
|
|
function walkAst(node, func) {
|
|
|
|
func(node);
|
|
|
|
nodesToWalk(node).forEach(n => walkAst(n, func));
|
|
|
|
}
|
|
|
|
|
|
|
|
function findGettextCalls(node) {
|
|
|
|
switch(node.type) {
|
|
|
|
case 'CallExpression':
|
|
|
|
if (node.callee.type === 'Identifier' &&
|
|
|
|
gettextFuncs.has(node.callee.name))
|
|
|
|
throw new Error();
|
|
|
|
if (node.callee.type === 'MemberExpression' &&
|
|
|
|
node.callee.object.type === 'Identifier' &&
|
|
|
|
node.callee.object.name === 'Gettext' &&
|
|
|
|
node.callee.property.type === 'Identifier' &&
|
|
|
|
gettextFuncs.has(node.callee.property.name))
|
|
|
|
throw new Error();
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
function nodesToWalk(node) {
|
|
|
|
switch(node.type) {
|
|
|
|
case 'ArrayPattern':
|
|
|
|
case 'BreakStatement':
|
|
|
|
case 'CallSiteObject': // i.e. strings passed to template
|
|
|
|
case 'ContinueStatement':
|
|
|
|
case 'DebuggerStatement':
|
|
|
|
case 'EmptyStatement':
|
|
|
|
case 'Identifier':
|
|
|
|
case 'Literal':
|
|
|
|
case 'MetaProperty': // i.e. new.target
|
|
|
|
case 'Super':
|
|
|
|
case 'ThisExpression':
|
|
|
|
return [];
|
|
|
|
case 'ArrowFunctionExpression':
|
|
|
|
case 'FunctionDeclaration':
|
|
|
|
case 'FunctionExpression':
|
|
|
|
return [...node.defaults, node.body].filter(n => !!n);
|
|
|
|
case 'AssignmentExpression':
|
|
|
|
case 'BinaryExpression':
|
|
|
|
case 'ComprehensionBlock':
|
|
|
|
case 'LogicalExpression':
|
|
|
|
return [node.left, node.right];
|
|
|
|
case 'ArrayExpression':
|
|
|
|
case 'TemplateLiteral':
|
|
|
|
return node.elements.filter(n => !!n);
|
|
|
|
case 'BlockStatement':
|
|
|
|
case 'Program':
|
|
|
|
return node.body;
|
|
|
|
case 'CallExpression':
|
|
|
|
case 'NewExpression':
|
2022-01-08 18:53:15 +00:00
|
|
|
case 'OptionalCallExpression':
|
2021-08-09 02:00:43 +00:00
|
|
|
case 'TaggedTemplate':
|
|
|
|
return [node.callee, ...node.arguments];
|
|
|
|
case 'CatchClause':
|
|
|
|
return [node.body, node.guard].filter(n => !!n);
|
|
|
|
case 'ClassExpression':
|
|
|
|
case 'ClassStatement':
|
|
|
|
return [...node.body, node.superClass].filter(n => !!n);
|
|
|
|
case 'ClassMethod':
|
|
|
|
return [node.name, node.body];
|
|
|
|
case 'ComprehensionExpression':
|
|
|
|
case 'GeneratorExpression':
|
|
|
|
return [node.body, ...node.blocks, node.filter].filter(n => !!n);
|
|
|
|
case 'ComprehensionIf':
|
|
|
|
return [node.test];
|
|
|
|
case 'ComputedName':
|
|
|
|
return [node.name];
|
|
|
|
case 'ConditionalExpression':
|
|
|
|
case 'IfStatement':
|
|
|
|
return [node.test, node.consequent, node.alternate].filter(n => !!n);
|
|
|
|
case 'DoWhileStatement':
|
|
|
|
case 'WhileStatement':
|
|
|
|
return [node.body, node.test];
|
|
|
|
case 'ExportDeclaration':
|
|
|
|
return [node.declaration, node.source].filter(n => !!n);
|
|
|
|
case 'ImportDeclaration':
|
|
|
|
return [...node.specifiers, node.source];
|
|
|
|
case 'LetStatement':
|
|
|
|
return [...node.head, node.body];
|
|
|
|
case 'ExpressionStatement':
|
|
|
|
return [node.expression];
|
|
|
|
case 'ForInStatement':
|
|
|
|
case 'ForOfStatement':
|
|
|
|
return [node.body, node.left, node.right];
|
|
|
|
case 'ForStatement':
|
|
|
|
return [node.init, node.test, node.update, node.body].filter(n => !!n);
|
|
|
|
case 'LabeledStatement':
|
|
|
|
return [node.body];
|
|
|
|
case 'MemberExpression':
|
|
|
|
return [node.object, node.property];
|
|
|
|
case 'ObjectExpression':
|
|
|
|
case 'ObjectPattern':
|
|
|
|
return node.properties;
|
|
|
|
case 'OptionalExpression':
|
|
|
|
return [node.expression];
|
|
|
|
case 'OptionalMemberExpression':
|
|
|
|
return [node.object, node.property];
|
|
|
|
case 'Property':
|
|
|
|
case 'PrototypeMutation':
|
|
|
|
return [node.value];
|
|
|
|
case 'ReturnStatement':
|
|
|
|
case 'ThrowStatement':
|
|
|
|
case 'UnaryExpression':
|
|
|
|
case 'UpdateExpression':
|
|
|
|
case 'YieldExpression':
|
|
|
|
return node.argument ? [node.argument] : [];
|
|
|
|
case 'SequenceExpression':
|
|
|
|
return node.expressions;
|
|
|
|
case 'SpreadExpression':
|
|
|
|
return [node.expression];
|
|
|
|
case 'SwitchCase':
|
|
|
|
return [node.test, ...node.consequent].filter(n => !!n);
|
|
|
|
case 'SwitchStatement':
|
|
|
|
return [node.discriminant, ...node.cases];
|
|
|
|
case 'TryStatement':
|
|
|
|
return [node.block, node.handler, node.finalizer].filter(n => !!n);
|
|
|
|
case 'VariableDeclaration':
|
|
|
|
return node.declarations;
|
|
|
|
case 'VariableDeclarator':
|
|
|
|
return node.init ? [node.init] : [];
|
|
|
|
case 'WithStatement':
|
|
|
|
return [node.object, node.body];
|
|
|
|
default:
|
|
|
|
print(`Ignoring ${node.type}, you should probably fix this in the script`);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
function walkDir(dir) {
|
|
|
|
os.file.listDir(dir).forEach(child => {
|
|
|
|
if (child.startsWith('.'))
|
|
|
|
return;
|
|
|
|
|
|
|
|
const path = os.path.join(dir, child);
|
|
|
|
const relativePath = path.replace(`${root}/`, '');
|
|
|
|
if (excludedFiles.has(relativePath))
|
|
|
|
return;
|
|
|
|
|
|
|
|
if (!child.endsWith('.js')) {
|
|
|
|
try {
|
|
|
|
walkDir(path);
|
|
|
|
} catch (e) {
|
|
|
|
// not a directory
|
|
|
|
}
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
try {
|
|
|
|
const script = os.file.readFile(path);
|
|
|
|
const ast = Reflect.parse(script);
|
|
|
|
walkAst(ast, findGettextCalls);
|
|
|
|
} catch (e) {
|
|
|
|
foundFiles.add(path);
|
|
|
|
}
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
walkDir(root);
|
|
|
|
|
|
|
|
if (foundFiles.size === 0)
|
|
|
|
quit(0);
|
|
|
|
|
|
|
|
print('The following files are missing from po/POTFILES.in:')
|
|
|
|
foundFiles.forEach(f => print(` ${f}`));
|
|
|
|
quit(1);
|