eslint-plugin-i18next
Advanced tools
+195
| exports.DOM_TAGS = [ | ||
| 'a', | ||
| 'abbr', | ||
| 'acronym', | ||
| 'address', | ||
| 'applet', | ||
| 'area', | ||
| 'article', | ||
| 'aside', | ||
| 'audio', | ||
| 'b', | ||
| 'base', | ||
| 'basefont', | ||
| 'bdi', | ||
| 'bdo', | ||
| 'big', | ||
| 'blockquote', | ||
| 'body', | ||
| 'br', | ||
| 'button', | ||
| 'canvas', | ||
| 'caption', | ||
| 'center', | ||
| 'cite', | ||
| 'code', | ||
| 'col', | ||
| 'colgroup', | ||
| 'data', | ||
| 'datalist', | ||
| 'dd', | ||
| 'del', | ||
| 'details', | ||
| 'dfn', | ||
| 'dialog', | ||
| 'dir', | ||
| 'div', | ||
| 'dl', | ||
| 'dt', | ||
| 'em', | ||
| 'embed', | ||
| 'fieldset', | ||
| 'figcaption', | ||
| 'figure', | ||
| 'font', | ||
| 'footer', | ||
| 'form', | ||
| 'frame', | ||
| 'frameset', | ||
| 'h1 to h6', | ||
| 'head', | ||
| 'header', | ||
| 'hr', | ||
| 'html', | ||
| 'i', | ||
| 'iframe', | ||
| 'img', | ||
| 'input', | ||
| 'ins', | ||
| 'kbd', | ||
| 'label', | ||
| 'legend', | ||
| 'li', | ||
| 'link', | ||
| 'main', | ||
| 'map', | ||
| 'mark', | ||
| 'meta', | ||
| 'meter', | ||
| 'nav', | ||
| 'noframes', | ||
| 'noscript', | ||
| 'object', | ||
| 'ol', | ||
| 'optgroup', | ||
| 'option', | ||
| 'output', | ||
| 'p', | ||
| 'param', | ||
| 'picture', | ||
| 'pre', | ||
| 'progress', | ||
| 'q', | ||
| 'rp', | ||
| 'rt', | ||
| 'ruby', | ||
| 's', | ||
| 'samp', | ||
| 'script', | ||
| 'section', | ||
| 'select', | ||
| 'small', | ||
| 'source', | ||
| 'span', | ||
| 'strike', | ||
| 'strong', | ||
| 'style', | ||
| 'sub', | ||
| 'summary', | ||
| 'sup', | ||
| 'svg', | ||
| 'table', | ||
| 'tbody', | ||
| 'td', | ||
| 'template', | ||
| 'textarea', | ||
| 'tfoot', | ||
| 'th', | ||
| 'thead', | ||
| 'time', | ||
| 'title', | ||
| 'tr', | ||
| 'track', | ||
| 'tt', | ||
| 'u', | ||
| 'ul', | ||
| 'var', | ||
| 'video', | ||
| 'wbr' | ||
| ]; | ||
| exports.SVG_TAGS = [ | ||
| 'a', | ||
| 'animate', | ||
| 'animateMotion', | ||
| 'animateTransform', | ||
| 'circle', | ||
| 'clipPath', | ||
| 'color-profile', | ||
| 'defs', | ||
| 'desc', | ||
| 'discard', | ||
| 'ellipse', | ||
| 'feBlend', | ||
| 'feColorMatrix', | ||
| 'feComponentTransfer', | ||
| 'feComposite', | ||
| 'feConvolveMatrix', | ||
| 'feDiffuseLighting', | ||
| 'feDisplacementMap', | ||
| 'feDistantLight', | ||
| 'feDropShadow', | ||
| 'feFlood', | ||
| 'feFuncA', | ||
| 'feFuncB', | ||
| 'feFuncG', | ||
| 'feFuncR', | ||
| 'feGaussianBlur', | ||
| 'feImage', | ||
| 'feMerge', | ||
| 'feMergeNode', | ||
| 'feMorphology', | ||
| 'feOffset', | ||
| 'fePointLight', | ||
| 'feSpecularLighting', | ||
| 'feSpotLight', | ||
| 'feTile', | ||
| 'feTurbulence', | ||
| 'filter', | ||
| 'foreignObject', | ||
| 'g', | ||
| 'hatch', | ||
| 'hatchpath', | ||
| 'image', | ||
| 'line', | ||
| 'linearGradient', | ||
| 'marker', | ||
| 'mask', | ||
| 'mesh', | ||
| 'meshgradient', | ||
| 'meshpatch', | ||
| 'meshrow', | ||
| 'metadata', | ||
| 'mpath', | ||
| 'path', | ||
| 'pattern', | ||
| 'polygon', | ||
| 'polyline', | ||
| 'radialGradient', | ||
| 'rect', | ||
| 'script', | ||
| 'set', | ||
| 'solidcolor', | ||
| 'stop', | ||
| 'style', | ||
| 'svg', | ||
| 'switch', | ||
| 'symbol', | ||
| 'text', | ||
| 'textPath', | ||
| 'title', | ||
| 'tspan', | ||
| 'unknown', | ||
| 'use', | ||
| 'view' | ||
| ]; |
+54
-0
@@ -5,2 +5,56 @@ # Changelog | ||
| ## [3.2.0](https://github.com/edvardchen/eslint-plugin-i18next/compare/v3.1.1...v3.2.0) (2019-10-21) | ||
| ### Features | ||
| * allow displayName property in classes ([5362281](https://github.com/edvardchen/eslint-plugin-i18next/commit/5362281)) | ||
| ### [3.1.1](https://github.com/edvardchen/eslint-plugin-i18next/compare/v3.1.0...v3.1.1) (2019-10-10) | ||
| ### Bug Fixes | ||
| * add missing plugin in recommended config ([dde83ed](https://github.com/edvardchen/eslint-plugin-i18next/commit/dde83ed)) | ||
| ## [3.1.0](https://github.com/edvardchen/eslint-plugin-i18next/compare/v3.0.0...v3.1.0) (2019-10-10) | ||
| ### Features | ||
| * ignore not-word string ([1752cbe](https://github.com/edvardchen/eslint-plugin-i18next/commit/1752cbe)) | ||
| ## [3.0.0](https://github.com/edvardchen/eslint-plugin-i18next/compare/v2.5.0...v3.0.0) (2019-10-09) | ||
| ### ⚠ BREAKING CHANGES | ||
| * SInce the whitelist was cut short, it would complain when the removed attributes | ||
| were added to custom component like <Foo src="hello" /> | ||
| ### Features | ||
| * ignore most DOM attrs ([71483c2](https://github.com/edvardchen/eslint-plugin-i18next/commit/71483c2)) | ||
| ## [2.5.0](https://github.com/edvardchen/eslint-plugin-i18next/compare/v2.4.0...v2.5.0) (2019-10-08) | ||
| ### Features | ||
| * add more ignored attributes and callee ([0f9e2ec](https://github.com/edvardchen/eslint-plugin-i18next/commit/0f9e2ec)) | ||
| ## [2.4.0](https://github.com/edvardchen/eslint-plugin-i18next/compare/v2.3.1...v2.4.0) (2019-10-08) | ||
| ### Features | ||
| * add ignoreAttribute option ([c854313](https://github.com/edvardchen/eslint-plugin-i18next/commit/c854313)) | ||
| ### [2.3.1](https://github.com/edvardchen/eslint-plugin-i18next/compare/v2.3.0...v2.3.1) (2019-09-16) | ||
| ### Bug Fixes | ||
| * whitelist addEventListener and few SVG attributes ([46241a6](https://github.com/edvardchen/eslint-plugin-i18next/commit/46241a6)) | ||
| ## [2.3.0](https://github.com/edvardchen/eslint-plugin-i18next/compare/v2.2.0...v2.3.0) (2019-07-26) | ||
@@ -7,0 +61,0 @@ |
+20
-0
@@ -0,1 +1,3 @@ | ||
| const { DOM_TAGS, SVG_TAGS } = require('./constants'); | ||
| function isUpperCase(str) { | ||
@@ -5,2 +7,20 @@ return /^[A-Z_-]+$/.test(str); | ||
| function isNativeDOMTag(str) { | ||
| return DOM_TAGS.includes(str); | ||
| } | ||
| function isSvgTag(str) { | ||
| return SVG_TAGS.includes(str); | ||
| } | ||
| const blacklistAttrs = ['placeholder', 'alt', 'aria-label', 'value']; | ||
| function isAllowedDOMAttr(tag, attr) { | ||
| if (isSvgTag(tag)) return true; | ||
| if (isNativeDOMTag(tag)) { | ||
| return !blacklistAttrs.includes(attr); | ||
| } | ||
| return false; | ||
| } | ||
| exports.isUpperCase = isUpperCase; | ||
| exports.isAllowedDOMAttr = isAllowedDOMAttr; |
+1
-0
@@ -22,2 +22,3 @@ /** | ||
| recommended: { | ||
| plugins: ['i18next'], | ||
| rules: { | ||
@@ -24,0 +25,0 @@ 'i18next/no-literal-string': [2] |
@@ -7,3 +7,3 @@ /** | ||
| const { isUpperCase } = require('../helper'); | ||
| const { isUpperCase, isAllowedDOMAttr } = require('../helper'); | ||
| // const { TypeFlags, SyntaxKind } = require('typescript'); | ||
@@ -37,2 +37,8 @@ | ||
| } | ||
| }, | ||
| ignoreAttribute: { | ||
| type: 'array', | ||
| items: { | ||
| type: 'string' | ||
| } | ||
| } | ||
@@ -51,5 +57,6 @@ }, | ||
| } = context; | ||
| const whitelists = ((option && option.ignore) || []).map( | ||
| item => new RegExp(item) | ||
| ); | ||
| const whitelists = [ | ||
| /^[^A-Za-z]+$/, // ignore not-word string | ||
| ...((option && option.ignore) || []) | ||
| ].map(item => new RegExp(item)); | ||
@@ -78,8 +85,21 @@ const calleeWhitelists = generateCalleeWhitelists(option); | ||
| if (calleeName === 'require') return true; | ||
| return calleeWhitelists.simple.indexOf(calleeName) !== -1; | ||
| } | ||
| const atts = ['className', 'style', 'styleName', 'src', 'type', 'id']; | ||
| const ignoredClassProperties = ['displayName']; | ||
| const ignoredAttributes = (option && option.ignoreAttribute) || []; | ||
| const userJSXAttrs = [ | ||
| 'className', | ||
| 'styleName', | ||
| 'type', | ||
| 'id', | ||
| 'width', | ||
| 'height', | ||
| ...ignoredAttributes | ||
| ]; | ||
| function isValidAttrName(name) { | ||
| return atts.includes(name); | ||
| return userJSXAttrs.includes(name); | ||
| } | ||
@@ -143,7 +163,15 @@ | ||
| const parent = getNearestAncestor(node, 'JSXAttribute'); | ||
| const attrName = parent.name.name; | ||
| // allow <div className="active" /> | ||
| if (isValidAttrName(parent.name.name)) { | ||
| // allow <MyComponent className="active" /> | ||
| if (isValidAttrName(attrName)) { | ||
| visited.add(node); | ||
| return; | ||
| } | ||
| const jsxElement = getNearestAncestor(node, 'JSXOpeningElement'); | ||
| const tagName = jsxElement.name.name; | ||
| if (isAllowedDOMAttr(tagName, attrName)) { | ||
| visited.add(node); | ||
| } | ||
| }, | ||
@@ -174,2 +202,10 @@ | ||
| 'ClassProperty > Literal'(node) { | ||
| const { parent } = node; | ||
| if (parent.key && ignoredClassProperties.includes(parent.key.name)) { | ||
| visited.add(node); | ||
| } | ||
| }, | ||
| 'VariableDeclarator > Literal'(node) { | ||
@@ -288,2 +324,6 @@ // allow statements like const A_B = "test" | ||
| const popularCallee = [ | ||
| 'addEventListener', | ||
| 'removeEventListener', | ||
| 'postMessage', | ||
| 'getElementById', | ||
| // | ||
@@ -297,3 +337,5 @@ // ─── VUEX CALLEE ──────────────────────────────────────────────────────────────── | ||
| 'includes', | ||
| 'indexOf' | ||
| 'indexOf', | ||
| 'endsWith', | ||
| 'startsWith' | ||
| ]; | ||
@@ -300,0 +342,0 @@ function generateCalleeWhitelists(option) { |
+10
-1
| { | ||
| "name": "eslint-plugin-i18next", | ||
| "version": "2.3.0", | ||
| "version": "3.2.0", | ||
| "description": "ESLint plugin for i18n", | ||
@@ -35,3 +35,5 @@ "keywords": [ | ||
| "husky": "^1.3.1", | ||
| "lint-staged": "^9.4.2", | ||
| "mocha": "^6.1.4", | ||
| "prettier": "^1.18.2", | ||
| "typescript": "^3.5.2", | ||
@@ -43,4 +45,11 @@ "vue-eslint-parser": "^6.0.3" | ||
| }, | ||
| "lint-staged": { | ||
| "*.js": [ | ||
| "prettier --write", | ||
| "git add" | ||
| ] | ||
| }, | ||
| "husky": { | ||
| "hooks": { | ||
| "pre-commit": "lint-staged", | ||
| "commit-msg": "commitlint -E HUSKY_GIT_PARAMS" | ||
@@ -47,0 +56,0 @@ } |
+25
-0
@@ -189,2 +189,16 @@ # eslint-plugin-i18next | ||
| #### SwithCase | ||
| Skip switchcase statement: | ||
| ```typescript | ||
| // correct | ||
| switch (type) { | ||
| case 'foo': | ||
| break; | ||
| case 'bar': | ||
| break; | ||
| } | ||
| ``` | ||
| ### Options | ||
@@ -215,1 +229,12 @@ | ||
| ``` | ||
| #### ignoreAttribute | ||
| The `ignoreAttribute` option specifies exceptions not to check for JSX attributes that match one of ignored attributes. | ||
| Examples of correct code for the `{ "ignoreAttribute": ["foo"] }` option: | ||
| ```jsx | ||
| /*eslint i18next/no-literal-string: ["error", { "ignoreAttribute": ["foo"] }]*/ | ||
| const element = <div foo="bar" />; | ||
| ``` |
@@ -38,4 +38,16 @@ /** | ||
| { code: 'a.includes("ios")' }, | ||
| { code: 'a.startsWith("ios")' }, | ||
| { code: 'a.endsWith("@gmail.com")' }, | ||
| { code: 'export * from "hello_export_all";' }, | ||
| { code: 'export { a } from "hello_export";' }, | ||
| { | ||
| code: | ||
| 'document.addEventListener("click", (event) => { event.preventDefault() })' | ||
| }, | ||
| { | ||
| code: | ||
| 'document.removeEventListener("click", (event) => { event.preventDefault() })' | ||
| }, | ||
| { code: 'window.postMessage("message", "*")' }, | ||
| { code: 'document.getElementById("some-id")' }, | ||
| { code: 'require("hello");' }, | ||
@@ -45,2 +57,4 @@ { code: 'const a = require(["hello"]);' }, | ||
| { code: 'const a = 1;' }, | ||
| { code: 'const a = "?";' }, | ||
| { code: `const a = "0123456789!@#$%^&*()_+|~-=\`[]{};':\\",./<>?";` }, | ||
| { code: 'i18n("hello");' }, | ||
@@ -59,6 +73,25 @@ { code: 'dispatch("hello");' }, | ||
| { code: 'var a = {foo: "FOO"};' }, | ||
| { code: 'class Form extends Component { displayName = "FormContainer" };' }, | ||
| // JSX | ||
| { code: '<div className="primary"></div>' }, | ||
| { code: '<div className={a ? "active": "inactive"}></div>' }, | ||
| { code: '<div>{i18next.t("foo")}</div>' } | ||
| { code: '<div>{i18next.t("foo")}</div>' }, | ||
| { code: '<svg viewBox="0 0 20 40"></svg>' }, | ||
| { code: '<line x1="0" y1="0" x2="10" y2="20" />' }, | ||
| { code: '<path d="M10 10" />' }, | ||
| { | ||
| code: | ||
| '<circle width="16px" height="16px" cx="10" cy="10" r="2" fill="red" />' | ||
| }, | ||
| { | ||
| code: | ||
| '<a href="https://google.com" target="_blank" rel="noreferrer noopener"></a>' | ||
| }, | ||
| { | ||
| code: '<div id="some-id" tabIndex="0" aria-labelledby="label-id"></div>' | ||
| }, | ||
| { code: '<div role="button"></div>' }, | ||
| { code: '<img src="./image.png" />' }, | ||
| { code: '<button type="button" for="form-id" />' }, | ||
| { code: '<DIV foo="bar" />', options: [{ ignoreAttribute: ['foo'] }] } | ||
| ], | ||
@@ -77,5 +110,9 @@ | ||
| { code: 'const a = "afoo";', options: [{ ignore: ['^foo'] }], errors }, | ||
| { code: 'class Form extends Component { property = "Something" };', errors }, | ||
| // JSX | ||
| { code: '<div>foo</div>', errors }, | ||
| { code: '<div>FOO</div>', errors } | ||
| { code: '<div>FOO</div>', errors }, | ||
| { code: '<DIV foo="bar" />', errors }, | ||
| { code: '<img src="./image.png" alt="some-image" />', errors }, | ||
| { code: '<button aria-label="Close" type="button" />', errors } | ||
| ] | ||
@@ -82,0 +119,0 @@ }); |
Dynamic require
Supply chain riskDynamic require can indicate the package is performing dangerous or unsafe dynamic code execution.
Found 1 instance in 1 package
URL strings
Supply chain riskPackage contains fragments of external URLs or IP addresses, which the package may be accessing at runtime.
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
38334
25.62%11
10%768
58.68%239
11.68%11
22.22%3
50%