Repository: intlify/eslint-plugin-vue-i18n
Pull request #510 (2024-05-10 08:30:19)

feat: add `no-deprecated-tc` rule
## background

In vue-i18n v10, `tc` and `$tc` are going to deprecated.
vue-i18n has already published docs for breaking changes.
https://vue-i18n.intlify.dev/guide/migration/breaking10.html#deprecate-tc-and-tc-for-legacy-api-mode

This rule supports migration.

changeset-bot[bot]: ###  ⚠️  No Changeset found

Latest commit: 0c4126247ecc8689e6ff8fd082563df4033778c5

Merging this PR will not cause a version bump for any packages. If these changes should not result in a new version, you're good to go. **If these changes should result in a version bump, you need to add a changeset.**

<details><summary>This PR includes no changesets</summary>

  When changesets are added to this PR, you'll see the packages that this PR includes changesets for and the associated semver types

</details>

[Click here to learn what changesets are, and how to add one](https://github.com/changesets/changesets/blob/main/docs/adding-a-changeset.md).

[Click here if you're a maintainer who wants to add a changeset to this PR](https://github.com/intlify/eslint-plugin-vue-i18n/new/feat/no-deprecated-tc?filename=.changeset/new-apples-ring.md&value=---%0A%22%40intlify%2Feslint-plugin-vue-i18n%22%3A%20patch%0A---%0A%0Afeat%3A%20add%20%60no-deprecated-tc%60%20rule%0A)


cloudflare-pages[bot]: ## Deploying eslint-plugin-vue-i18n with &nbsp;<a href="https://pages.dev"><img alt="Cloudflare Pages" src="https://user-images.githubusercontent.com/23264/106598434-9e719e00-654f-11eb-9e59-6167043cfa01.png" width="16"></a> &nbsp;Cloudflare Pages

<table><tr><td><strong>Latest commit:</strong> </td><td>
<code>55041e6</code>
</td></tr>
<tr><td><strong>Status:</strong></td><td>⚡️&nbsp; Build in progress...</td></tr>
</table>

[View logs](https://dash.cloudflare.com/?to=/:account/pages/view/eslint-plugin-vue-i18n/c769531c-6a39-4424-823e-e6837e03b935)


Patch:
diff --git a/.changeset/new-apples-ring.md b/.changeset/new-apples-ring.md
new file mode 100644
index 0000000..5e888be
--- /dev/null
+++ b/.changeset/new-apples-ring.md
@@ -0,0 +1,5 @@
+---
+"@intlify/eslint-plugin-vue-i18n": patch
+---
+
+feat: add `no-deprecated-tc` rule
diff --git a/docs/rules/index.md b/docs/rules/index.md
index 80651d6..eb45a48 100644
--- a/docs/rules/index.md
+++ b/docs/rules/index.md
@@ -25,6 +25,7 @@
 | Rule ID | Description |    |
 |:--------|:------------|:---|
 | [@intlify/vue-i18n/<wbr>key-format-style](./key-format-style.html) | enforce specific casing for localization keys |  |
+| [@intlify/vue-i18n/<wbr>no-deprecated-tc](./no-deprecated-tc.html) | disallow using deprecated `tc` or `$tc` (Deprecated in Vue I18n 10.0.0, removed fully in Vue I18n 11.0.0) |  |
 | [@intlify/vue-i18n/<wbr>no-duplicate-keys-in-locale](./no-duplicate-keys-in-locale.html) | disallow duplicate localization keys within the same locale |  |
 | [@intlify/vue-i18n/<wbr>no-dynamic-keys](./no-dynamic-keys.html) | disallow localization dynamic keys at localization methods |  |
 | [@intlify/vue-i18n/<wbr>no-missing-keys-in-other-locales](./no-missing-keys-in-other-locales.html) | disallow missing locale message keys in other locales |  |
diff --git a/docs/rules/no-deprecated-tc.md b/docs/rules/no-deprecated-tc.md
new file mode 100644
index 0000000..a5bc232
--- /dev/null
+++ b/docs/rules/no-deprecated-tc.md
@@ -0,0 +1,64 @@
+---
+title: '@intlify/vue-i18n/no-deprecated-tc'
+description: disallow using deprecated `tc` or `$tc` (Deprecated in Vue I18n 10.0.0, removed fully in Vue I18n 11.0.0)
+since: v3.0.0
+---
+
+# @intlify/vue-i18n/no-deprecated-tc
+
+> disallow using deprecated `tc` or `$tc` (Deprecated in Vue I18n 10.0.0, removed fully in Vue I18n 11.0.0)
+
+If you are migrating from Vue I18n v9 to v10, `tc` or `$tc` should be replaced with `t` or `$t`.
+
+## :book: Rule Details
+
+This rule reports use of deprecated `tc` or `$tc` (Deprecated in Vue I18n 10.0.0, removed fully in Vue I18n 11.0.0)
+
+:-1: Examples of **incorrect** code for this rule:
+
+<eslint-code-block>
+
+<!-- eslint-skip -->
+
+```vue
+<script>
+/* eslint @intlify/vue-i18n/no-deprecated-tc: 'error' */
+</script>
+<template>
+  <!-- ✗ BAD -->
+  <p>{{ $tc('banana') }}</p>
+</template>
+```
+
+</eslint-code-block>
+
+:+1: Examples of **correct** code for this rule:
+
+<eslint-code-block>
+
+<!-- eslint-skip -->
+
+```vue
+<script>
+/* eslint @intlify/vue-i18n/no-deprecated-tc: 'error' */
+</script>
+<template>
+  <!-- ✓ GOOD -->
+  <p>{{ $t('banana', 1) }}</p>
+</template>
+```
+
+</eslint-code-block>
+
+## :books: Further reading
+
+- [Vue I18n > Breaking Changes in v10 - Deprecate tc and $tc for Legacy API mode](https://vue-i18n.intlify.dev/guide/migration/breaking10.html#deprecate-tc-and-tc-for-legacy-api-mode)
+
+## :rocket: Version
+
+This rule was introduced in `@intlify/eslint-plugin-vue-i18n` v3.0.0
+
+## :mag: Implementation
+
+- [Rule source](https://github.com/intlify/eslint-plugin-vue-i18n/blob/master/lib/rules/no-deprecated-tc.ts)
+- [Test source](https://github.com/intlify/eslint-plugin-vue-i18n/tree/master/tests/lib/rules/no-deprecated-tc.ts)
diff --git a/lib/index.ts b/lib/index.ts
index dbd5a84..3bad70a 100644
--- a/lib/index.ts
+++ b/lib/index.ts
@@ -11,6 +11,7 @@ import noDeprecatedI18nComponent from './rules/no-deprecated-i18n-component'
 import noDeprecatedI18nPlaceAttr from './rules/no-deprecated-i18n-place-attr'
 import noDeprecatedI18nPlacesProp from './rules/no-deprecated-i18n-places-prop'
 import noDeprecatedModuloSyntax from './rules/no-deprecated-modulo-syntax'
+import noDeprecatedTc from './rules/no-deprecated-tc'
 import noDuplicateKeysInLocale from './rules/no-duplicate-keys-in-locale'
 import noDynamicKeys from './rules/no-dynamic-keys'
 import noHtmlMessages from './rules/no-html-messages'
@@ -43,6 +44,7 @@ export = {
     'no-deprecated-i18n-place-attr': noDeprecatedI18nPlaceAttr,
     'no-deprecated-i18n-places-prop': noDeprecatedI18nPlacesProp,
     'no-deprecated-modulo-syntax': noDeprecatedModuloSyntax,
+    'no-deprecated-tc': noDeprecatedTc,
     'no-duplicate-keys-in-locale': noDuplicateKeysInLocale,
     'no-dynamic-keys': noDynamicKeys,
     'no-html-messages': noHtmlMessages,
diff --git a/lib/rules/no-deprecated-tc.ts b/lib/rules/no-deprecated-tc.ts
new file mode 100644
index 0000000..63de6d5
--- /dev/null
+++ b/lib/rules/no-deprecated-tc.ts
@@ -0,0 +1,60 @@
+/**
+ * @author kazuya kawaguchi (a.k.a. kazupon)
+ */
+import { defineTemplateBodyVisitor } from '../utils/index'
+import { createRule } from '../utils/rule'
+
+import type { RuleContext, RuleListener } from '../types'
+import type { AST as VAST } from 'vue-eslint-parser'
+
+function checkCallExpression(
+  context: RuleContext,
+  node: VAST.ESLintCallExpression
+) {
+  const funcName =
+    (node.callee.type === 'MemberExpression' &&
+      node.callee.property.type === 'Identifier' &&
+      node.callee.property.name) ||
+    (node.callee.type === 'Identifier' && node.callee.name) ||
+    ''
+
+  if (/^(\$tc|tc)$/.test(funcName)) {
+    context.report({
+      node,
+      message: `'${funcName}' is used, but it is deprecated. Use 't' or '$t' instead.`
+    })
+    return
+  }
+}
+
+function create(context: RuleContext): RuleListener {
+  return defineTemplateBodyVisitor(
+    context,
+    {
+      CallExpression(node: VAST.ESLintCallExpression) {
+        checkCallExpression(context, node)
+      }
+    },
+    {
+      CallExpression(node: VAST.ESLintCallExpression) {
+        checkCallExpression(context, node)
+      }
+    }
+  )
+}
+
+export = createRule({
+  meta: {
+    type: 'suggestion',
+    docs: {
+      description:
+        'disallow using deprecated `tc` or `$tc` (Deprecated in Vue I18n 10.0.0, removed fully in Vue I18n 11.0.0)',
+      category: 'Best Practices',
+      url: 'https://eslint-plugin-vue-i18n.intlify.dev/rules/no-deprecated-tc.html',
+      recommended: false
+    },
+    fixable: null,
+    schema: []
+  },
+  create
+})


Test patch:
diff --git a/tests/lib/rules/no-deprecated-tc.ts b/tests/lib/rules/no-deprecated-tc.ts
new file mode 100644
index 0000000..22b0cb1
--- /dev/null
+++ b/tests/lib/rules/no-deprecated-tc.ts
@@ -0,0 +1,38 @@
+/**
+ * @author kazuya kawaguchi (a.k.a. kazupon)
+ */
+import { RuleTester } from '../eslint-compat'
+import rule from '../../../lib/rules/no-deprecated-tc'
+import * as vueParser from 'vue-eslint-parser'
+
+const tester = new RuleTester({
+  languageOptions: { parser: vueParser, ecmaVersion: 2015 }
+})
+
+tester.run('no-deprecated-tc', rule as never, {
+  valid: [],
+  invalid: [
+    {
+      // tc key
+      code: `tc('banana')`,
+      errors: [`'tc' is used, but it is deprecated. Use 't' or '$t' instead.`]
+    },
+    {
+      // $tc key
+      code: `$tc('banana')`,
+      errors: [`'$tc' is used, but it is deprecated. Use 't' or '$t' instead.`]
+    },
+    {
+      // via i18n instance
+      code: `i18n.tc('banana')`,
+      errors: [`'tc' is used, but it is deprecated. Use 't' or '$t' instead.`]
+    },
+    {
+      // using mustaches in template block
+      code: `<template>
+      <p>{{ $tc('banana') }}</p>
+    </template>`,
+      errors: [`'$tc' is used, but it is deprecated. Use 't' or '$t' instead.`]
+    }
+  ]
+})