Skip to content

Commit

Permalink
flat config support + eslint v9
Browse files Browse the repository at this point in the history
  • Loading branch information
timofei-iatsenko committed Oct 10, 2024
1 parent 6cc74a3 commit f22c2f7
Show file tree
Hide file tree
Showing 19 changed files with 588 additions and 808 deletions.
14 changes: 9 additions & 5 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -29,18 +29,22 @@
"**/*": "prettier --write --ignore-unknown"
},
"dependencies": {
"@typescript-eslint/utils": "^5.61.0"
"@typescript-eslint/utils": "^8.8.1"
},
"peerDependencies": {
"eslint": "^8.37.0 || ^9.0.0"
},
"devDependencies": {
"@types/eslint": "^8.40.2",
"@types/jest": "^29.5.13",
"@types/node": "^20.3.3",
"@typescript-eslint/parser": "^4.2.0",
"babel-eslint": "^10.0.3",
"eslint": "^8.44.0",
"@typescript-eslint/parser": "^8.8.1",
"@typescript-eslint/rule-tester": "^8.8.1",
"eslint": "^9.12.0",
"husky": "^8.0.3",
"jest": "^29.5.0",
"lint-staged": "^14.0.0",
"prettier": "2.1.2",
"prettier": "3.3.3",
"ts-jest": "^29.1.1",
"ts-node": "^10.9.1",
"typescript": "^5.1.6"
Expand Down
9 changes: 9 additions & 0 deletions src/create-rule.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import { ESLintUtils } from '@typescript-eslint/utils'

export type ExtraRuleDocs = {
recommended: 'strict' | 'error' | 'warn'
}

export const createRule = ESLintUtils.RuleCreator<ExtraRuleDocs>(
(name) => `https://github.com/lingui/eslint-plugin?tab=readme-ov-file#${name}`,
)
74 changes: 60 additions & 14 deletions src/index.ts
Original file line number Diff line number Diff line change
@@ -1,17 +1,63 @@
import noExpressionInMessageRule from './rules/no-expression-in-message'
import noUnlocalizedStringsRule from './rules/no-unlocalized-strings'
import noSingleTagToTranslateRule from './rules/no-single-tag-to-translate'
import noSingleVariablesToTranslateRule from './rules/no-single-variables-to-translate'
import tCallInFunctionRule from './rules/t-call-in-function'
import textRestrictionsRule from './rules/text-restrictions'
import noTransInsideTransRule from './rules/no-trans-inside-trans'
import * as noExpressionInMessageRule from './rules/no-expression-in-message'
import * as noUnlocalizedStringsRule from './rules/no-unlocalized-strings'
import * as noSingleTagToTranslateRule from './rules/no-single-tag-to-translate'
import * as noSingleVariablesToTranslateRule from './rules/no-single-variables-to-translate'
import * as tCallInFunctionRule from './rules/t-call-in-function'
import * as textRestrictionsRule from './rules/text-restrictions'
import * as noTransInsideTransRule from './rules/no-trans-inside-trans'

import { ESLint, Linter } from 'eslint'
import { FlatConfig, RuleModule } from '@typescript-eslint/utils/ts-eslint'

export const rules = {
'no-expression-in-message': noExpressionInMessageRule,
'no-unlocalized-strings': noUnlocalizedStringsRule,
'no-single-tag-to-translate': noSingleTagToTranslateRule,
'no-single-variables-to-translate': noSingleVariablesToTranslateRule,
't-call-in-function': tCallInFunctionRule,
'text-restrictions': textRestrictionsRule,
'no-trans-inside-trans': noTransInsideTransRule,
[noExpressionInMessageRule.name]: noExpressionInMessageRule.rule,
[noUnlocalizedStringsRule.name]: noUnlocalizedStringsRule.rule,
[noSingleTagToTranslateRule.name]: noSingleTagToTranslateRule.rule,
[noSingleVariablesToTranslateRule.name]: noSingleVariablesToTranslateRule.rule,
[tCallInFunctionRule.name]: tCallInFunctionRule.rule,
[textRestrictionsRule.name]: textRestrictionsRule.rule,
[noTransInsideTransRule.name]: noTransInsideTransRule.rule,
}

type RuleKey = keyof typeof rules

interface Plugin extends Omit<ESLint.Plugin, 'rules'> {
rules: Record<RuleKey, RuleModule<any, any, any>>
configs: {
recommended: ESLint.ConfigData
'flat/recommended': Array<Linter.FlatConfig>
}
}

const plugin: Plugin = {
meta: {
name: 'eslint-plugin-lingui',
},
configs: {} as Plugin['configs'],
rules,
}

const recommendedRules: { [K in RuleKey as `lingui/${K}`]?: FlatConfig.RuleLevel } = {
'lingui/t-call-in-function': 'error',
'lingui/no-single-tag-to-translate': 'warn',
'lingui/no-single-variable-to-translate': 'warn',
'lingui/no-trans-inside-trans': 'warn',
}

// Assign configs here so we can reference `plugin`
Object.assign(plugin.configs, {
recommended: {
plugins: ['lingui'],
rules: recommendedRules,
},
'flat/recommended': [
{
plugins: {
lingui: plugin,
},
rules: recommendedRules,
},
],
})

export default plugin
18 changes: 7 additions & 11 deletions src/rules/no-expression-in-message.ts
Original file line number Diff line number Diff line change
@@ -1,16 +1,14 @@
import { TSESTree } from '@typescript-eslint/utils'
import {
RuleContext,
RuleRecommendation,
RuleModule,
} from '@typescript-eslint/utils/dist/ts-eslint/Rule'
import { getNearestAncestor, isTTaggedTemplateExpression } from '../helpers'
import { createRule } from '../create-rule'

const rule: RuleModule<string, readonly unknown[]> = {
export const name = 'no-expression-in-message'
export const rule = createRule({
name: 'no-expression-in-message',
meta: {
docs: {
description: "doesn't allow functions or member expressions in templates",
recommended: 'error' as RuleRecommendation,
recommended: 'error',
},
messages: {
default: 'Should be ${variable}, not ${object.property} or ${my_function()}',
Expand All @@ -27,7 +25,7 @@ const rule: RuleModule<string, readonly unknown[]> = {

defaultOptions: [],

create: function (context: RuleContext<string, readonly unknown[]>) {
create: function (context) {
return {
'TemplateLiteral:exit'(node: TSESTree.TemplateLiteral) {
const noneIdentifierExpressions = node.expressions
Expand Down Expand Up @@ -56,6 +54,4 @@ const rule: RuleModule<string, readonly unknown[]> = {
},
}
},
}

export default rule
})
18 changes: 7 additions & 11 deletions src/rules/no-single-tag-to-translate.ts
Original file line number Diff line number Diff line change
@@ -1,15 +1,13 @@
import {
RuleContext,
RuleRecommendation,
RuleModule,
} from '@typescript-eslint/utils/dist/ts-eslint/Rule'
import { TSESTree } from '@typescript-eslint/utils'
import { createRule } from '../create-rule'

const rule: RuleModule<string, readonly unknown[]> = {
export const name = 'no-single-tag-to-translate'
export const rule = createRule({
name,
meta: {
docs: {
description: "doesn't allow <Trans></Trans> to wrap a single element unnecessarily.",
recommended: 'error' as RuleRecommendation,
recommended: 'error',
},
messages: {
default: '<Trans></Trans> should not wrap a single element unnecessarily',
Expand All @@ -26,7 +24,7 @@ const rule: RuleModule<string, readonly unknown[]> = {

defaultOptions: [],

create: function (context: RuleContext<string, readonly unknown[]>) {
create: function (context) {
return {
'JSXClosingElement > JSXIdentifier[name=Trans]'(node: TSESTree.JSXIdentifier) {
const parentJSXElement: TSESTree.JSXElement = node.parent?.parent as TSESTree.JSXElement
Expand All @@ -53,6 +51,4 @@ const rule: RuleModule<string, readonly unknown[]> = {
},
}
},
}

export default rule
})
19 changes: 8 additions & 11 deletions src/rules/no-single-variables-to-translate.ts
Original file line number Diff line number Diff line change
@@ -1,21 +1,20 @@
import { TSESTree } from '@typescript-eslint/utils'
import {
RuleContext,
RuleRecommendation,
RuleModule,
} from '@typescript-eslint/utils/dist/ts-eslint/Rule'

import {
getQuasisValue,
getNearestAncestor,
getIdentifierName,
isTTaggedTemplateExpression,
} from '../helpers'
import { createRule } from '../create-rule'

const rule: RuleModule<string, readonly unknown[]> = {
export const name = 'no-single-variable-to-translate'
export const rule = createRule({
name,
meta: {
docs: {
description: "doesn't allow single variables without text to translate",
recommended: 'error' as RuleRecommendation,
recommended: 'error',
},
messages: {
asJsx: "You couldn't translate just a variable, remove Trans or add some text inside",
Expand All @@ -33,7 +32,7 @@ const rule: RuleModule<string, readonly unknown[]> = {

defaultOptions: [],

create: function (context: RuleContext<string, readonly unknown[]>) {
create: function (context) {
const hasSomeJSXTextWithContent = (nodes: TSESTree.JSXChild[]): boolean => {
return nodes.some((jsxChild) => {
switch (jsxChild.type) {
Expand Down Expand Up @@ -87,6 +86,4 @@ const rule: RuleModule<string, readonly unknown[]> = {
},
}
},
}

export default rule
})
18 changes: 7 additions & 11 deletions src/rules/no-trans-inside-trans.ts
Original file line number Diff line number Diff line change
@@ -1,16 +1,14 @@
import { TSESTree } from '@typescript-eslint/utils'
import {
RuleContext,
RuleRecommendation,
RuleModule,
} from '@typescript-eslint/utils/dist/ts-eslint/Rule'
import { hasAncestorWithName, getIdentifierName } from '../helpers'
import { createRule } from '../create-rule'

const rule: RuleModule<string, readonly unknown[]> = {
export const name = 'no-trans-inside-trans'
export const rule = createRule({
name,
meta: {
docs: {
description: "doesn't allow Trans component be inside Trans component",
recommended: 'error' as RuleRecommendation,
recommended: 'error',
},
messages: {
default: "Trans couldn't be wrapped into Trans",
Expand All @@ -27,7 +25,7 @@ const rule: RuleModule<string, readonly unknown[]> = {

defaultOptions: [],

create: function (context: RuleContext<string, readonly unknown[]>) {
create: function (context) {
return {
JSXElement(node: TSESTree.JSXElement) {
const identifierName = getIdentifierName(node?.openingElement?.name)
Expand All @@ -42,6 +40,4 @@ const rule: RuleModule<string, readonly unknown[]> = {
},
}
},
}

export default rule
})
Loading

0 comments on commit f22c2f7

Please sign in to comment.