Skip to content

Commit 59fc325

Browse files
authored
Add no-deprecated-i18n-component rule (#164)
* Add @intlify/vue-i18n/no-deprecated-i18n-component rule * fix docs
1 parent ff8cbe1 commit 59fc325

File tree

7 files changed

+439
-17
lines changed

7 files changed

+439
-17
lines changed

docs/rules/README.md

Lines changed: 20 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -5,26 +5,30 @@
55

66
## Recommended
77

8-
| Rule ID | Description | |
9-
| :------------------------------------------------------------------------- | :------------------------------------------------------------------- | :----- |
10-
| [@intlify/vue-i18n/<wbr>no-html-messages](./no-html-messages.html) | disallow use HTML localization messages | :star: |
11-
| [@intlify/vue-i18n/<wbr>no-missing-keys](./no-missing-keys.html) | disallow missing locale message key at localization methods | :star: |
12-
| [@intlify/vue-i18n/<wbr>no-raw-text](./no-raw-text.html) | disallow to string literal in template or JSX | :star: |
13-
| [@intlify/vue-i18n/<wbr>no-v-html](./no-v-html.html) | disallow use of localization methods on v-html to prevent XSS attack | :star: |
14-
| [@intlify/vue-i18n/<wbr>valid-message-syntax](./valid-message-syntax.html) | disallow invalid message syntax | |
8+
<!--prettier-ignore-->
9+
| Rule ID | Description | |
10+
|:--------|:------------|:---|
11+
| [@intlify/vue-i18n/<wbr>no-deprecated-i18n-component](./no-deprecated-i18n-component.html) | disallow using deprecated `<i18n>` components (in Vue I18n 9.0.0+) | :black_nib: |
12+
| [@intlify/vue-i18n/<wbr>no-html-messages](./no-html-messages.html) | disallow use HTML localization messages | :star: |
13+
| [@intlify/vue-i18n/<wbr>no-missing-keys](./no-missing-keys.html) | disallow missing locale message key at localization methods | :star: |
14+
| [@intlify/vue-i18n/<wbr>no-raw-text](./no-raw-text.html) | disallow to string literal in template or JSX | :star: |
15+
| [@intlify/vue-i18n/<wbr>no-v-html](./no-v-html.html) | disallow use of localization methods on v-html to prevent XSS attack | :star: |
16+
| [@intlify/vue-i18n/<wbr>valid-message-syntax](./valid-message-syntax.html) | disallow invalid message syntax | |
1517

1618
## Best Practices
1719

18-
| Rule ID | Description | |
19-
| :------------------------------------------------------------------------------------------------- | :---------------------------------------------------------- | :---------- |
20-
| [@intlify/vue-i18n/<wbr>key-format-style](./key-format-style.html) | enforce specific casing for localization keys | |
21-
| [@intlify/vue-i18n/<wbr>no-duplicate-keys-in-locale](./no-duplicate-keys-in-locale.html) | disallow duplicate localization keys within the same locale | |
22-
| [@intlify/vue-i18n/<wbr>no-dynamic-keys](./no-dynamic-keys.html) | disallow localization dynamic keys at localization methods | |
23-
| [@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 | |
24-
| [@intlify/vue-i18n/<wbr>no-unused-keys](./no-unused-keys.html) | disallow unused localization keys | :black_nib: |
20+
<!--prettier-ignore-->
21+
| Rule ID | Description | |
22+
|:--------|:------------|:---|
23+
| [@intlify/vue-i18n/<wbr>key-format-style](./key-format-style.html) | enforce specific casing for localization keys | |
24+
| [@intlify/vue-i18n/<wbr>no-duplicate-keys-in-locale](./no-duplicate-keys-in-locale.html) | disallow duplicate localization keys within the same locale | |
25+
| [@intlify/vue-i18n/<wbr>no-dynamic-keys](./no-dynamic-keys.html) | disallow localization dynamic keys at localization methods | |
26+
| [@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 | |
27+
| [@intlify/vue-i18n/<wbr>no-unused-keys](./no-unused-keys.html) | disallow unused localization keys | :black_nib: |
2528

2629
## Stylistic Issues
2730

28-
| Rule ID | Description | |
29-
| :----------------------------------------------------------------------------------------- | :----------------------------------------------- | :---------- |
31+
<!--prettier-ignore-->
32+
| Rule ID | Description | |
33+
|:--------|:------------|:---|
3034
| [@intlify/vue-i18n/<wbr>prefer-linked-key-with-paren](./prefer-linked-key-with-paren.html) | enforce linked key to be enclosed in parentheses | :black_nib: |
Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
---
2+
title: '@intlify/vue-i18n/no-deprecated-i18n-component'
3+
description: disallow using deprecated `<i18n>` components (in Vue I18n 9.0.0+)
4+
---
5+
6+
# @intlify/vue-i18n/no-deprecated-i18n-component
7+
8+
> disallow using deprecated `<i18n>` components (in Vue I18n 9.0.0+)
9+
10+
- :black_nib:️ The `--fix` option on the [command line](http://eslint.org/docs/user-guide/command-line-interface#fix) can automatically fix some of the problems reported by this rule.
11+
12+
If you are migrating from Vue I18n v8 to v9, the `<i18n>` component should be replaced with the `<i18n-t>` component.
13+
14+
## :book: Rule Details
15+
16+
This rule reports use of deprecated `<i18n>` components (in Vue I18n 9.0.0+).
17+
18+
:-1: Examples of **incorrect** code for this rule:
19+
20+
<eslint-code-block fix>
21+
22+
<!-- eslint-skip -->
23+
24+
```vue
25+
<script>
26+
/* eslint @intlify/vue-i18n/no-deprecated-i18n-component: 'error' */
27+
</script>
28+
<template>
29+
<div class="app">
30+
<!-- ✗ BAD -->
31+
<i18n path="message.greeting" />
32+
</div>
33+
</template>
34+
```
35+
36+
</eslint-code-block>
37+
38+
:+1: Examples of **correct** code for this rule:
39+
40+
<eslint-code-block fix>
41+
42+
<!-- eslint-skip -->
43+
44+
```vue
45+
<script>
46+
/* eslint @intlify/vue-i18n/no-deprecated-i18n-component: 'error' */
47+
</script>
48+
<template>
49+
<div class="app">
50+
<!-- ✓ GOOD -->
51+
<i18n-t keypath="message.greeting" />
52+
</div>
53+
</template>
54+
```
55+
56+
</eslint-code-block>
57+
58+
## :books: Further reading
59+
60+
- [Vue I18n > Breaking Changes - Rename to `i18n-t` from `i18n`](https://vue-i18n.intlify.dev/guide/migration/breaking.html#rename-to-i18n-tfrom-i18n)
61+
62+
## :mag: Implementation
63+
64+
- [Rule source](https://github.com/intlify/eslint-plugin-vue-i18n/blob/master/lib/rules/no-deprecated-i18n-component.ts)
65+
- [Test source](https://github.com/intlify/eslint-plugin-vue-i18n/tree/master/tests/lib/rules/no-deprecated-i18n-component.ts)

lib/rules.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
/** DON'T EDIT THIS FILE; was created by scripts. */
22
import keyFormatStyle from './rules/key-format-style'
3+
import noDeprecatedI18nComponent from './rules/no-deprecated-i18n-component'
34
import noDuplicateKeysInLocale from './rules/no-duplicate-keys-in-locale'
45
import noDynamicKeys from './rules/no-dynamic-keys'
56
import noHtmlMessages from './rules/no-html-messages'
@@ -13,6 +14,7 @@ import validMessageSyntax from './rules/valid-message-syntax'
1314

1415
export = {
1516
'key-format-style': keyFormatStyle,
17+
'no-deprecated-i18n-component': noDeprecatedI18nComponent,
1618
'no-duplicate-keys-in-locale': noDuplicateKeysInLocale,
1719
'no-dynamic-keys': noDynamicKeys,
1820
'no-html-messages': noHtmlMessages,
Lines changed: 101 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,101 @@
1+
/**
2+
* @author Yosuke Ota
3+
*/
4+
import { defineTemplateBodyVisitor } from '../utils/index'
5+
import type { RuleContext, RuleListener } from '../types'
6+
import type { AST as VAST } from 'vue-eslint-parser'
7+
8+
function create(context: RuleContext): RuleListener {
9+
return defineTemplateBodyVisitor(context, {
10+
VElement(node: VAST.VElement) {
11+
if (node.name !== 'i18n') {
12+
return
13+
}
14+
const tokenStore = context.parserServices.getTemplateBodyTokenStore()
15+
const tagNameToken = tokenStore.getFirstToken(node.startTag)
16+
context.report({
17+
node: tagNameToken,
18+
messageId: 'deprecated',
19+
*fix(fixer) {
20+
yield fixer.replaceText(tagNameToken, '<i18n-t')
21+
22+
let hasTag = false
23+
for (const attr of node.startTag.attributes) {
24+
if (attr.directive) {
25+
if (attr.key.name.name !== 'bind') {
26+
continue
27+
}
28+
if (
29+
!attr.key.argument ||
30+
attr.key.argument.type !== 'VIdentifier'
31+
) {
32+
continue
33+
}
34+
if (attr.key.argument.name === 'path') {
35+
yield fixer.replaceText(attr.key.argument, 'keypath')
36+
} else if (attr.key.argument.name === 'tag') {
37+
hasTag = true
38+
if (
39+
attr.value &&
40+
attr.value.expression &&
41+
attr.value.expression.type === 'Literal' &&
42+
typeof attr.value.expression.value === 'boolean'
43+
) {
44+
if (attr.value.expression.value) {
45+
// :tag="true"
46+
yield fixer.replaceText(attr, 'tag="span"')
47+
} else {
48+
yield fixer.remove(attr)
49+
}
50+
}
51+
}
52+
} else {
53+
if (attr.key.name === 'path') {
54+
yield fixer.replaceText(attr.key, 'keypath')
55+
} else if (attr.key.name === 'tag') {
56+
hasTag = true
57+
}
58+
}
59+
}
60+
if (!hasTag) {
61+
// Add a default `tag` for the <i18n> component.
62+
yield fixer.insertTextAfter(
63+
(node.startTag.attributes.length > 0 &&
64+
node.startTag.attributes[
65+
node.startTag.attributes.length - 1
66+
]) ||
67+
tagNameToken,
68+
' tag="span"'
69+
)
70+
}
71+
72+
if (node.endTag) {
73+
yield fixer.replaceText(
74+
tokenStore.getFirstToken(node.endTag),
75+
'</i18n-t'
76+
)
77+
}
78+
}
79+
})
80+
}
81+
})
82+
}
83+
84+
export = {
85+
meta: {
86+
type: 'problem',
87+
docs: {
88+
description:
89+
'disallow using deprecated `<i18n>` components (in Vue I18n 9.0.0+)',
90+
category: 'Recommended',
91+
recommended: false
92+
},
93+
fixable: 'code',
94+
schema: [],
95+
messages: {
96+
deprecated:
97+
'Deprecated <i18n> component was found. For VueI18n v9.0, use <i18n-t> component instead.'
98+
}
99+
},
100+
create
101+
}

scripts/update-docs-index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ function toCategorySection({
2626
}) {
2727
return `## ${category}
2828
29+
<!--prettier-ignore-->
2930
| Rule ID | Description | |
3031
|:--------|:------------|:---|
3132
${rules.map(toTableRow).join('\n')}

scripts/update-rule-docs.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ import { writeFileSync, readFileSync } from 'fs'
77
import { join } from 'path'
88
import type { RuleInfo } from './lib/rules'
99
import rules from './lib/rules'
10-
const PLACE_HOLDER = /^#[^\n]*\n+> .+\n+(?:- .+\n)*\n*/u
10+
const PLACE_HOLDER = /#[^\n]*\n+> .+\n+(?:- .+\n)*\n*/u
1111

1212
export function updateRuleDocs({
1313
nextVersion

0 commit comments

Comments
 (0)