eslint-plugin-m6web-i18n
Advanced tools
Comparing version 0.1.3 to 0.2.0
'use strict'; | ||
var noUnknownKey = require('./rules/no-unknown-key'); | ||
var noTextAsChildren = require('./rules/no-text-as-children'); | ||
var interpolationData = require('./rules/interpolation-data'); | ||
module.exports = { | ||
rules: { | ||
'no-unknown-key': require('./rules/no-unknown-key')('principalLangs'), | ||
'no-unknown-key-secondary-langs': require('./rules/no-unknown-key')('secondaryLangs'), | ||
'no-text-as-children': require('./rules/no-text-as-children') | ||
'no-unknown-key': noUnknownKey('principalLangs'), | ||
'no-unknown-key-secondary-langs': noUnknownKey('secondaryLangs'), | ||
'no-text-as-children': noTextAsChildren, | ||
'interpolation-data': interpolationData | ||
} | ||
}; |
'use strict'; | ||
module.exports = function (context) { | ||
var config = context.settings.i18n; | ||
var minimatch = require('minimatch'); | ||
if (!config || config.ignoreFiles && new RegExp(config.ignoreFiles).test(context.getFilename())) { | ||
return {}; | ||
} | ||
module.exports = { | ||
meta: { | ||
docs: { | ||
description: 'ensures that no plain text is used in JSX components', | ||
category: 'Possible errors' | ||
}, | ||
schema: [] | ||
}, | ||
create: function create(context) { | ||
var config = context.settings.i18n; | ||
return { | ||
JSXElement: function JSXElement(node) { | ||
node.children.forEach(function (child) { | ||
if (child.type === 'Literal') { | ||
var text = child.raw.trim().replace('\\n', ''); | ||
if (text.length) { | ||
context.report({ node: child, message: 'Untranslated text \'' + text + '\'' }); | ||
} | ||
} | ||
}); | ||
if (config && config.ignoreFiles && minimatch(context.getFilename(), config.ignoreFiles)) { | ||
return {}; | ||
} | ||
}; | ||
}; | ||
module.exports.schema = []; | ||
return { | ||
JSXElement: function JSXElement(node) { | ||
node.children.forEach(function (child) { | ||
if (child.type === 'Literal') { | ||
var text = child.raw.trim().replace('\\n', ''); | ||
if (text.length) { | ||
context.report({ node: child, message: 'Untranslated text \'' + text + '\'' }); | ||
} | ||
} | ||
}); | ||
} | ||
}; | ||
} | ||
}; |
@@ -5,79 +5,84 @@ 'use strict'; | ||
var _appRootPath = require('app-root-path'); | ||
var minimatch = require('minimatch'); | ||
var _appRootPath2 = _interopRequireDefault(_appRootPath); | ||
var _require = require('../utils/utils'), | ||
getKeyValue = _require.getKeyValue, | ||
has = _require.has, | ||
getLangConfig = _require.getLangConfig; | ||
var _utils = require('../utils/utils'); | ||
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } | ||
module.exports = function (langsKey) { | ||
return function (context) { | ||
var langConfig = []; | ||
var config = context.settings.i18n; | ||
return { | ||
meta: { | ||
docs: { | ||
description: 'ensures that used translate key is in translation file', | ||
category: 'Possible errors' | ||
}, | ||
schema: [] | ||
}, | ||
create: function create(context) { | ||
var config = context.settings.i18n; | ||
if (!config || config.ignoreFiles && new RegExp(config.ignoreFiles).test(context.getFilename())) { | ||
return {}; | ||
} | ||
if (!config || config.ignoreFiles && minimatch(context.getFilename(), config.ignoreFiles)) { | ||
return {}; | ||
} | ||
if (config && config[langsKey]) { | ||
langConfig = config[langsKey].map(function (_ref) { | ||
var name = _ref.name, | ||
translationPath = _ref.translationPath; | ||
return { | ||
name: name, | ||
translation: require(_appRootPath2.default + '/' + translationPath) | ||
}; | ||
}); | ||
} | ||
return { | ||
CallExpression: function CallExpression(node) { | ||
var funcName = node.callee.type === 'MemberExpression' && node.callee.property.name || node.callee.name; | ||
return { | ||
CallExpression: function CallExpression(node) { | ||
var funcName = node.callee.type === 'MemberExpression' && node.callee.property.name || node.callee.name; | ||
if (funcName !== config.functionName || !node.arguments || !node.arguments.length) { | ||
return; | ||
} | ||
if (funcName !== config.functionName || !node.arguments || !node.arguments.length) { | ||
return; | ||
} | ||
var _node$arguments = _slicedToArray(node.arguments, 3), | ||
keyNode = _node$arguments[0], | ||
countNode = _node$arguments[2]; | ||
var _node$arguments = _slicedToArray(node.arguments, 2), | ||
keyNode = _node$arguments[0], | ||
countNode = _node$arguments[1]; | ||
var key = getKeyValue(keyNode); | ||
var key = (0, _utils.getKeyValue)(keyNode); | ||
if (!key) { | ||
return; | ||
} | ||
if (!key) { | ||
return; | ||
} | ||
getLangConfig(config, langsKey).forEach(function (_ref) { | ||
var name = _ref.name, | ||
translation = _ref.translation; | ||
if (typeof countNode === 'undefined') { | ||
langConfig.forEach(function (_ref2) { | ||
var name = _ref2.name, | ||
translation = _ref2.translation; | ||
if (!translation) { | ||
context.report({ | ||
node: node, | ||
severity: 2, | ||
message: '\'' + name + '\' language is missing' | ||
}); | ||
if (!(0, _utils.has)(translation, key)) { | ||
context.report({ node: node, severity: 2, message: '\'' + key + '\' is missing from \'' + name + '\' language' }); | ||
return; | ||
} | ||
}); | ||
} else if (config.pluralizedKeys && config.pluralizedKeys.length) { | ||
langConfig.forEach(function (_ref3) { | ||
var name = _ref3.name, | ||
translation = _ref3.translation; | ||
var missingKeys = config.pluralizedKeys.reduce(function (accumulator, plural) { | ||
return (0, _utils.has)(translation, key + '.' + plural) ? accumulator : accumulator.concat(plural); | ||
}, []); | ||
if (missingKeys.length) { | ||
if (typeof countNode === 'undefined' && !has(translation, key)) { | ||
context.report({ | ||
node: node, | ||
message: '[' + missingKeys + '] keys are missing for key \'' + key + '\' in \'' + name + '\' language' | ||
severity: 2, | ||
message: '\'' + key + '\' is missing from \'' + name + '\' language' | ||
}); | ||
return; | ||
} | ||
if (countNode && Array.isArray(config.pluralizedKeys)) { | ||
var missingKeys = config.pluralizedKeys.filter(function (plural) { | ||
return !has(translation, key + '.' + plural); | ||
}); | ||
if (missingKeys.length) { | ||
context.report({ | ||
node: node, | ||
message: '[' + missingKeys + '] keys are missing for key \'' + key + '\' in \'' + name + '\' language' | ||
}); | ||
} | ||
} | ||
}); | ||
} | ||
} | ||
}; | ||
}; | ||
} | ||
}; | ||
}; | ||
module.exports.schema = []; | ||
}; |
'use strict'; | ||
Object.defineProperty(exports, "__esModule", { | ||
value: true | ||
}); | ||
var _has = function _has(object, keys, index) { | ||
var fs = require('fs'); | ||
var path = require('path'); | ||
var appRootPath = require('app-root-path'); | ||
var recursiveGet = function recursiveGet(object, keys, index) { | ||
if (keys.length - index === 1) { | ||
return !!object[keys[index]]; | ||
return object[keys[index]]; | ||
} | ||
return object[keys[index]] ? _has(object[keys[index]], keys, index + 1) : false; | ||
return object[keys[index]] ? recursiveGet(object[keys[index]], keys, index + 1) : undefined; | ||
}; | ||
var has = exports.has = function has(object, key) { | ||
return _has(object, key.split('.'), 0); | ||
exports.has = function (object, key) { | ||
return !!recursiveGet(object, key.split('.'), 0); | ||
}; | ||
var getKeyValue = exports.getKeyValue = function getKeyValue(key) { | ||
exports.get = function (object, key) { | ||
return recursiveGet(object, key.split('.'), 0); | ||
}; | ||
exports.getKeyValue = function (key) { | ||
if (key.type === 'Literal') { | ||
@@ -26,2 +31,30 @@ return key.value; | ||
return null; | ||
}; | ||
var langConfig = void 0; | ||
var expireAt = 0; | ||
exports.getLangConfig = function (config, languagesKey) { | ||
if (expireAt <= Date.now() || config.disableCache) { | ||
langConfig = config[languagesKey].map(function (_ref) { | ||
var name = _ref.name, | ||
translationPath = _ref.translationPath; | ||
try { | ||
var langFile = JSON.parse(fs.readFileSync(path.resolve(appRootPath + '/' + translationPath)).toString()); | ||
return { | ||
name: name, | ||
translation: langFile | ||
}; | ||
} catch (e) { | ||
return { | ||
name: name, | ||
translation: null | ||
}; | ||
} | ||
}); | ||
expireAt = Date.now() + (config.translationsCacheTTL || 500); | ||
} | ||
return langConfig; | ||
}; |
{ | ||
"name": "eslint-plugin-m6web-i18n", | ||
"version": "0.1.3", | ||
"version": "0.2.0", | ||
"description": "eslint plugin for generic i18n", | ||
@@ -16,3 +16,5 @@ "author": "M6web", | ||
"format": "prettier-eslint --write src/**/*.js", | ||
"lint": "eslint src/**/*.js" | ||
"lint": "eslint src/**/*.js", | ||
"test": "mocha --recursive tests", | ||
"prepack": "yarn lint && yarn test && yarn build" | ||
}, | ||
@@ -33,9 +35,12 @@ "peerDependencies": { | ||
"eslint-plugin-react": "^7.0.1", | ||
"mocha": "^3.4.2", | ||
"mversion": "^1.10.1", | ||
"prettier": "^1.3.0", | ||
"prettier-eslint-cli": "4.0.0" | ||
"prettier-eslint-cli": "^4.0.0", | ||
"sinon": "^2.3.7" | ||
}, | ||
"dependencies": { | ||
"app-root-path": "2.0.1" | ||
"app-root-path": "2.0.1", | ||
"minimatch": "3.0.4" | ||
} | ||
} |
@@ -29,5 +29,8 @@ # eslint-plugin-m6web-i18n | ||
* i18n/no-unknown-key: Verify that all translation keys you use are present in your primary translation files. | ||
* i18n/no-unknown-key-secondary-langs: Same as the previous one. Allow you to have a different error level for secondary languages. | ||
* i18n/no-text-as-children: Verify that you have no text children in your react code. | ||
* **i18n/no-unknown-key**: Verify that all translation keys you use are present in your primary translation files. | ||
* **i18n/no-unknown-key-secondary-langs**: Same as the previous one. Allow you to have a different error level for secondary languages. | ||
* **i18n/no-text-as-children**: Verify that you have no text children in your react code. | ||
* **i18n/interpolation-data**: Checks for usage of keys containing string interpolation, if translate function is called without | ||
interpolation data it will show an error. Also if interpolation data is given and key doesn't contain interpolation it will also | ||
show an error. `interpolationPattern` option is required to match interpolation in your translation file. | ||
@@ -47,3 +50,4 @@ ## Config | ||
"i18n/no-unknown-key-secondary-langs": "warn", | ||
"i18n/no-text-as-children": "error" | ||
"i18n/no-text-as-children": "error", | ||
"i18n/interpolation-data": ["error", { "interpolationPattern": "\\{\\.+\\}" }] | ||
}, | ||
@@ -76,7 +80,9 @@ // The plugin needs jsx feature to be on for 'no-text-as-children' rule | ||
// If you want to ignore specific files | ||
"ignoreFiles": "spec.js", | ||
"ignoreFiles": "**/*.spec.js", | ||
// If you have pluralization | ||
"pluralizedKeys": ["one", "other"] | ||
"pluralizedKeys": ["one", "other"], | ||
// TTL of the translations file caching (defaults to 500ms) | ||
"translationsCacheTTL": 300 | ||
} | ||
} | ||
``` |
@@ -0,7 +1,12 @@ | ||
const noUnknownKey = require('./rules/no-unknown-key'); | ||
const noTextAsChildren = require('./rules/no-text-as-children'); | ||
const interpolationData = require('./rules/interpolation-data'); | ||
module.exports = { | ||
rules: { | ||
'no-unknown-key': require('./rules/no-unknown-key')('principalLangs'), | ||
'no-unknown-key-secondary-langs': require('./rules/no-unknown-key')('secondaryLangs'), | ||
'no-text-as-children': require('./rules/no-text-as-children'), | ||
} | ||
'no-unknown-key': noUnknownKey('principalLangs'), | ||
'no-unknown-key-secondary-langs': noUnknownKey('secondaryLangs'), | ||
'no-text-as-children': noTextAsChildren, | ||
'interpolation-data': interpolationData, | ||
}, | ||
}; |
@@ -1,22 +0,31 @@ | ||
module.exports = context => { | ||
const config = context.settings.i18n; | ||
const minimatch = require('minimatch'); | ||
if (!config || (config.ignoreFiles && new RegExp(config.ignoreFiles).test(context.getFilename()))) { | ||
return {}; | ||
} | ||
module.exports = { | ||
meta: { | ||
docs: { | ||
description: 'ensures that no plain text is used in JSX components', | ||
category: 'Possible errors', | ||
}, | ||
schema: [], | ||
}, | ||
create(context) { | ||
const config = context.settings.i18n; | ||
return { | ||
JSXElement(node) { | ||
node.children.forEach(child => { | ||
if (child.type === 'Literal') { | ||
const text = child.raw.trim().replace('\\n', ''); | ||
if (text.length) { | ||
context.report({ node: child, message: `Untranslated text '${text}'` }); | ||
if (config && config.ignoreFiles && minimatch(context.getFilename(), config.ignoreFiles)) { | ||
return {}; | ||
} | ||
return { | ||
JSXElement(node) { | ||
node.children.forEach(child => { | ||
if (child.type === 'Literal') { | ||
const text = child.raw.trim().replace('\\n', ''); | ||
if (text.length) { | ||
context.report({ node: child, message: `Untranslated text '${text}'` }); | ||
} | ||
} | ||
} | ||
}); | ||
}, | ||
}; | ||
}); | ||
}, | ||
}; | ||
}, | ||
}; | ||
module.exports.schema = []; |
@@ -1,59 +0,69 @@ | ||
import appRootPath from 'app-root-path'; | ||
import { getKeyValue, has } from '../utils/utils'; | ||
const minimatch = require('minimatch'); | ||
const { getKeyValue, has, getLangConfig } = require('../utils/utils'); | ||
module.exports = langsKey => context => { | ||
let langConfig = []; | ||
const config = context.settings.i18n; | ||
module.exports = langsKey => ({ | ||
meta: { | ||
docs: { | ||
description: 'ensures that used translate key is in translation file', | ||
category: 'Possible errors', | ||
}, | ||
schema: [], | ||
}, | ||
create(context) { | ||
const config = context.settings.i18n; | ||
if (!config || (config.ignoreFiles && new RegExp(config.ignoreFiles).test(context.getFilename()))) { | ||
return {}; | ||
} | ||
if (!config || (config.ignoreFiles && minimatch(context.getFilename(), config.ignoreFiles))) { | ||
return {}; | ||
} | ||
if (config && config[langsKey]) { | ||
langConfig = config[langsKey].map(({ name, translationPath }) => ({ | ||
name, | ||
translation: require(`${appRootPath}/${translationPath}`), | ||
})); | ||
} | ||
return { | ||
CallExpression(node) { | ||
const funcName = (node.callee.type === 'MemberExpression' && node.callee.property.name) || node.callee.name; | ||
return { | ||
CallExpression(node) { | ||
const funcName = (node.callee.type === 'MemberExpression' && node.callee.property.name) || node.callee.name; | ||
if (funcName !== config.functionName || !node.arguments || !node.arguments.length) { | ||
return; | ||
} | ||
if (funcName !== config.functionName || !node.arguments || !node.arguments.length) { | ||
return; | ||
} | ||
const [keyNode, , countNode] = node.arguments; | ||
const key = getKeyValue(keyNode); | ||
const [keyNode, countNode] = node.arguments; | ||
const key = getKeyValue(keyNode); | ||
if (!key) { | ||
return; | ||
} | ||
if (!key) { | ||
return; | ||
} | ||
getLangConfig(config, langsKey).forEach(({ name, translation }) => { | ||
if (!translation) { | ||
context.report({ | ||
node, | ||
severity: 2, | ||
message: `'${name}' language is missing`, | ||
}); | ||
if (typeof countNode === 'undefined') { | ||
langConfig.forEach(({ name, translation }) => { | ||
if (!has(translation, key)) { | ||
context.report({ node, severity: 2, message: `'${key}' is missing from '${name}' language` }); | ||
return; | ||
} | ||
}); | ||
} else if (config.pluralizedKeys && config.pluralizedKeys.length) { | ||
langConfig.forEach(({ name, translation }) => { | ||
const missingKeys = config.pluralizedKeys.reduce( | ||
(accumulator, plural) => (has(translation, `${key}.${plural}`) ? accumulator : accumulator.concat(plural)), | ||
[], | ||
); | ||
if (missingKeys.length) { | ||
if (typeof countNode === 'undefined' && !has(translation, key)) { | ||
context.report({ | ||
node, | ||
message: `[${missingKeys}] keys are missing for key '${key}' in '${name}' language`, | ||
severity: 2, | ||
message: `'${key}' is missing from '${name}' language`, | ||
}); | ||
return; | ||
} | ||
if (countNode && Array.isArray(config.pluralizedKeys)) { | ||
const missingKeys = config.pluralizedKeys.filter(plural => !has(translation, `${key}.${plural}`)); | ||
if (missingKeys.length) { | ||
context.report({ | ||
node, | ||
message: `[${missingKeys}] keys are missing for key '${key}' in '${name}' language`, | ||
}); | ||
} | ||
} | ||
}); | ||
} | ||
}, | ||
}; | ||
}; | ||
module.exports.schema = []; | ||
}, | ||
}; | ||
}, | ||
}); |
@@ -1,12 +0,18 @@ | ||
const _has = (object, keys, index) => { | ||
const fs = require('fs'); | ||
const path = require('path'); | ||
const appRootPath = require('app-root-path'); | ||
const recursiveGet = (object, keys, index) => { | ||
if (keys.length - index === 1) { | ||
return !!object[keys[index]]; | ||
return object[keys[index]]; | ||
} | ||
return object[keys[index]] ? _has(object[keys[index]], keys, index + 1) : false; | ||
return object[keys[index]] ? recursiveGet(object[keys[index]], keys, index + 1) : undefined; | ||
}; | ||
export const has = (object, key) => _has(object, key.split('.'), 0); | ||
exports.has = (object, key) => !!recursiveGet(object, key.split('.'), 0); | ||
export const getKeyValue = key => { | ||
exports.get = (object, key) => recursiveGet(object, key.split('.'), 0); | ||
exports.getKeyValue = key => { | ||
if (key.type === 'Literal') { | ||
@@ -20,1 +26,26 @@ return key.value; | ||
}; | ||
let langConfig; | ||
let expireAt = 0; | ||
exports.getLangConfig = (config, languagesKey) => { | ||
if (expireAt <= Date.now() || config.disableCache) { | ||
langConfig = config[languagesKey].map(({ name, translationPath }) => { | ||
try { | ||
const langFile = JSON.parse(fs.readFileSync(path.resolve(`${appRootPath}/${translationPath}`)).toString()); | ||
return { | ||
name, | ||
translation: langFile, | ||
}; | ||
} catch (e) { | ||
return { | ||
name, | ||
translation: null, | ||
}; | ||
} | ||
}); | ||
expireAt = Date.now() + (config.translationsCacheTTL || 500); | ||
} | ||
return langConfig; | ||
}; |
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
Major refactor
Supply chain riskPackage has recently undergone a major refactor. It may be unstable or indicate significant internal changes. Use caution when updating to versions that include significant changes.
Found 1 instance in 1 package
New author
Supply chain riskA new npm collaborator published a version of the package for the first time. New collaborators are usually benign additions to a project, but do indicate a change to the security surface area of a package.
Found 1 instance in 1 package
Filesystem access
Supply chain riskAccesses the file system, and could potentially read sensitive data.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
Dynamic require
Supply chain riskDynamic require can indicate the package is performing dangerous or unsafe dynamic code execution.
Found 1 instance in 1 package
60135
24
439
86
3
15
2
+ Addedminimatch@3.0.4
+ Addedminimatch@3.0.4(transitive)