localer
Version:
Locales collector from js sources.
832 lines (617 loc) • 21.6 kB
JavaScript
'use strict';
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.LocaleSource = undefined;
var _typeof = typeof Symbol === "function" && typeof Symbol.iterator === "symbol" ? function (obj) { return typeof obj; } : function (obj) { return obj && typeof Symbol === "function" && obj.constructor === Symbol && obj !== Symbol.prototype ? "symbol" : typeof obj; };
var _slicedToArray = function () { function sliceIterator(arr, i) { var _arr = []; var _n = true; var _d = false; var _e = undefined; try { for (var _i = arr[Symbol.iterator](), _s; !(_n = (_s = _i.next()).done); _n = true) { _arr.push(_s.value); if (i && _arr.length === i) break; } } catch (err) { _d = true; _e = err; } finally { try { if (!_n && _i["return"]) _i["return"](); } finally { if (_d) throw _e; } } return _arr; } return function (arr, i) { if (Array.isArray(arr)) { return arr; } else if (Symbol.iterator in Object(arr)) { return sliceIterator(arr, i); } else { throw new TypeError("Invalid attempt to destructure non-iterable instance"); } }; }();
var _extends = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; };
var _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }();
var _helpers = require('./helpers');
var _babylon = require('babylon');
var Babylon = _interopRequireWildcard(_babylon);
var _babelTraverse = require('babel-traverse');
var _babelTraverse2 = _interopRequireDefault(_babelTraverse);
var _babelCodeFrame = require('babel-code-frame');
var _babelCodeFrame2 = _interopRequireDefault(_babelCodeFrame);
var _ansiToHtml = require('ansi-to-html');
var _ansiToHtml2 = _interopRequireDefault(_ansiToHtml);
var _escapeHtml = require('escape-html');
var _escapeHtml2 = _interopRequireDefault(_escapeHtml);
var _path = require('path');
var _path2 = _interopRequireDefault(_path);
require('colour');
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
function _interopRequireWildcard(obj) { if (obj && obj.__esModule) { return obj; } else { var newObj = {}; if (obj != null) { for (var key in obj) { if (Object.prototype.hasOwnProperty.call(obj, key)) newObj[key] = obj[key]; } } newObj.default = obj; return newObj; } }
function _toConsumableArray(arr) { if (Array.isArray(arr)) { for (var i = 0, arr2 = Array(arr.length); i < arr.length; i++) { arr2[i] = arr[i]; } return arr2; } else { return Array.from(arr); } }
function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }
var codeFrameOptions = {
highlightCode: true
};
var Locales = function () {
/**
* Locales constructor.
*
* @param {Array<LocaleSource>|Locales} fromLocalesOrLocales
* @param {Array<String>} fromUnused
* @return {Locales}
*/
/**
* Locales soruces.
* @type {Array<LocaleSources>}
*/
/**
* Babylon parser options.
* @type {Object}
*/
/**
* Functions with few argument.
* @type {Array<String>}
*/
/**
* Tagged literals.
* @type {Array<String>}
*/
function Locales() {
var fromLocalesOrLocales = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : [];
var fromUnused = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : [];
_classCallCheck(this, Locales);
this.tags = ['__', '__n'];
this.fns = ['__', '__n', '__mf', '__l', '__h'];
this.fns2 = ['__n'];
this.convert = new _ansiToHtml2.default({
fg: '#000',
bg: '#FFF'
});
this.babylonOptions = {
sourceType: 'module',
plugins: ['jsx',
// "flow",
'dynamicImport', 'asyncFunctions', 'classConstructorCall', 'doExpressions', 'trailingFunctionCommas', 'objectRestSpread', 'decorators', 'classProperties', 'exportExtensions', 'exponentiationOperator', 'asyncGenerators', 'functionBind', 'functionSent']
};
this.transformers = [];
this.locales = [];
this.unused = [];
if (fromLocalesOrLocales instanceof Locales) {
this.from(fromLocalesOrLocales);
return;
}
if (!Array.isArray(fromLocalesOrLocales) || !Array.isArray(fromUnused)) {
throw new Error('Invalid arguments.');
}
var fromLocales = fromLocalesOrLocales,
locales = this.locales,
unused = this.unused;
fromLocales.forEach(function (localeSource) {
if (!(localeSource instanceof LocaleSource)) {
throw new Error('Invalid locale source.');
}
locales.push(localeSource.copy());
});
fromUnused.forEach(function (localeString) {
if (typeof localeString != 'string') {
throw new Error('Invalid locale string.');
}
unused.push(localeString);
});
}
/**
* Import data from other instance.
*
* @param {Locales} locales
* @return {this}
*/
/**
* Unused locales.
* @type {Array<String>}
*/
/**
* Code transformers.
* @type {Array<Function>}
*/
/**
* ANSI to HTML instance.
* @type {Convert}
*/
/**
* Functions with one argument.
* @type {Array<String>}
*/
_createClass(Locales, [{
key: 'from',
value: function from(locales) {
var _this = this;
if (!(locales instanceof Locales)) {
throw new Error('Invalid locales.');
}
this.convert = locales.convert;
this.tags = locales.tags.slice();
this.fns = locales.fns.slice();
this.fns2 = locales.fns2.slice();
this.transformers = locales.transformers.slice();
this.babylonOptions = _extends({}, locales.babylonOptions, {
plugins: locales.babylonOptions.plugins.slice()
});
this.locales = [];
locales.locales.forEach(function (localeSource) {
if (!(localeSource instanceof LocaleSource)) {
throw new Error('Invalid locale source.');
}
_this.locales.push(localeSource.copy());
});
this.unused = [];
locales.unused.forEach(function (localeString) {
if (typeof localeString != 'string') {
throw new Error('Invalid locale string.');
}
_this.unused.push(localeString);
});
return this;
}
/**
* Create copy of this.
*
* @return {Locales}
*/
}, {
key: 'copy',
value: function copy() {
return new Locales(this);
}
/**
* Collect locales from source code.
*
* @param {String} code
* @param {String} file
* @return {this}
*/
}, {
key: 'fromCode',
value: function fromCode(sourceCode, file) {
if (typeof sourceCode != 'string') {
throw new Error('Invalid arguments.');
}
var extname = '.js';
if (typeof file == 'string') {
extname = _path2.default.extname(file);
}
var tags = this.tags,
fns = this.fns,
fns2 = this.fns2,
transformers = this.transformers,
babylonOptions = this.babylonOptions,
locales = this.locales;
var code = transformers.reduce(function (code, transform) {
return transform(code, extname);
}, sourceCode);
(0, _babelTraverse2.default)(Babylon.parse(code, babylonOptions), {
TaggedTemplateExpression: function TaggedTemplateExpression(path) {
var node = path.node;
if (!~tags.indexOf(node.tag.name)) {
return;
}
locales.push(new LocaleSource(file, code, node, node.tag.name, node.quasi.quasis.map(function (node) {
return node.value.raw;
}).join('%s')));
},
CallExpression: function CallExpression(path) {
var node = path.node,
callee = node.callee;
var name = '';
if (callee.type == 'Identifier') {
name = callee.name;
} else if (callee.type == 'MemberExpression' && callee.property.type == 'Identifier') {
name = callee.property.name;
} else {
return;
}
if (!~fns.indexOf(name)) {
return;
}
var _node$arguments = _slicedToArray(node.arguments, 2),
first = _node$arguments[0],
second = _node$arguments[1];
if (typeof first != 'undefined') {
if (first.type == 'StringLiteral') {
locales.push(new LocaleSource(file, code, node, name, first.value));
} else if (first.type == 'TemplateLiteral') {
locales.push(new LocaleSource(file, code, node, name
// first.quasis.map(node => node.value.raw).join('%s')
));
} else {
locales.push(new LocaleSource(file, code, node, name));
}
}
if (typeof second != 'undefined' && ~fns2.indexOf(name)) {
if (second.type == 'StringLiteral') {
locales.push(new LocaleSource(file, code, node, name, second.value));
} else if (second.type == 'TemplateLiteral') {
locales.push(new LocaleSource(file, code, node, name
// second.quasis.map(node => node.value.raw).join('%s')
));
} else {
locales.push(new LocaleSource(file, code, node, name));
}
}
}
});
return this;
}
/**
* Collect locales from JavaScript source files by glob pattern.
*
* @param {String|Array<String>} maskOrMasks
* @return {Promise<this>}
*/
}, {
key: 'fromFiles',
value: function fromFiles(maskOrMasks) {
var _this2 = this;
var masks = [];
if (typeof maskOrMasks == 'string') {
masks = [maskOrMasks];
} else if (Array.isArray(maskOrMasks)) {
masks = maskOrMasks;
} else {
throw new Error('Invalid arguments.');
}
return (0, _helpers.glob)(masks).then(function (files) {
return (0, _helpers.asyncForEach)(files, function (file) {
return (0, _helpers.readFile)(file).then(function (code) {
return _this2.fromCode(code, file);
});
});
}).then(function () {
return _this2;
});
}
/**
* Exclude given locales from `locales` and `unused`.
*
* @param {Array<String|LocaleSource>|Object<String,any>} arrayOrObjectToExlcude
* @return {this}
*/
}, {
key: 'exclude',
value: function exclude(arrayOrObjectToExlcude) {
var exclude = [];
if (Array.isArray(arrayOrObjectToExlcude)) {
exclude = arrayOrObjectToExlcude.map(function (locale) {
if (locale instanceof LocaleSource) {
return locale.string;
} else if (typeof locale == 'string') {
return locale;
} else {
throw new Error('Invalid locale to exclude.');
}
});
} else if ((typeof arrayOrObjectToExlcude === 'undefined' ? 'undefined' : _typeof(arrayOrObjectToExlcude)) == 'object' && arrayOrObjectToExlcude != null) {
exclude = (0, _helpers.flatKeys)(arrayOrObjectToExlcude);
} else {
throw new Error('Invalid arguments.');
}
this.locales = this.locales.filter(function (_ref) {
var string = _ref.string;
return !~exclude.indexOf(string);
});
this.unused = this.unused.filter(function (string) {
return !~exclude.indexOf(string);
});
return this;
}
/**
* Exclude locales getted from JSON files from `locales` and `unused`.
*
* @param {String|Array<String>} maskOrMasks
* @return {Promise<this>}
*/
}, {
key: 'excludeFiles',
value: function excludeFiles(maskOrMasks) {
var _this3 = this;
var cwd = process.cwd();
var masks = [];
if (typeof maskOrMasks == 'string') {
masks = [maskOrMasks];
} else if (Array.isArray(maskOrMasks)) {
masks = maskOrMasks;
} else {
throw new Error('Invalid arguments.');
}
return (0, _helpers.glob)(masks).then(function (files) {
return files.forEach(function (file) {
return _this3.exclude(require(_path2.default.resolve(cwd, file)));
});
}).then(function () {
return _this3;
});
}
/**
* Get difference between locales parsed from JavaScript sources and locales.
*
* @param {Array<String|LocaleSource>|Object<String,any>} arrayOrObjectBase
* @return {Locales}
*/
}, {
key: 'diff',
value: function diff(arrayOrObjectBase) {
var base = [];
if (Array.isArray(arrayOrObjectBase)) {
base = arrayOrObjectBase.map(function (locale) {
if (locale instanceof LocaleSource) {
return locale.string;
} else if (typeof locale == 'string') {
return locale;
} else {
throw new Error('Invalid locale to exclude.');
}
});
} else if ((typeof arrayOrObjectBase === 'undefined' ? 'undefined' : _typeof(arrayOrObjectBase)) == 'object' && arrayOrObjectBase != null) {
base = (0, _helpers.flatKeys)(arrayOrObjectBase);
} else {
throw new Error('Invalid arguments.');
}
var compareResult = new Locales(this),
usedStrings = [];
compareResult.locales = compareResult.locales.filter(function (_ref2) {
var string = _ref2.string;
if (typeof string == 'undefined') {
return true;
}
usedStrings.push(string);
return !~base.indexOf(string);
});
compareResult.unused = base.filter(function (string) {
return !~usedStrings.indexOf(string);
});
return compareResult;
}
/**
* Get difference between locales parsed from JavaScript sources and locales from JSON files.
*
* @param {String|Array<String>} maskOrMasks
* @return {Promise<Locales>}
*/
}, {
key: 'diffFiles',
value: function diffFiles(maskOrMasks) {
var _this4 = this;
var cwd = process.cwd();
var masks = [];
if (typeof maskOrMasks == 'string') {
masks = [maskOrMasks];
} else if (Array.isArray(maskOrMasks)) {
masks = maskOrMasks;
} else {
throw new Error('Invalid arguments.');
}
var base = [];
return (0, _helpers.glob)(masks).then(function (files) {
return files.forEach(function (file) {
var json = require(_path2.default.resolve(cwd, file));
if (Array.isArray(json)) {
base.push.apply(base, _toConsumableArray(json));
} else {
base.push.apply(base, _toConsumableArray((0, _helpers.flatKeys)(json)));
}
});
}).then(function () {
return _this4.diff(base);
});
}
/**
* Generate report for terminal.
*
* @param {Boolean} withSummary
* @return {String}
*/
}, {
key: 'terminalReport',
value: function terminalReport() {
var withSummary = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : false;
var onlyStrings = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : false;
var locales = this.locales,
unused = this.unused,
added = [];
var report = '',
currentFile = '';
locales.forEach(function (_ref3) {
var file = _ref3.file,
string = _ref3.string,
fn = _ref3.fn,
codeFrame = _ref3.codeFrame;
var chunk = '';
if (typeof string == 'string') {
chunk += 'String:'.yellow + ' ' + string.green + '\n\n';
if (withSummary) {
(0, _helpers.pushUnique)(added, string);
}
} else if (!onlyStrings) {
chunk += 'Function:'.yellow + ' ' + fn.green + '\n\n';
} else {
return;
}
if (currentFile != file) {
currentFile = file;
chunk = '\n' + 'File:'.yellow + ' ' + file.underline.cyan + '\n\n' + chunk;
}
report += '' + chunk + codeFrame + '\n\n';
});
if (withSummary && (added.length || unused.length)) {
var summary = '\nSummary:\n\n'.yellow;
if (added.length) {
summary += ' ' + 'Added:'.yellow + '\n ' + added.join('\n ') + '\n\n';
}
if (unused.length) {
summary += ' ' + 'Unused (maybe):'.yellow + '\n ' + unused.join('\n ') + '\n\n';
}
report = summary + report;
}
return report;
}
/**
* Generate report as html.
*
* @param {Boolean} withSummary
* @return {String}
*/
}, {
key: 'htmlReport',
value: function htmlReport() {
var withSummary = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : false;
var onlyStrings = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : false;
var locales = this.locales,
unused = this.unused,
added = [];
var report = '',
currentFile = '';
locales.forEach(function (_ref4) {
var file = _ref4.file,
string = _ref4.string,
fn = _ref4.fn,
codeFrame = _ref4.codeFrame;
var chunk = '';
if (typeof string == 'string') {
chunk += '<h3>String: <span>' + string + '</span></h2>';
if (withSummary) {
(0, _helpers.pushUnique)(added, string);
}
} else if (!onlyStrings) {
chunk += '<h3>Function: <span>' + fn + '</span></h2>';
} else {
return;
}
if (currentFile != file) {
currentFile = file;
chunk = '<br><h1>File: <span>' + file + '</span></h1>' + chunk;
}
report += chunk + '<pre>' + (0, _escapeHtml2.default)(codeFrame.replace(/\t/g, ' ')) + '</pre>';
report += '<small>' + file + '</small><br>';
});
if (withSummary && (added.length || unused.length)) {
var summary = '<h1>Summary:</h1>';
if (added.length) {
summary += '<h3>Added:</h3><pre>' + (0, _escapeHtml2.default)(added.join('\n')) + '</pre><br>';
}
if (unused.length) {
summary += '<h3>Unused<sup>(maybe)</sup>:</h3><pre>' + (0, _escapeHtml2.default)(unused.join('\n')) + '</pre><br>';
}
report = summary + report;
}
report = '\n\t\t<!DOCTYPE html>\n\t\t<html>\n\t\t\t<head>\n\t\t\t\t<meta http-equiv="Content-Type" content="text/html; charset=utf-8">\n\t\t\t\t<title>REPORT</title>\n\t\t\t\t<style>\n\n\t\t\t\t\tbody {\n\t\t\t\t\t\tfont-family: Arial, Calibri;\n\t\t\t\t\t\toverflow-x: hidden;\n\t\t\t\t\t}\n\n\t\t\t\t\th1 {\n\t\t\t\t\t\tfont-size: 1.5em;\n\t\t\t\t\t}\n\n\t\t\t\t\th1 span {\n\t\t\t\t\t\tfont-family: monospace;\n\t\t\t\t\t\tfont-size: 1.4em;\n\t\t\t\t\t\tfont-weight: normal;\n\t\t\t\t\t\tmargin-left: .25em;\n\t\t\t\t\t\ttext-decoration: underline;\n\t\t\t\t\t}\n\n\t\t\t\t\th3 span {\n\t\t\t\t\t\tfont-family: monospace;\n\t\t\t\t\t\tfont-size: 1.45em;\n\t\t\t\t\t\tfont-weight: normal;\n\t\t\t\t\t\tmargin-left: .25em;\n\t\t\t\t\t}\n\n\t\t\t\t\th3 sup {\n\t\t\t\t\t\tfont-size: .7em;\n\t\t\t\t\t}\n\n\t\t\t\t\tpre {\n\t\t\t\t\t\tcolor: #000;\n\t\t\t\t\t\tpadding: 1em .5em;\n\t\t\t\t\t\tborder: 1px solid #000;\n\t\t\t\t\t}\n\n\t\t\t\t\tpre b {\n\t\t\t\t\t\tfont-weight: normal;\n\t\t\t\t\t}\n\n\t\t\t\t\tsmall {\n\t\t\t\t\t\tfont-family: monospace;\n\t\t\t\t\t}\n\n\t\t\t\t</style>\n\t\t\t</head>\n\t\t\t<body>\n\t\t\t\t' + this.convert.toHtml(report) + '\n\t\t\t</body>\n\t\t</html>';
return report;
}
}]);
return Locales;
}();
exports.default = Locales;
var LocaleSource = exports.LocaleSource = function () {
/**
* LocaleSource constructor.
*
* @param {String|LocaleSource} fileOrLocaleSource
* @param {String} code
* @param {Node} node
* @param {String} fn
* @param {String} string
* @return {LocaleSource}
*/
/**
* Locale string.
* @type {String}
*/
/**
* Column of token.
* @type {Number}
*/
/**
* Type of node.
* @type {String}
*/
function LocaleSource(fileOrLocaleSource, code, node, fn, string) {
_classCallCheck(this, LocaleSource);
this.file = null;
this.type = null;
this.line = null;
this.column = null;
this.fn = null;
this.string = null;
this.codeFrame = null;
if (fileOrLocaleSource instanceof LocaleSource) {
this.from(fileOrLocaleSource);
return;
}
var file = fileOrLocaleSource;
if (typeof file != 'string' && typeof file != 'undefined') {
throw new Error('File path must be a string');
}
if (typeof code != 'string') {
throw new Error('Code must be setted');
}
if ((typeof node === 'undefined' ? 'undefined' : _typeof(node)) != 'object' || typeof node.type != 'string') {
throw new Error('Node be setted');
}
if (typeof fn != 'string') {
throw new Error('Function name must be setted');
}
if (typeof string != 'string' && typeof string != 'undefined') {
throw new Error('Locale string must be setted');
}
this.file = file;
this.type = node.type;
this.line = node.loc.start.line;
this.column = node.loc.start.column + 1;
this.fn = fn;
this.string = string;
this.codeFrame = (0, _babelCodeFrame2.default)(code, this.line, this.column, codeFrameOptions);
}
/**
* Import data from other instance.
*
* @param {LocaleSource} localeSource
* @return {this}
*/
/**
* Code frame.
* @type {String}
*/
/**
* Function name.
* @type {String}
*/
/**
* Line of token.
* @type {Number}
*/
/**
* Path to file.
* @type {String}
*/
_createClass(LocaleSource, [{
key: 'from',
value: function from(localeSource) {
if (!(localeSource instanceof LocaleSource)) {
throw new Error('Invalid locale source.');
}
this.file = localeSource.file;
this.type = localeSource.type;
this.line = localeSource.line;
this.column = localeSource.column;
this.fn = localeSource.fn;
this.string = localeSource.string;
this.codeFrame = localeSource.codeFrame;
return this;
}
/**
* Create copy of this.
*
* @return {LocaleSource}
*/
}, {
key: 'copy',
value: function copy() {
return new LocaleSource(this);
}
}]);
return LocaleSource;
}();