mirror of https://github.com/langgenius/dify.git
remove eslint rule
This commit is contained in:
parent
e6dbd1facf
commit
c07f38b4c2
|
|
@ -1,14 +0,0 @@
|
|||
import noLegacyNamespacePrefix from './rules/no-legacy-namespace-prefix.js'
|
||||
|
||||
/** @type {import('eslint').ESLint.Plugin} */
|
||||
const plugin = {
|
||||
meta: {
|
||||
name: 'dify-i18n',
|
||||
version: '1.0.0',
|
||||
},
|
||||
rules: {
|
||||
'no-legacy-namespace-prefix': noLegacyNamespacePrefix,
|
||||
},
|
||||
}
|
||||
|
||||
export default plugin
|
||||
|
|
@ -1,87 +0,0 @@
|
|||
// Auto-generated from i18n-config/i18next-config.ts
|
||||
// Keep in sync with the namespaces object
|
||||
|
||||
// @keep-sorted
|
||||
export const NAMESPACES = [
|
||||
'app',
|
||||
'appAnnotation',
|
||||
'appApi',
|
||||
'appDebug',
|
||||
'appLog',
|
||||
'appOverview',
|
||||
'billing',
|
||||
'common',
|
||||
'custom',
|
||||
'dataset',
|
||||
'datasetCreation',
|
||||
'datasetDocuments',
|
||||
'datasetHitTesting',
|
||||
'datasetPipeline',
|
||||
'datasetSettings',
|
||||
'education',
|
||||
'explore',
|
||||
'layout',
|
||||
'login',
|
||||
'oauth',
|
||||
'pipeline',
|
||||
'plugin',
|
||||
'pluginTags',
|
||||
'pluginTrigger',
|
||||
'register',
|
||||
'runLog',
|
||||
'share',
|
||||
'time',
|
||||
'tools',
|
||||
'workflow',
|
||||
]
|
||||
|
||||
// Sort by length descending to match longer prefixes first
|
||||
// e.g., 'datasetDocuments' before 'dataset'
|
||||
export const NAMESPACES_BY_LENGTH = [...NAMESPACES].sort((a, b) => b.length - a.length)
|
||||
|
||||
/**
|
||||
* Extract namespace from a translation key
|
||||
* Returns null if no namespace prefix found or if already in namespace:key format
|
||||
* @param {string} key
|
||||
* @returns {{ ns: string, localKey: string } | null}
|
||||
*/
|
||||
export function extractNamespace(key) {
|
||||
// Skip if already in namespace:key format
|
||||
for (const ns of NAMESPACES_BY_LENGTH) {
|
||||
if (key.startsWith(`${ns}:`)) {
|
||||
return null
|
||||
}
|
||||
}
|
||||
// Check for legacy namespace.key format
|
||||
for (const ns of NAMESPACES_BY_LENGTH) {
|
||||
if (key.startsWith(`${ns}.`)) {
|
||||
return { ns, localKey: key.slice(ns.length + 1) }
|
||||
}
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove namespace prefix from a string value
|
||||
* Used for fixing variable declarations
|
||||
* @param {string} value
|
||||
* @returns {{ ns: string, newValue: string } | null}
|
||||
*/
|
||||
export function removeNamespacePrefix(value) {
|
||||
// Skip if already in namespace:key format
|
||||
for (const ns of NAMESPACES_BY_LENGTH) {
|
||||
if (value.startsWith(`${ns}:`)) {
|
||||
return null
|
||||
}
|
||||
}
|
||||
// Check for legacy namespace.key format
|
||||
for (const ns of NAMESPACES_BY_LENGTH) {
|
||||
if (value.startsWith(`${ns}.`)) {
|
||||
return { ns, newValue: value.slice(ns.length + 1) }
|
||||
}
|
||||
if (value === ns) {
|
||||
return { ns, newValue: '' }
|
||||
}
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
|
@ -1,374 +0,0 @@
|
|||
import { extractNamespace, removeNamespacePrefix } from '../namespaces.js'
|
||||
|
||||
/** @type {import('eslint').Rule.RuleModule} */
|
||||
export default {
|
||||
meta: {
|
||||
type: 'suggestion',
|
||||
docs: {
|
||||
description: 'Disallow legacy namespace prefix in i18n translation keys',
|
||||
},
|
||||
fixable: 'code',
|
||||
schema: [],
|
||||
messages: {
|
||||
legacyNamespacePrefix:
|
||||
'Translation key "{{key}}" should not include namespace prefix. Use t(\'{{localKey}}\') with useTranslation(\'{{ns}}\') instead.',
|
||||
legacyNamespacePrefixInVariable:
|
||||
'Variable "{{name}}" contains namespace prefix "{{ns}}". Remove the prefix and use useTranslation(\'{{ns}}\') instead.',
|
||||
},
|
||||
},
|
||||
create(context) {
|
||||
const sourceCode = context.sourceCode
|
||||
|
||||
// Track all t() calls to fix
|
||||
/** @type {Array<{ node: import('estree').CallExpression }>} */
|
||||
const tCallsToFix = []
|
||||
|
||||
// Track variables with namespace prefix
|
||||
/** @type {Map<string, { node: import('estree').VariableDeclarator, name: string, oldValue: string, newValue: string, ns: string }>} */
|
||||
const variablesToFix = new Map()
|
||||
|
||||
// Track all namespaces used in the file
|
||||
/** @type {Set<string>} */
|
||||
const namespacesUsed = new Set()
|
||||
|
||||
// Track variable values for template literal analysis
|
||||
/** @type {Map<string, string>} */
|
||||
const variableValues = new Map()
|
||||
|
||||
/**
|
||||
* Analyze a template literal and extract namespace info
|
||||
* @param {import('estree').TemplateLiteral} node
|
||||
*/
|
||||
function analyzeTemplateLiteral(node) {
|
||||
const quasis = node.quasis
|
||||
const expressions = node.expressions
|
||||
|
||||
const firstQuasi = quasis[0].value.raw
|
||||
|
||||
// Check if first quasi starts with namespace
|
||||
const extracted = extractNamespace(firstQuasi)
|
||||
if (extracted) {
|
||||
const fixedQuasis = [extracted.localKey, ...quasis.slice(1).map(q => q.value.raw)]
|
||||
return { ns: extracted.ns, canFix: true, fixedQuasis, variableToUpdate: null }
|
||||
}
|
||||
|
||||
// Check if first expression is a variable with namespace prefix
|
||||
if (expressions.length > 0 && firstQuasi === '') {
|
||||
const firstExpr = expressions[0]
|
||||
if (firstExpr.type === 'Identifier') {
|
||||
const varValue = variableValues.get(firstExpr.name)
|
||||
if (varValue) {
|
||||
const extracted = removeNamespacePrefix(varValue)
|
||||
if (extracted) {
|
||||
return {
|
||||
ns: extracted.ns,
|
||||
canFix: true,
|
||||
fixedQuasis: null,
|
||||
variableToUpdate: {
|
||||
name: firstExpr.name,
|
||||
newValue: extracted.newValue,
|
||||
ns: extracted.ns,
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return { ns: null, canFix: false, fixedQuasis: null, variableToUpdate: null }
|
||||
}
|
||||
|
||||
/**
|
||||
* Build a fixed template literal string
|
||||
* @param {string[]} quasis
|
||||
* @param {import('estree').Expression[]} expressions
|
||||
*/
|
||||
function buildTemplateLiteral(quasis, expressions) {
|
||||
let result = '`'
|
||||
for (let i = 0; i < quasis.length; i++) {
|
||||
result += quasis[i]
|
||||
if (i < expressions.length) {
|
||||
result += `\${${sourceCode.getText(expressions[i])}}`
|
||||
}
|
||||
}
|
||||
result += '`'
|
||||
return result
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if a t() call already has ns in its second argument
|
||||
* @param {import('estree').CallExpression} node
|
||||
* @returns {boolean}
|
||||
*/
|
||||
function hasNsArgument(node) {
|
||||
if (node.arguments.length < 2)
|
||||
return false
|
||||
const secondArg = node.arguments[1]
|
||||
if (secondArg.type !== 'ObjectExpression')
|
||||
return false
|
||||
return secondArg.properties.some(
|
||||
prop => prop.type === 'Property'
|
||||
&& prop.key.type === 'Identifier'
|
||||
&& prop.key.name === 'ns',
|
||||
)
|
||||
}
|
||||
|
||||
return {
|
||||
// Track variable declarations
|
||||
VariableDeclarator(node) {
|
||||
if (
|
||||
node.id.type === 'Identifier'
|
||||
&& node.init
|
||||
&& node.init.type === 'Literal'
|
||||
&& typeof node.init.value === 'string'
|
||||
) {
|
||||
variableValues.set(node.id.name, node.init.value)
|
||||
|
||||
const extracted = removeNamespacePrefix(node.init.value)
|
||||
if (extracted) {
|
||||
variablesToFix.set(node.id.name, {
|
||||
node,
|
||||
name: node.id.name,
|
||||
oldValue: node.init.value,
|
||||
newValue: extracted.newValue,
|
||||
ns: extracted.ns,
|
||||
})
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
CallExpression(node) {
|
||||
// Check for t() calls - both direct t() and i18n.t()
|
||||
const isTCall = (
|
||||
node.callee.type === 'Identifier'
|
||||
&& node.callee.name === 't'
|
||||
) || (
|
||||
node.callee.type === 'MemberExpression'
|
||||
&& node.callee.property.type === 'Identifier'
|
||||
&& node.callee.property.name === 't'
|
||||
)
|
||||
|
||||
if (isTCall && node.arguments.length > 0) {
|
||||
const firstArg = node.arguments[0]
|
||||
|
||||
// Skip if already has ns argument
|
||||
if (hasNsArgument(node))
|
||||
return
|
||||
|
||||
// Case 1: Static string literal
|
||||
if (firstArg.type === 'Literal' && typeof firstArg.value === 'string') {
|
||||
const extracted = extractNamespace(firstArg.value)
|
||||
if (extracted) {
|
||||
namespacesUsed.add(extracted.ns)
|
||||
tCallsToFix.push({ node })
|
||||
|
||||
context.report({
|
||||
node: firstArg,
|
||||
messageId: 'legacyNamespacePrefix',
|
||||
data: {
|
||||
key: firstArg.value,
|
||||
localKey: extracted.localKey,
|
||||
ns: extracted.ns,
|
||||
},
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// Case 2: Template literal
|
||||
if (firstArg.type === 'TemplateLiteral') {
|
||||
const analysis = analyzeTemplateLiteral(firstArg)
|
||||
if (analysis.ns) {
|
||||
namespacesUsed.add(analysis.ns)
|
||||
tCallsToFix.push({ node })
|
||||
|
||||
if (!analysis.variableToUpdate) {
|
||||
const firstQuasi = firstArg.quasis[0].value.raw
|
||||
const extracted = extractNamespace(firstQuasi)
|
||||
if (extracted) {
|
||||
context.report({
|
||||
node: firstArg,
|
||||
messageId: 'legacyNamespacePrefix',
|
||||
data: {
|
||||
key: `${firstQuasi}...`,
|
||||
localKey: `${extracted.localKey}...`,
|
||||
ns: extracted.ns,
|
||||
},
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Case 3: Conditional expression
|
||||
if (firstArg.type === 'ConditionalExpression') {
|
||||
const consequent = firstArg.consequent
|
||||
const alternate = firstArg.alternate
|
||||
let hasNs = false
|
||||
|
||||
if (consequent.type === 'Literal' && typeof consequent.value === 'string') {
|
||||
const extracted = extractNamespace(consequent.value)
|
||||
if (extracted) {
|
||||
hasNs = true
|
||||
namespacesUsed.add(extracted.ns)
|
||||
}
|
||||
}
|
||||
|
||||
if (alternate.type === 'Literal' && typeof alternate.value === 'string') {
|
||||
const extracted = extractNamespace(alternate.value)
|
||||
if (extracted) {
|
||||
hasNs = true
|
||||
namespacesUsed.add(extracted.ns)
|
||||
}
|
||||
}
|
||||
|
||||
if (hasNs) {
|
||||
tCallsToFix.push({ node })
|
||||
|
||||
context.report({
|
||||
node: firstArg,
|
||||
messageId: 'legacyNamespacePrefix',
|
||||
data: {
|
||||
key: '(conditional)',
|
||||
localKey: '...',
|
||||
ns: '...',
|
||||
},
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
'Program:exit': function (program) {
|
||||
if (namespacesUsed.size === 0)
|
||||
return
|
||||
|
||||
// Report variables with namespace prefix (once per variable)
|
||||
for (const [, varInfo] of variablesToFix) {
|
||||
if (namespacesUsed.has(varInfo.ns)) {
|
||||
context.report({
|
||||
node: varInfo.node,
|
||||
messageId: 'legacyNamespacePrefixInVariable',
|
||||
data: {
|
||||
name: varInfo.name,
|
||||
ns: varInfo.ns,
|
||||
},
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// Report on program with fix
|
||||
const sortedNamespaces = Array.from(namespacesUsed).sort()
|
||||
|
||||
context.report({
|
||||
node: program,
|
||||
messageId: 'legacyNamespacePrefix',
|
||||
data: {
|
||||
key: '(file)',
|
||||
localKey: '...',
|
||||
ns: sortedNamespaces.join(', '),
|
||||
},
|
||||
fix(fixer) {
|
||||
/** @type {import('eslint').Rule.Fix[]} */
|
||||
const fixes = []
|
||||
|
||||
// Fix variable declarations - remove namespace prefix
|
||||
for (const [, varInfo] of variablesToFix) {
|
||||
if (namespacesUsed.has(varInfo.ns) && varInfo.node.init) {
|
||||
fixes.push(fixer.replaceText(varInfo.node.init, `'${varInfo.newValue}'`))
|
||||
}
|
||||
}
|
||||
|
||||
// Fix t() calls - use { ns: 'xxx' } as second argument
|
||||
for (const { node } of tCallsToFix) {
|
||||
const firstArg = node.arguments[0]
|
||||
const secondArg = node.arguments[1]
|
||||
const hasSecondArg = node.arguments.length >= 2
|
||||
|
||||
/**
|
||||
* Add ns to existing object or create new object
|
||||
* @param {string} ns
|
||||
*/
|
||||
const addNsToArgs = (ns) => {
|
||||
if (hasSecondArg && secondArg.type === 'ObjectExpression') {
|
||||
// Add ns property to existing object
|
||||
if (secondArg.properties.length === 0) {
|
||||
// Empty object: {} -> { ns: 'xxx' }
|
||||
fixes.push(fixer.replaceText(secondArg, `{ ns: '${ns}' }`))
|
||||
}
|
||||
else {
|
||||
// Non-empty object: { foo } -> { ns: 'xxx', foo }
|
||||
const firstProp = secondArg.properties[0]
|
||||
fixes.push(fixer.insertTextBefore(firstProp, `ns: '${ns}', `))
|
||||
}
|
||||
}
|
||||
else if (!hasSecondArg) {
|
||||
// No second argument, add new object
|
||||
fixes.push(fixer.insertTextAfter(firstArg, `, { ns: '${ns}' }`))
|
||||
}
|
||||
// If second arg exists but is not an object, skip (can't safely add ns)
|
||||
}
|
||||
|
||||
if (firstArg.type === 'Literal' && typeof firstArg.value === 'string') {
|
||||
const extracted = extractNamespace(firstArg.value)
|
||||
if (extracted) {
|
||||
// Replace key
|
||||
fixes.push(fixer.replaceText(firstArg, `'${extracted.localKey}'`))
|
||||
// Add ns
|
||||
addNsToArgs(extracted.ns)
|
||||
}
|
||||
}
|
||||
else if (firstArg.type === 'TemplateLiteral') {
|
||||
const analysis = analyzeTemplateLiteral(firstArg)
|
||||
if (analysis.canFix && analysis.fixedQuasis) {
|
||||
// For template literals with namespace prefix directly in template
|
||||
const newTemplate = buildTemplateLiteral(analysis.fixedQuasis, firstArg.expressions)
|
||||
fixes.push(fixer.replaceText(firstArg, newTemplate))
|
||||
addNsToArgs(analysis.ns)
|
||||
}
|
||||
else if (analysis.canFix && analysis.variableToUpdate) {
|
||||
// Variable's namespace prefix is being removed
|
||||
const quasis = firstArg.quasis.map(q => q.value.raw)
|
||||
// If variable becomes empty and next quasi starts with '.', remove the dot
|
||||
if (analysis.variableToUpdate.newValue === '' && quasis.length > 1 && quasis[1].startsWith('.')) {
|
||||
quasis[1] = quasis[1].slice(1)
|
||||
}
|
||||
const newTemplate = buildTemplateLiteral(quasis, firstArg.expressions)
|
||||
fixes.push(fixer.replaceText(firstArg, newTemplate))
|
||||
addNsToArgs(analysis.ns)
|
||||
}
|
||||
}
|
||||
else if (firstArg.type === 'ConditionalExpression') {
|
||||
const consequent = firstArg.consequent
|
||||
const alternate = firstArg.alternate
|
||||
let ns = null
|
||||
|
||||
if (consequent.type === 'Literal' && typeof consequent.value === 'string') {
|
||||
const extracted = extractNamespace(consequent.value)
|
||||
if (extracted) {
|
||||
ns = extracted.ns
|
||||
fixes.push(fixer.replaceText(consequent, `'${extracted.localKey}'`))
|
||||
}
|
||||
}
|
||||
|
||||
if (alternate.type === 'Literal' && typeof alternate.value === 'string') {
|
||||
const extracted = extractNamespace(alternate.value)
|
||||
if (extracted) {
|
||||
ns = ns || extracted.ns
|
||||
fixes.push(fixer.replaceText(alternate, `'${extracted.localKey}'`))
|
||||
}
|
||||
}
|
||||
|
||||
// Add ns argument
|
||||
if (ns) {
|
||||
addNsToArgs(ns)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return fixes
|
||||
},
|
||||
})
|
||||
},
|
||||
}
|
||||
},
|
||||
}
|
||||
|
|
@ -3,7 +3,6 @@ import antfu from '@antfu/eslint-config'
|
|||
import sonar from 'eslint-plugin-sonarjs'
|
||||
import storybook from 'eslint-plugin-storybook'
|
||||
import tailwind from 'eslint-plugin-tailwindcss'
|
||||
// import difyI18n from './eslint-rules/index.js'
|
||||
|
||||
export default antfu(
|
||||
{
|
||||
|
|
@ -156,15 +155,4 @@ export default antfu(
|
|||
'tailwindcss/migration-from-tailwind-2': 'warn',
|
||||
},
|
||||
},
|
||||
// dify i18n namespace migration
|
||||
// {
|
||||
// files: ['**/*.ts', '**/*.tsx'],
|
||||
// ignores: ['eslint-rules/**', 'i18n/**', 'i18n-config/**'],
|
||||
// plugins: {
|
||||
// 'dify-i18n': difyI18n,
|
||||
// },
|
||||
// rules: {
|
||||
// 'dify-i18n/no-legacy-namespace-prefix': 'error',
|
||||
// },
|
||||
// },
|
||||
)
|
||||
|
|
|
|||
Loading…
Reference in New Issue