zhang-wei-jian's picture
:rocket:
7d73cf2
"use strict";
/**
* Code generator for i18n js resource
*/
Object.defineProperty(exports, "__esModule", { value: true });
exports.generate = void 0;
const shared_1 = require("@intlify/shared");
const acorn_1 = require("acorn");
const escodegen_1 = require("escodegen");
const estree_walker_1 = require("estree-walker");
const codegen_1 = require("./codegen");
/**
* @internal
*/
function generate(targetSource, { type = 'plain', bridge = false, exportESM = false, filename = 'vue-i18n-loader.js', inSourceMap = undefined, locale = '', isGlobal = false, sourceMap = false, env = 'development', forceStringify = false, onError = undefined, strictMessage = true, escapeHtml = false, useClassComponent = false, allowDynamic = false }, injector) {
const target = Buffer.isBuffer(targetSource)
? targetSource.toString()
: targetSource;
const value = target;
const options = {
type,
bridge,
exportESM,
source: value,
sourceMap,
locale,
isGlobal,
inSourceMap,
env,
filename,
forceStringify,
onError,
strictMessage,
escapeHtml,
useClassComponent
};
const generator = (0, codegen_1.createCodeGenerator)(options);
const ast = (0, acorn_1.parse)(value, {
ecmaVersion: 'latest',
sourceType: 'module',
sourceFile: filename,
allowImportExportEverywhere: true
});
const exportResult = scanAst(ast);
if (!allowDynamic) {
// if (!astExportDefaultWithObject.length) {
if (!exportResult || exportResult !== 'object') {
throw new Error(`You need to define an object as the locale message with 'export default'.`);
}
}
else {
if (!exportResult) {
throw new Error(`You need to define 'export default' that will return the locale messages.`);
}
if (exportResult !== 'object') {
/**
* NOTE:
* If `allowDynamic` is `true`, do not transform the code by this function, return it as is.
* This means that the user **must transform locale messages ownself**.
* Especially at the production, you need to do locale messages pre-compiling.
*/
return {
ast,
code: value,
map: inSourceMap
};
}
}
const codeMaps = generateNode(generator, ast, options, injector);
const { code, map } = generator.context();
// if (map) {
// const s = new SourceMapConsumer((map as any).toJSON())
// s.eachMapping(m => {
// console.log('sourcemap json', m)
// })
// }
// prettier-ignore
const newMap = map
? (0, codegen_1.mapLinesColumns)(map.toJSON(), codeMaps, inSourceMap) || null // eslint-disable-line @typescript-eslint/no-explicit-any
: null;
return {
ast,
code,
map: newMap != null ? newMap : undefined
};
}
exports.generate = generate;
function scanAst(ast) {
if (ast.type !== 'Program') {
throw new Error('Invalid AST: does not have Program node');
}
let ret = false;
for (const node of ast.body) {
if (node.type === 'ExportDefaultDeclaration') {
if (node.declaration.type === 'ObjectExpression') {
ret = 'object';
break;
}
else if (node.declaration.type === 'FunctionDeclaration') {
ret = 'function';
break;
}
else if (node.declaration.type === 'ArrowFunctionExpression') {
ret = 'arrow-function';
break;
}
}
}
return ret;
}
function generateNode(generator, node, options, injector) {
const propsCountStack = [];
const pathStack = [];
const itemsCountStack = [];
const skipStack = [];
const { forceStringify } = generator.context();
const codeMaps = new Map();
const { type, bridge, exportESM, sourceMap, isGlobal, locale, useClassComponent } = options;
const componentNamespace = '_Component';
(0, estree_walker_1.walk)(node, {
/**
* NOTE:
* force cast to Node of `estree-walker@3.x`,
* because `estree-walker@3.x` is not dual packages,
* so it's support only esm only ...
*/
// @ts-ignore
enter(node, parent) {
switch (node.type) {
case 'Program':
if (type === 'plain') {
generator.push(`const resource = `);
}
else if (type === 'sfc') {
// for 'sfc'
const variableName = type === 'sfc' ? (!isGlobal ? '__i18n' : '__i18nGlobal') : '';
const localeName = type === 'sfc' ? (locale != null ? locale : `""`) : '';
const exportSyntax = bridge
? exportESM
? `export default`
: `module.exports =`
: `export default`;
generator.push(`${exportSyntax} function (Component) {`);
generator.indent();
// prettier-ignore
const componentVariable = bridge
? `Component.options || Component`
: useClassComponent
? `Component.__o || Component`
: `Component`;
// prettier-ignore
generator.pushline(`const ${componentNamespace} = ${componentVariable}`);
generator.pushline(`${componentNamespace}.${variableName} = ${componentNamespace}.${variableName} || []`);
generator.push(`${componentNamespace}.${variableName}.push({`);
generator.indent();
generator.pushline(`"locale": ${JSON.stringify(localeName)},`);
generator.push(`"resource": `);
}
break;
case 'ObjectExpression':
generator.push(`{`);
generator.indent();
propsCountStack.push(node.properties.length);
if (parent != null && parent.type === 'ArrayExpression') {
const lastIndex = itemsCountStack.length - 1;
const currentCount = parent.elements.length - itemsCountStack[lastIndex];
pathStack.push(currentCount.toString());
itemsCountStack[lastIndex] = --itemsCountStack[lastIndex];
}
break;
case 'Property':
if (parent != null && parent.type === 'ObjectExpression') {
if (node != null) {
if (isJSONablePrimitiveLiteral(node.value) &&
(node.key.type === 'Literal' || node.key.type === 'Identifier')) {
// prettier-ignore
const name = node.key.type === 'Literal'
? String(node.key.value)
: node.key.name;
if ((node.value.type === 'Literal' &&
(0, shared_1.isString)(node.value.value)) ||
node.value.type === 'TemplateLiteral') {
const value = getValue(node.value);
generator.push(`${JSON.stringify(name)}: `);
pathStack.push(name);
const { code, map } = (0, codegen_1.generateMessageFunction)(value, options, pathStack);
sourceMap && map != null && codeMaps.set(value, map);
generator.push(`${code}`, node.value, value);
skipStack.push(false);
}
else {
const value = getValue(node.value);
if (forceStringify) {
const strValue = JSON.stringify(value);
generator.push(`${JSON.stringify(name)}: `);
pathStack.push(name);
const { code, map } = (0, codegen_1.generateMessageFunction)(strValue, options, pathStack);
sourceMap && map != null && codeMaps.set(strValue, map);
generator.push(`${code}`, node.value, strValue);
}
else {
generator.push(`${JSON.stringify(name)}: ${JSON.stringify(value)}`);
pathStack.push(name);
}
skipStack.push(false);
}
}
else if ((node.value.type === 'FunctionExpression' ||
node.value.type === 'ArrowFunctionExpression') &&
(node.key.type === 'Literal' || node.key.type === 'Identifier')) {
// prettier-ignore
const name = node.key.type === 'Literal'
? String(node.key.value)
: node.key.name;
generator.push(`${JSON.stringify(name)}: `);
pathStack.push(name);
const code = (0, escodegen_1.generate)(node.value);
generator.push(`${code}`, node.value, code);
skipStack.push(false);
}
else if ((node.value.type === 'ObjectExpression' ||
node.value.type === 'ArrayExpression') &&
(node.key.type === 'Literal' || node.key.type === 'Identifier')) {
// prettier-ignore
const name = node.key.type === 'Literal'
? String(node.key.value)
: node.key.name;
generator.push(`${JSON.stringify(name)}: `);
pathStack.push(name);
}
else {
// for Regex, function, etc.
skipStack.push(true);
}
}
const lastIndex = propsCountStack.length - 1;
propsCountStack[lastIndex] = --propsCountStack[lastIndex];
}
break;
case 'ArrayExpression':
generator.push(`[`);
generator.indent();
if (parent != null && parent.type === 'ArrayExpression') {
const lastIndex = itemsCountStack.length - 1;
const currentCount = parent.elements.length - itemsCountStack[lastIndex];
pathStack.push(currentCount.toString());
itemsCountStack[lastIndex] = --itemsCountStack[lastIndex];
}
itemsCountStack.push(node.elements.length);
break;
default:
if (node != null && parent != null) {
if (parent.type === 'ArrayExpression') {
const lastIndex = itemsCountStack.length - 1;
const currentCount = parent.elements.length - itemsCountStack[lastIndex];
pathStack.push(currentCount.toString());
if (isJSONablePrimitiveLiteral(node)) {
if ((node.type === 'Literal' && (0, shared_1.isString)(node.value)) ||
node.type === 'TemplateLiteral') {
const value = getValue(node);
const { code, map } = (0, codegen_1.generateMessageFunction)(value, options, pathStack);
sourceMap && map != null && codeMaps.set(value, map);
generator.push(`${code}`, node, value);
}
else {
const value = getValue(node);
if (forceStringify) {
const strValue = JSON.stringify(value);
const { code, map } = (0, codegen_1.generateMessageFunction)(strValue, options, pathStack);
sourceMap && map != null && codeMaps.set(strValue, map);
generator.push(`${code}`, node, strValue);
}
else {
generator.push(`${JSON.stringify(value)}`);
}
}
skipStack.push(false);
}
else {
// for Regex, function, etc.
skipStack.push(true);
}
itemsCountStack[lastIndex] = --itemsCountStack[lastIndex];
}
}
else {
// ...
}
break;
}
},
/**
* NOTE:
* force cast to Node of `estree-walker@3.x`,
* because `estree-walker@3.x` is not dual packages,
* so it's support only esm only ...
*/
// @ts-ignore
leave(node, parent) {
switch (node.type) {
case 'Program':
if (type === 'sfc') {
generator.deindent();
generator.push(`})`);
if (bridge && injector) {
generator.newline();
generator.pushline(`${componentNamespace}.__i18nBridge = ${componentNamespace}.__i18nBridge || []`);
generator.pushline(`${componentNamespace}.__i18nBridge.push('${injector()}')`);
generator.pushline(`delete ${componentNamespace}._Ctor`);
}
generator.deindent();
generator.pushline(`}`);
}
else if (type === 'plain') {
generator.push(`\n`);
generator.push('export default resource');
}
break;
case 'ObjectExpression':
if (propsCountStack[propsCountStack.length - 1] === 0) {
pathStack.pop();
propsCountStack.pop();
}
generator.deindent();
generator.push(`}`);
if (parent != null && parent.type === 'ArrayExpression') {
if (itemsCountStack[itemsCountStack.length - 1] !== 0) {
pathStack.pop();
generator.pushline(`,`);
}
}
break;
case 'Property':
if (parent != null && parent.type === 'ObjectExpression') {
if (propsCountStack[propsCountStack.length - 1] !== 0) {
pathStack.pop();
if (!skipStack.pop()) {
generator.pushline(`,`);
}
}
}
break;
case 'ArrayExpression':
if (itemsCountStack[itemsCountStack.length - 1] === 0) {
pathStack.pop();
itemsCountStack.pop();
}
generator.deindent();
generator.push(`]`);
if (parent != null && parent.type === 'ArrayExpression') {
if (itemsCountStack[itemsCountStack.length - 1] !== 0) {
pathStack.pop();
if (!skipStack.pop()) {
generator.pushline(`,`);
}
}
}
break;
case 'Literal':
if (parent != null && parent.type === 'ArrayExpression') {
if (itemsCountStack[itemsCountStack.length - 1] !== 0) {
pathStack.pop();
if (!skipStack.pop()) {
generator.pushline(`,`);
}
}
else {
if (!skipStack.pop()) {
generator.pushline(`,`);
}
}
}
break;
default:
break;
}
}
});
return codeMaps;
}
function isJSONablePrimitiveLiteral(node) {
return ((node.type === 'Literal' &&
((0, shared_1.isString)(node.value) ||
(0, shared_1.isNumber)(node.value) ||
(0, shared_1.isBoolean)(node.value) ||
node.value === null)) ||
node.type === 'TemplateLiteral');
// NOTE: the following code is same the above code
/*
if (node.type === 'Literal') {
if (
isString(node.value) ||
isNumber(node.value) ||
isBoolean(node.value) ||
node.value === null
) {
return true
} else if (isRegExp(node.value)) {
return false
} else {
return false
}
} else if (node.type === 'TemplateLiteral') {
return true
} else {
return false
}
*/
}
function getValue(node) {
// prettier-ignore
return node.type === 'Literal'
? node.value
: node.type === 'TemplateLiteral'
? node.quasis.map(quasi => quasi.value.cooked).join('')
: undefined;
}