webpack-angular-translate
Version:
Webpack plugin that extracts the translation-ids with the default texts.
326 lines • 14.6 kB
JavaScript
;
var __assign = (this && this.__assign) || function () {
__assign = Object.assign || function(t) {
for (var s, i = 1, n = arguments.length; i < n; i++) {
s = arguments[i];
for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p))
t[p] = s[p];
}
return t;
};
return __assign.apply(this, arguments);
};
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
Object.defineProperty(o, k2, { enumerable: true, get: function() { return m[k]; } });
}) : (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
o["default"] = v;
});
var __importStar = (this && this.__importStar) || function (mod) {
if (mod && mod.__esModule) return mod;
var result = {};
if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
__setModuleDefault(result, mod);
return result;
};
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.isCommentedWithSuppressError = void 0;
var path = __importStar(require("path"));
var ast_types_1 = require("ast-types");
var translation_1 = __importDefault(require("../translation"));
var TRANSLATE_SERVICE_NAME = "$translate";
function createTranslateVisitor(loader, parserOptions) {
if (parserOptions === void 0) { parserOptions = { ecmaVersion: "latest" }; }
var context = null;
var comments = [];
var options = __assign(__assign({}, parserOptions), { locations: true, onComment: comments,
// onToken: tokens,
ranges: true });
/**
* Handles a $translate(translateId, interpolateParams, interpolationId, defaultText) call.
* @param path the path to the call expression
*/
function visitTranslate(path) {
var call = path.node;
var args = call.arguments;
if (args.length < 1) {
throwSuppressableError("A call to " + TRANSLATE_SERVICE_NAME + " requires at least one argument that is the translation id", path);
}
var translationIds = getTranslationIdFromTranslateCall(path);
var defaultText = getDefaultTextFromTranslateCall(path);
for (var _i = 0, translationIds_1 = translationIds; _i < translationIds_1.length; _i++) {
var translationId = translationIds_1[_i];
var translation = createTranslation(translationId, defaultText, call);
loader.registerTranslation(translation);
}
}
function getTranslationIdFromTranslateCall(path) {
var args = path.node.arguments;
if (ast_types_1.namedTypes.Literal.check(args[0])) {
return [args[0].value];
}
if (ast_types_1.namedTypes.ArrayExpression.check(args[0])) {
var arrayExpression = args[0];
return arrayExpression.elements.map(function (element) {
if (ast_types_1.namedTypes.Literal.check(element)) {
return element.value;
}
throwSuppressableError("The array with the translation ids should only contain literals", path);
});
}
throwSuppressableError("The translation id should either be a string literal or an array containing string literals", path);
}
function getDefaultTextFromTranslateCall(path) {
var args = path.node.arguments;
if (args.length > 3) {
if (ast_types_1.namedTypes.Literal.check(args[3])) {
return args[3].value;
}
throwSuppressableError("The default text should be a string literal", path);
}
return undefined;
}
/**
* Handles a call to i18n.registerTranslation(translationId, defaultText?).
* Evaluates the expression and registers a translation. The call expression itself is replaced with the id of the
* translation id.
* @param path of the call expression.
*/
function visitRegisterTranslation(path) {
var call = path.node, args = call.arguments;
if (args.length === 0 || !ast_types_1.namedTypes.Literal.check(args[0])) {
throwError("Illegal argument for call to 'i18n.registerTranslation'. The call requires at least the 'translationId' argument that needs to be a literal", call);
}
var translationId = args[0].value;
var defaultText;
if (args.length > 1) {
if (ast_types_1.namedTypes.Literal.check(args[1])) {
defaultText = args[1].value;
}
else {
throwError("Illegal argument for call to i18n.registerTranslation: the default text has to be a literal", call);
}
}
var translation = createTranslation(translationId, defaultText, call);
loader.registerTranslation(translation);
path.replace(ast_types_1.builders.literal(translation.id));
context.reportChanged();
}
/**
* Handles a call to i18n.registerTranslations({ translationId: defaultText }).
* @param path the path to the call expression
*/
function visitRegisterTranslations(path) {
var call = path.node, args = call.arguments, translationsArgument = args.length === 0 ? null : args[0];
if (translationsArgument === null ||
!ast_types_1.namedTypes.ObjectExpression.check(translationsArgument)) {
throwError("Illegal argument for call to i18n.registerTranslations: requires a single argument that is an object where the key is the translationId and the value is the default text", call);
}
var translations = (translationsArgument).properties.map(function (property) {
var translationId;
var defaultText;
if (property.type === "SpreadElement" ||
property.type === "SpreadProperty" ||
property.type === "ObjectMethod") {
throwError("Illegal argument for call to i18n.registerTranslations: The passed object contains a spread property, spread element, or method. This is not supported.", property);
return;
}
if (ast_types_1.namedTypes.Identifier.check(property.key)) {
translationId = property.key.name;
}
else if (ast_types_1.namedTypes.Literal.check(property.key)) {
translationId = property.key.value;
}
else {
throwError("Illegal argument for call to i18n.registerTranslations: The key needs to be a literal or an identifier.", call);
}
if (ast_types_1.namedTypes.Literal.check(property.value)) {
defaultText = property.value.value;
}
else {
throwError("Illegal argument for call to i18n.registerTranslations: The value for the key '" + translationId + "' needs to be a literal", call);
}
return createTranslation(translationId, defaultText, call);
});
for (var _i = 0, translations_1 = translations; _i < translations_1.length; _i++) {
var translation = translations_1[_i];
loader.registerTranslation(translation);
}
var ids = ast_types_1.builders.arrayExpression(translations.map(function (translation) { return ast_types_1.builders.literal(translation.id); }));
path.replace(ids);
context.reportChanged();
}
function createTranslation(translationId, defaultText, node) {
var idAsString = valueToString(translationId, "");
var defaultTextAsString = valueToString(defaultText, undefined);
return new translation_1.default(idAsString, defaultTextAsString, {
resource: path.relative(loader.context, loader.resourcePath),
loc: node.loc.start
});
}
/**
* Gets the function name from a call expression
* @param call the call expression
* @returns {string} the name of the function
*/
function getFunctionName(call) {
var callee = call.callee;
if (ast_types_1.namedTypes.Identifier.check(callee)) {
return callee.name;
}
else if (ast_types_1.namedTypes.MemberExpression.check(callee)) {
var property = callee.property;
if (ast_types_1.namedTypes.Identifier.check(property)) {
return property.name;
}
return "[expression]";
}
else if (ast_types_1.namedTypes.FunctionExpression.check(callee)) {
return "(function () { ... })";
}
}
/**
* Gets the name of the callee of a function.
* Returns the name of the object before the dot (.) in a function call,
* e.g this for this.$translate or i18n for i18n.registerTranslation
* @param call the call expression
* @returns {string} the name of the callee or null if the name could not be determined
*/
function getCalleeName(call) {
// this.method() or object.method()
if (call.callee.type === "MemberExpression") {
var member = call.callee;
if (member.object.type === "Identifier") {
return member.object.name;
}
else if (member.object.type === "ThisExpression") {
return "this";
}
else if (member.object.type == "MemberExpression") {
var parent = member.object;
if (parent.property.type === "Identifier") {
return parent.property.name;
}
}
}
return null;
}
/**
* Emits an error to webpack and throws an error to abort the processing of the node.
*
* @param message the message to emit
* @param node the node for which a message is emitted
*/
function throwError(message, node) {
var relativePath = path.relative(loader.context, loader.resourcePath);
var start = node.loc.start, completeMessage = message + " (" + relativePath + ":" + start.line + ":" + start.column + ")";
loader.emitError(new Error(completeMessage));
throw context.abort();
}
/**
* Emits an error to webpack if no comment with suppress-dynamic-translation-error: true is found
* in the scope of the passed in path
*
* @param message the message to emit
* @param path the path of the node to which the error belongs
*/
function throwSuppressableError(message, path) {
var call = path.node, calleeName = getCalleeName(call), functionName = getFunctionName(call), completeFunctionName = (calleeName ? calleeName + "." : "") + functionName, completeMessage = "Illegal argument for call to " + completeFunctionName + ": " + message + ". If you have registered the translation manually, you can use a /* suppress-dynamic-translation-error: true */ comment in the block of the function call to suppress this error.";
if (!isCommentedWithSuppressErrors(path)) {
throwError(completeMessage, call);
}
context.abort();
}
/**
* Tests if a {@code suppress-dynamic-translation-error: true } comment exists in the scope of the passed in path.
* @param path the path to check
* @returns {boolean} {@code true} if the current block contains such a comment, otherwise false
*/
function isCommentedWithSuppressErrors(path) {
return isCommentedWithSuppressError(path, comments);
}
function valueToString(value, fallback) {
if (value === null || typeof value === "undefined") {
return fallback;
}
return "" + value;
}
var visitor = ast_types_1.PathVisitor.fromMethodsObject({
visitCallExpression: function (path) {
context = this;
var call = path.node, functionName = getFunctionName(call), calleeName = getCalleeName(call);
try {
if (functionName === TRANSLATE_SERVICE_NAME) {
visitTranslate(path);
}
else if (functionName === "registerTranslation" &&
calleeName === "i18n") {
visitRegisterTranslation(path);
}
else if (functionName === "registerTranslations" &&
calleeName === "i18n") {
visitRegisterTranslations(path);
}
else if (functionName === "instant" &&
calleeName === TRANSLATE_SERVICE_NAME) {
visitTranslate(path);
}
else {
context.traverse(path);
}
}
catch (e) {
if (e instanceof context.AbortRequest) {
e.cancel();
}
else {
throw e;
}
}
return false;
}
});
return {
get changedAst() {
return visitor.wasChangeReported();
},
comments: comments,
options: options,
visit: function (ast) {
return visitor.visit(ast);
}
};
}
exports.default = createTranslateVisitor;
function isCommentedWithSuppressError(path, comments) {
var blockStartPath = path;
while (blockStartPath.parentPath &&
!(ast_types_1.namedTypes.BlockStatement.check(blockStartPath.node) ||
ast_types_1.namedTypes.Program.check(blockStartPath.node))) {
blockStartPath = blockStartPath.parentPath;
}
var blockStart = blockStartPath.node;
var suppressCommentExpression = /suppress-dynamic-translation-error:\s*true/;
for (var _i = 0, comments_1 = comments; _i < comments_1.length; _i++) {
var comment = comments_1[_i];
if (comment.loc.end.line > path.node.loc.start.line) {
return false;
}
if (comment.loc.start.line >= blockStart.loc.start.line &&
suppressCommentExpression.test(comment.value)) {
return true;
}
}
return false;
}
exports.isCommentedWithSuppressError = isCommentedWithSuppressError;
//# sourceMappingURL=translate-visitor.js.map