diff --git a/README.md b/README.md index 9c807db..c58206c 100644 --- a/README.md +++ b/README.md @@ -36,7 +36,7 @@ yarn add eslint-plugin-lingui --dev ### Recommended Setup -To enable all of the recommended rules for our plugin, add the following config: +To enable all the recommended rules for our plugin, add the following config: ```js import pluginLingui from 'eslint-plugin-lingui' @@ -47,6 +47,8 @@ export default [ ] ``` +We also recommend enabling the [no-unlocalized-strings](docs/rules/no-unlocalized-strings.md) rule. It’s not enabled by default because it needs to be set up specifically for your project. Please check the rule's documentation for example configurations. + ### Custom setup Alternatively, you can load the plugin and configure only the rules you want to use: diff --git a/docs/rules/no-unlocalized-strings.md b/docs/rules/no-unlocalized-strings.md index cd658a6..836fcd0 100644 --- a/docs/rules/no-unlocalized-strings.md +++ b/docs/rules/no-unlocalized-strings.md @@ -5,18 +5,78 @@ Ensures that all string literals, templates, and JSX text are wrapped using ` [!IMPORTANT] > This rule may require TypeScript type information. Enable this feature by setting `{ useTsTypes: true }`. +This rule is designed to **match all** JSXText, StringLiterals, and TmplLiterals, and then exclude some of them based on attributes, property names, variable names, and so on. + +The rule doesn’t come with built-in ignore settings because each project is unique and needs different configurations. You can use the following config as a starting point and then adjust it for your project: + + +```json5 +{ + "no-unlocalized-strings": [ + "error", + { + "ignore": [ + // Ignore strings that don’t start with an uppercase letter + // or don't contain two words separated by whitespace + "^(?![A-Z].*|\\w+\\s\\w+).+$", + // Ignore UPPERCASE literals + // Example: const test = "FOO" + "^[A-Z0-9_-]+$" + ], + "ignoreNames": [ + // Ignore matching className (case-insensitive) + { "regex": { "pattern": "className", "flags": "i" } }, + // Ignore UPPERCASE names + // Example: test.FOO = "ola!" + { "regex": { "pattern": "^[A-Z0-9_-]+$" } }, + "styleName", + "src", + "srcSet", + "type", + "id", + "width", + "height", + "displayName", + "Authorization" + ], + "ignoreFunctions": [ + "cva", + "cn", + "track", + "Error", + "console.*", + "*headers.set", + "*.addEventListener", + "*.removeEventListener", + "*.postMessage", + "*.getElementById", + "*.dispatch", + "*.commit", + "*.includes", + "*.indexOf", + "*.endsWith", + "*.startsWith", + "require" + ], + // Following settings require typed linting https://typescript-eslint.io/getting-started/typed-linting/ + "useTsTypes": true, + "ignoreMethodsOnType": [ + // Ignore specified methods on Map and Set types + "Map.get", + "Map.has", + "Set.has" + ] + } + ] +} +``` + ## Options ### `useTsTypes` Enables the rule to use TypeScript type information. Requires [typed linting](https://typescript-eslint.io/getting-started/typed-linting/) to be configured. -This option automatically excludes built-in methods such as `Map` and `Set`, and cases where string literals are used as TypeScript constants, e.g.: - -```ts -const a: 'abc' = 'abc' -``` - ### `ignore` Specifies patterns for string literals to ignore. Strings matching any of the provided regular expressions will not trigger the rule. @@ -28,17 +88,17 @@ Example for `{ "ignore": ["rgba"] }`: const color =
``` -### `ignoreFunction` +### `ignoreFunctions` Specifies functions whose string arguments should be ignored. Example of `correct` code with this option: ```js -/*eslint lingui/no-unlocalized-strings: ["error", {"ignoreFunction": ["showIntercomMessage"]}]*/ +/*eslint lingui/no-unlocalized-strings: ["error", {"ignoreFunctions": ["showIntercomMessage"]}]*/ showIntercomMessage('Please write me') -/*eslint lingui/no-unlocalized-strings: ["error", { "ignoreFunction": ["cva"] }]*/ +/*eslint lingui/no-unlocalized-strings: ["error", { "ignoreFunctions": ["cva"] }]*/ const labelVariants = cva('text-form-input-content-helper', { variants: { size: { @@ -49,131 +109,60 @@ const labelVariants = cva('text-form-input-content-helper', { }) ``` -This option also supports member expressions. Example for `{ "ignoreFunction": ["console.log"] }`: +This option also supports member expressions. Example for `{ "ignoreFunctions": ["console.log"] }`: ```js -/*eslint lingui/no-unlocalized-strings: ["error", {"ignoreFunction": ["console.log"]}]*/ +/*eslint lingui/no-unlocalized-strings: ["error", {"ignoreFunctions": ["console.log"]}]*/ console.log('Log this message') ``` You can use patterns (processed by [micromatch](https://www.npmjs.com/package/micromatch)) to match function calls. ```js -/*eslint lingui/no-unlocalized-strings: ["error", {"ignoreFunction": ["console.*"]}]*/ +/*eslint lingui/no-unlocalized-strings: ["error", {"ignoreFunctions": ["console.*"]}]*/ console.log('Log this message') ``` ```js -/*eslint lingui/no-unlocalized-strings: ["error", {"ignoreFunction": ["*.headers.set"]}]*/ +/*eslint lingui/no-unlocalized-strings: ["error", {"ignoreFunctions": ["*.headers.set"]}]*/ context.headers.set('Authorization', `Bearer ${token}`) ``` Dynamic segments are replaced with `$`, you can target them as ```js -/*eslint lingui/no-unlocalized-strings: ["error", {"ignoreFunction": ["foo.$.set"]}]*/ +/*eslint lingui/no-unlocalized-strings: ["error", {"ignoreFunctions": ["foo.$.set"]}]*/ foo[getName()].set('Hello') ``` -### `ignoreAttribute` - -Specifies JSX attributes that should be ignored. By default, the attributes `className`, `styleName`, `type`, `id`, `width`, and `height` are ignored. - -Example for `{ "ignoreAttribute": ["style"] }`: - -```jsx -/*eslint lingui/no-unlocalized-strings: ["error", {"ignoreAttribute": ["style"]}]*/ -const element =
-``` - -#### `regex` - -Defines regex patterns for ignored attributes. - -Example: - -```json -{ - "no-unlocalized-strings": [ - "error", - { - "ignoreAttribute": [ - { - "regex": { - "pattern": "classname", - "flags": "i" - } - } - ] - } - ] -} -``` - -Example of **correct** code: - -```jsx -const element =
-``` - -### `strictAttribute` - -Specifies JSX attributes that should always be checked, regardless of other `ignore` settings or defaults. - -Example for `{ "strictAttribute": ["alt"] }`: +### `ignoreNames` -```jsx -/*eslint lingui/no-unlocalized-strings: ["error", {"strictAttribute": ["alt"]}]*/ -const element = IMAGE -``` - -#### `regex` - -Defines regex patterns for attributes that must always be checked. - -Example: +List of identifier names to ignore across attributes, properties, and variables. Use this option to exclude specific names, like "className", from being flagged by the rule. This option covers any of these contexts: JSX attribute names, variable names, or property names. -```json -{ - "no-unlocalized-strings": [ - "error", - { - "strictAttribute": [ - { - "regex": { - "pattern": "^desc.*" - } - } - ] - } - ] -} -``` +Example for `{ "ignoreNames": ["style"] }`: -Examples of **incorrect** code: +Example of `correct` code with this option: ```jsx -const element =
-``` - -### `ignoreProperty` - -Specifies object property names whose values should be ignored. By default, UPPERCASED properties and `className`, `styleName`, `type`, `id`, `width`, `height`, and `displayName` are ignored. - -Example for `{ "ignoreProperty": ["myProperty"] }`: +/* eslint lingui/no-unlocalized-strings: ["error", {"ignoreNames": ["style"]}] */ +// ignored by JSX sttribute name +const element =
+// ignored by variable name +const style = 'Ignored value!' -```jsx -const obj = { myProperty: 'Ignored value' } -obj.myProperty = 'Ignored value' +/* eslint lingui/no-unlocalized-strings: ["error", {"ignoreNames": ["displayName"]}] */ +// ignored by property name +const obj = { displayName: 'Ignored value' } +obj.displayName = 'Ignored value' class MyClass { - myProperty = 'Ignored value' + displayName = 'Ignored value' } ``` #### `regex` -Defines regex patterns for ignored properties. +Defines regex patterns for ignored names. Example: @@ -182,7 +171,7 @@ Example: "no-unlocalized-strings": [ "error", { - "ignoreProperty": [ + "ignoreNames": [ { "regex": { "pattern": "classname", @@ -195,9 +184,16 @@ Example: } ``` -Examples of **correct** code: +Example of **correct** code: ```jsx +// ignored by JSX attribute name +const element =
+ +// ignored by variable name +const wrapperClassName = 'Ignored value' + +// ignored by property name const obj = { wrapperClassName: 'Ignored value' } obj.wrapperClassName = 'Ignored value' @@ -206,46 +202,6 @@ class MyClass { } ``` -### `ignoreVariable` - -Specifies variable name whose values should be ignored. By default, UPPERCASED variables are ignored. - -Example for `{ "ignoreVariable": ["myVariable"] }`: - -```jsx -const myVariable = 'Ignored value' -``` - -#### `regex` - -Defines regex patterns for ignored variables. - -Example: - -```json -{ - "no-unlocalized-strings": [ - "error", - { - "ignoreVariable": [ - { - "regex": { - "pattern": "classname", - "flags": "i" - } - } - ] - } - ] -} -``` - -Examples of **correct** code: - -```jsx -const wrapperClassName = 'Ignored value' -``` - ### `ignoreMethodsOnTypes` Uses TypeScript type information to ignore methods defined on specific types. @@ -264,5 +220,3 @@ interface Foo { const foo: Foo foo.get('Some string') ``` - -The following methods are ignored by default: `Map.get`, `Map.has`, `Set.has`. diff --git a/src/helpers.ts b/src/helpers.ts index a085b04..16655ec 100644 --- a/src/helpers.ts +++ b/src/helpers.ts @@ -29,8 +29,6 @@ export const LinguiCallExpressionMessageQuery = */ export const LinguiTransQuery = 'JSXElement[openingElement.name.name=Trans]' -export const UpperCaseRegexp = /^[A-Z_-]+$/ - export function isNativeDOMTag(str: string) { return DOM_TAGS.includes(str) } @@ -39,27 +37,13 @@ export function isSvgTag(str: string) { return SVG_TAGS.includes(str) } -export function containAllAttributes(attributeNames: string[]) { - const attrs = ['value', 'other'] - return attrs.every((attr) => attributeNames.includes(attr)) -} - -export function isLinguiTags(str: string, attributeNames: string[]) { - if (str === 'Trans') { - return true - } - return ['Plural', 'Select'].includes(str) && containAllAttributes(attributeNames) -} - const blacklistAttrs = ['placeholder', 'alt', 'aria-label', 'value'] export function isAllowedDOMAttr(tag: string, attr: string, attributeNames: string[]) { if (isSvgTag(tag)) return true if (isNativeDOMTag(tag)) { return !blacklistAttrs.includes(attr) } - if (isLinguiTags(tag, attributeNames)) { - return true - } + return false } @@ -87,26 +71,6 @@ export const getText = ( return node.value.toString().trim() } -export function hasAncestorWithName( - node: TSESTree.JSXElement | TSESTree.TemplateLiteral | TSESTree.Literal | TSESTree.JSXText, - name: string, -) { - let p: TSESTree.Node = node.parent - while (p) { - switch (p.type) { - case TSESTree.AST_NODE_TYPES.JSXElement: - const identifierName = getIdentifierName(p?.openingElement?.name) - if (identifierName === name) { - return true - } - default: - } - - p = p.parent - } - return false -} - export function getIdentifierName(jsxTagNameExpression: TSESTree.JSXTagNameExpression) { switch (jsxTagNameExpression.type) { case TSESTree.AST_NODE_TYPES.JSXIdentifier: diff --git a/src/rules/no-unlocalized-strings.ts b/src/rules/no-unlocalized-strings.ts index 3c63d4f..24d244c 100644 --- a/src/rules/no-unlocalized-strings.ts +++ b/src/rules/no-unlocalized-strings.ts @@ -9,14 +9,12 @@ import { getIdentifierName, getNearestAncestor, getText, - hasAncestorWithName, isAllowedDOMAttr, isIdentifier, isJSXAttribute, isLiteral, isMemberExpression, isTemplateLiteral, - UpperCaseRegexp, } from '../helpers' import { createRule } from '../create-rule' import * as micromatch from 'micromatch' @@ -25,11 +23,8 @@ type MatcherDef = string | { regex: { pattern: string; flags?: string } } export type Option = { ignore?: string[] - ignoreFunction?: string[] - ignoreAttribute?: MatcherDef[] - strictAttribute?: MatcherDef[] - ignoreProperty?: MatcherDef[] - ignoreVariable?: MatcherDef[] + ignoreFunctions?: string[] + ignoreNames?: MatcherDef[] ignoreMethodsOnTypes?: string[] useTsTypes?: boolean } @@ -62,15 +57,13 @@ const MatcherSchema: JSONSchema.JSONSchema4 = { ], } -function preparePatterns(items: MatcherDef[]): (string | RegExp)[] { - return items.map((item) => +function createMatcher(patterns: MatcherDef[]) { + const _patterns = patterns.map((item) => typeof item === 'string' ? item : new RegExp(item.regex.pattern, item.regex.flags), ) -} -function createMatcher(patterns: (string | RegExp)[]) { return (str: string) => { - return patterns.some((pattern) => { + return _patterns.some((pattern) => { if (typeof pattern === 'string') { return pattern === str } @@ -105,7 +98,11 @@ export const rule = createRule({ type: 'string', }, }, - ignoreFunction: { + ignoreNames: { + type: 'array', + items: MatcherSchema, + }, + ignoreFunctions: { type: 'array', items: { type: 'string', @@ -117,22 +114,6 @@ export const rule = createRule({ type: 'string', }, }, - ignoreAttribute: { - type: 'array', - items: MatcherSchema, - }, - strictAttribute: { - type: 'array', - items: MatcherSchema, - }, - ignoreProperty: { - type: 'array', - items: MatcherSchema, - }, - ignoreVariable: { - type: 'array', - items: MatcherSchema, - }, useTsTypes: { type: 'boolean', }, @@ -155,32 +136,20 @@ export const rule = createRule({ tsService = ESLintUtils.getParserServices(context, false) } const whitelists = [ - /^[^A-Za-z]+$/, // ignore not-word string - UpperCaseRegexp, - /^(?![A-Z].*|\w+\s\w+).+$/, - ...((option && option.ignore) || []), - ].map((item) => new RegExp(item)) + // + /^[^\p{L}]+$/u, // ignore non word messages + ...(option?.ignore || []).map((item) => new RegExp(item)), + ] const calleeWhitelists = [ - // popular callee - '*.addEventListener', - '*.removeEventListener', - '*.postMessage', - '*.getElementById', - '*.dispatch', - '*.commit', - '*.includes', - '*.indexOf', - '*.endsWith', - '*.startsWith', - 'require', - // lingui callee 'i18n._', 't', 'plural', 'select', - ...(option?.ignoreFunction || []), + 'selectOrdinal', + 'msg', + ...(option?.ignoreFunctions || []), ].map((pattern) => micromatch.matcher(pattern)) const isCalleeWhitelisted = (callee: string) => @@ -233,46 +202,9 @@ export const rule = createRule({ } } - const ignoredJSXElements = ['Trans'] const ignoredJSXSymbols = ['←', ' ', '·'] - const strictAttributes = [...preparePatterns(option?.strictAttribute || [])] - - const ignoredAttributes = [ - 'className', - 'styleName', - 'type', - 'id', - 'width', - 'height', - - ...preparePatterns(option?.ignoreAttribute || []), - ] - - const ignoredMethodsOnTypes = [ - 'Map.get', - 'Map.has', - 'Set.has', - ...(option?.ignoreMethodsOnTypes || []), - ] - - const ignoredProperties = [ - 'className', - 'styleName', - 'type', - 'id', - 'width', - 'height', - 'displayName', - UpperCaseRegexp, - ...preparePatterns(option?.ignoreProperty || []), - ] - - const ignoreVariable = [ - // - UpperCaseRegexp, - ...preparePatterns(option?.ignoreVariable || []), - ] + const ignoredMethodsOnTypes = option?.ignoreMethodsOnTypes || [] //---------------------------------------------------------------------- // Public @@ -283,16 +215,7 @@ export const rule = createRule({ return ignoredJSXSymbols.some((name) => name === str) } - const isIgnoredAttribute = createMatcher(ignoredAttributes) - const isIgnoredProperty = createMatcher(ignoredProperties) - const isIgnoredVariable = createMatcher(ignoreVariable) - const isStrictAttribute = createMatcher(strictAttributes) - - function isIgnoredJSXElement( - node: TSESTree.Literal | TSESTree.TemplateLiteral | TSESTree.JSXText, - ) { - return ignoredJSXElements.some((name) => hasAncestorWithName(node, name)) - } + const isIgnoredName = createMatcher(option?.ignoreNames || []) function isStringLiteral(node: TSESTree.Literal | TSESTree.TemplateLiteral | TSESTree.JSXText) { switch (node.type) { @@ -320,7 +243,7 @@ export const rule = createRule({ visited.add(node) const text = getText(node) - if (!text || isIgnoredJSXElement(node) || isIgnoredSymbol(text)) { + if (!text || isIgnoredSymbol(text)) { return } @@ -350,8 +273,10 @@ export const rule = createRule({ visited.add(node) }, - JSXText(node: TSESTree.JSXText) { - processTextNode(node) + [`:matches(${['Trans', 'Plural', 'Select', 'SelectOrdinal'].map((name) => `JSXElement[openingElement.name.name=${name}]`)}) :matches(TemplateLiteral, Literal, JSXText)`]( + node, + ) { + visited.add(node) }, 'JSXElement > Literal'(node: TSESTree.Literal) { @@ -375,14 +300,8 @@ export const rule = createRule({ ) const attrName = getAttrName(parent?.name?.name) - if (isStrictAttribute(attrName)) { - visited.add(node) - context.report({ node, messageId: 'default' }) - return - } - // allow - if (isIgnoredAttribute(attrName)) { + if (isIgnoredName(attrName)) { visited.add(node) return } @@ -416,7 +335,7 @@ export const rule = createRule({ //@ts-ignore parent.type === 'ClassProperty') && isIdentifier(parent.key) && - isIgnoredProperty(parent.key.name) + isIgnoredName(parent.key.name) ) { visited.add(node) } @@ -432,7 +351,7 @@ export const rule = createRule({ const parent = node.parent as TSESTree.VariableDeclarator // allow statements like const A_B = "test" - if (isIdentifier(parent.id) && isIgnoredVariable(parent.id.name)) { + if (isIdentifier(parent.id) && isIgnoredName(parent.id.name)) { visited.add(node) } }, @@ -443,7 +362,7 @@ export const rule = createRule({ // {A_B: "hello world"}; // ^^^^ - if (isIdentifier(parent.key) && isIgnoredProperty(parent.key.name)) { + if (isIdentifier(parent.key) && isIgnoredName(parent.key.name)) { visited.add(node) } @@ -451,7 +370,7 @@ export const rule = createRule({ // ^^^^ if ( (isLiteral(parent.key) || isTemplateLiteral(parent.key)) && - isIgnoredProperty(getText(parent.key)) + isIgnoredName(getText(parent.key)) ) { visited.add(node) } @@ -470,7 +389,7 @@ export const rule = createRule({ if ( !memberExp.computed && isIdentifier(memberExp.property) && - isIgnoredProperty(memberExp.property.name) + isIgnoredName(memberExp.property.name) ) { visited.add(node) } @@ -534,6 +453,10 @@ export const rule = createRule({ visited.add(node) }, + 'JSXText:exit'(node: TSESTree.JSXText) { + if (visited.has(node)) return + processTextNode(node) + }, 'Literal:exit'(node: TSESTree.Literal) { // visited and passed linting if (visited.has(node)) return @@ -544,15 +467,15 @@ export const rule = createRule({ // // TYPESCRIPT + // todo: that doesn't work + // if (tsService) { + // const typeObj = tsService.getTypeAtLocation(node.parent) // - if (tsService) { - const typeObj = tsService.getTypeAtLocation(node.parent) - - // var a: 'abc' = 'abc' - if (typeObj.isStringLiteral() && typeObj.symbol) { - return - } - } + // // var a: 'abc' = 'abc' + // if (typeObj.isStringLiteral() && typeObj.symbol) { + // return + // } + // } context.report({ node, messageId: 'default' }) }, diff --git a/tests/src/rules/no-unlocalized-strings.test.ts b/tests/src/rules/no-unlocalized-strings.test.ts index b184d20..bdaa34f 100644 --- a/tests/src/rules/no-unlocalized-strings.test.ts +++ b/tests/src/rules/no-unlocalized-strings.test.ts @@ -16,6 +16,9 @@ const ruleTester = new RuleTester({ }, }) +const upperCaseRegex = '^[A-Z_-]+$' +const ignoreUpperCaseName = { ignoreNames: [{ regex: { pattern: upperCaseRegex } }] } + const errors = [{ messageId: 'default' }] // default errors ruleTester.run(name, rule, { @@ -24,97 +27,68 @@ ruleTester.run(name, rule, { code: 'i18n._(t`Hello ${nice}`)', }, { code: 't(i18n)({ message: `Hello ${name}` })' }, + { + name: 'should ignore non word strings', + code: 'const test = "1111"', + }, + { + name: 'should ignore non word strings 2', + code: 'const a = `0123456789!@#$%^&*()_+|~-=\\`[]{};\':",./<>?`;', + }, + { + code: 'hello("Hello")', + options: [{ ignoreFunctions: ['hello'] }], + }, + { + code: 'new Error("hello")', + options: [{ ignoreFunctions: ['Error'] }], + }, { code: 'custom.wrapper()({message: "Hello!"})', - options: [{ ignoreFunction: ['custom.wrapper'] }], + options: [{ ignoreFunctions: ['custom.wrapper'] }], }, { name: 'Should ignore calls using complex object.method expression', code: 'console.log("Hello")', - options: [{ ignoreFunction: ['console.log'] }], + options: [{ ignoreFunctions: ['console.log'] }], }, { name: 'Should ignore method calls using pattern', code: 'console.log("Hello"); console.error("Hello");', - options: [{ ignoreFunction: ['console.*'] }], + options: [{ ignoreFunctions: ['console.*'] }], }, { name: 'Should ignore methods multilevel', code: 'context.headers.set("Hello"); level.context.headers.set("Hello");', - options: [{ ignoreFunction: ['*.headers.set'] }], + options: [{ ignoreFunctions: ['*.headers.set'] }], + }, + { + name: 'Should ignore methods multilevel 2', + code: 'headers.set("Hello"); level.context.headers.set("Hello");', + options: [{ ignoreFunctions: ['*headers.set'] }], }, { name: 'Should ignore methods with dynamic segment ', code: 'getData().two.three.four("Hello")', - options: [{ ignoreFunction: ['*.three.four'] }], + options: [{ ignoreFunctions: ['*.three.four'] }], }, { code: 'name === `Hello brat` || name === `Nice have`' }, { code: 'switch(a){ case `a`: break; default: break;}' }, - { code: 'a.indexOf(`ios`)' }, - { code: 'a.includes(`ios`)' }, - { code: 'a.startsWith(`ios`)' }, - { code: 'a.endsWith(`@gmail.com`)' }, - { - 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`);' }, - { code: 'const a = require([`hello`]);' }, - { code: 'const a = require([`hel` + `lo`]);' }, - { code: 'const a = `?`;' }, - { code: 'const a = `0123456789!@#$%^&*()_+|~-=\\`[]{};\':",./<>?`;' }, { code: 'i18n._(`hello`);' }, - { code: 'dispatch(`hello`);' }, - { code: 'store.dispatch(`hello`);' }, - { code: 'store.commit(`hello`);' }, { code: 'const a = `absfoo`;', options: [{ ignore: ['foo'] }] }, { code: 'const a = `fooabc`;', options: [{ ignore: ['^foo'] }] }, - { code: 'const a = `FOO`;' }, { code: "name === 'Hello brat' || name === 'Nice have'" }, { code: "switch(a){ case 'a': break; default: break;}" }, { code: 'import name from "hello";' }, - { code: 'a.indexOf("ios")' }, - { 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", "Ola!")' }, - { code: 'document.getElementById("some-id")' }, - { code: 'require("hello");' }, - { code: 'const a = require(["hello"]);' }, - { code: 'const a = require(["hel" + "lo"]);' }, + { code: 'const a = require(["hello"]);', options: [{ ignoreFunctions: ['require'] }] }, + { code: 'const a = require(["hel" + "lo"]);', options: [{ ignoreFunctions: ['require'] }] }, { code: 'const a = 1;' }, - { code: 'const a = "?";' }, - { code: `const a = "0123456789!@#$%^&*()_+|~-=\`[]{};':\\",./<>?";` }, { code: 'i18n._("hello");' }, - { code: 'dispatch("hello");' }, - { code: 'store.dispatch("hello");' }, - { code: 'store.commit("hello");' }, { code: 'const a = "absfoo";', options: [{ ignore: ['foo'] }] }, { code: 'const a = "fooabc";', options: [{ ignore: ['^foo'] }] }, - { code: 'const a = "FOO";' }, - { code: 'var A_B = "world";' }, - { code: 'var A_B = `world`;' }, - { code: 'var a = {["A_B"]: "hello world"};' }, - { code: 'var a = {[A_B]: "hello world"};' }, - { code: 'var a = {A_B: "hello world"};' }, - { code: 'var a = {foo: "FOO"};' }, - { code: 'var a = {[`A_B`]: `hello world`};' }, - { code: 'var a = {[A_B]: `hello world`};' }, - { code: 'var a = {A_B: `hello world`};' }, - { code: 'var a = {foo: `FOO`};' }, - { code: 'class Form extends Component { displayName = "FormContainer" };' }, + // // JSX { code: '
' }, { code: '
' }, @@ -151,46 +125,23 @@ ruleTester.run(name, rule, { { code: '' }, { code: '`, + filename: 'a.tsx', + errors: [{ messageId: 'forJsxText' }], + }, + { + code: ``, + filename: 'a.tsx', + errors: [{ messageId: 'forJsxText' }], + }, + { + code: "function Button({ t= 'Name' }: {t: 'name' & 'Abs'}){} ", + errors, + }, + { + code: "function Button({ t= 'Name' }: {t: 1 | 'Abs'}){} ", + errors, + }, + { code: "var a: {text: string} = {text: 'Bold'}", errors }, { code: `function getQueryPlaceholder(compact: boolean | undefined) { return compact || mobileMediaQuery.matches @@ -320,6 +379,10 @@ jsxTester.run('no-unlocalized-strings', rule, { { code: '', }, + { code: " " }, + { code: '# Book} other={<># Books} /> ' }, + { code: "