"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; }