Skip to content

Commit 4eee05f

Browse files
committed
Add @intlify/vue-i18n/no-deprecated-i18n-place-attr rule
1 parent ff8cbe1 commit 4eee05f

8 files changed

+356
-17
lines changed

docs/rules/README.md

+20-16
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-place-attr](./no-deprecated-i18n-place-attr.html) | disallow using deprecated `place` attribute (Removed in Vue I18n 9.0.0+) | |
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: |
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,95 @@
1+
---
2+
title: '@intlify/vue-i18n/no-deprecated-i18n-place-attr'
3+
description: disallow using deprecated `place` attribute (Removed in Vue I18n 9.0.0+)
4+
---
5+
6+
# @intlify/vue-i18n/no-deprecated-i18n-place-attr
7+
8+
> disallow using deprecated `place` attribute (Removed in Vue I18n 9.0.0+)
9+
10+
If you are migrating from Vue I18n v8 to v9, the `place` attribute should be replaced with the `v-slot`.
11+
12+
## :book: Rule Details
13+
14+
This rule reports use of deprecated `place` attribute (Removed in Vue I18n 9.0.0+).
15+
16+
:-1: Examples of **incorrect** code for this rule:
17+
18+
<eslint-code-block>
19+
20+
<!-- eslint-skip -->
21+
22+
```vue
23+
<script>
24+
/* eslint @intlify/vue-i18n/no-deprecated-i18n-place-attr: 'error' */
25+
</script>
26+
<template>
27+
<div class="app">
28+
<i18n path="info" tag="p">
29+
<!-- ✗ BAD -->
30+
<span place="limit">{{ changeLimit }}</span>
31+
<!-- ✗ BAD -->
32+
<a place="action" :href="changeUrl">{{ $t('change') }}</a>
33+
</i18n>
34+
35+
<!-- Also check the <i18n-t> component to prevent mistakes. -->
36+
<i18n-t path="info" tag="p">
37+
<!-- ✗ BAD -->
38+
<span place="limit">{{ changeLimit }}</span>
39+
<!-- ✗ BAD -->
40+
<a place="action" :href="changeUrl">{{ $t('change') }}</a>
41+
</i18n-t>
42+
</div>
43+
</template>
44+
```
45+
46+
</eslint-code-block>
47+
48+
:+1: Examples of **correct** code for this rule:
49+
50+
<eslint-code-block>
51+
52+
<!-- eslint-skip -->
53+
54+
```vue
55+
<script>
56+
/* eslint @intlify/vue-i18n/no-deprecated-i18n-place-attr: 'error' */
57+
</script>
58+
<template>
59+
<div class="app">
60+
<i18n path="info" tag="p">
61+
<!-- ✓ GOOD -->
62+
<template v-slot:limit>
63+
<span>{{ changeLimit }}</span>
64+
</template>
65+
<!-- ✓ GOOD -->
66+
<template v-slot:action>
67+
<a :href="changeUrl">{{ $t('change') }}</a>
68+
</template>
69+
</i18n>
70+
71+
<i18n-t keypath="info" tag="p">
72+
<!-- ✓ GOOD -->
73+
<template #limit>
74+
<span>{{ changeLimit }}</span>
75+
</template>
76+
<!-- ✓ GOOD -->
77+
<template #action>
78+
<a :href="changeUrl">{{ $t('change') }}</a>
79+
</template>
80+
</i18n-t>
81+
</div>
82+
</template>
83+
```
84+
85+
</eslint-code-block>
86+
87+
## :books: Further reading
88+
89+
- [Vue I18n > Breaking Changes - Remove place syntax with `place` attr and `places` prop](https://vue-i18n.intlify.dev/guide/migration/breaking.html#remove-place-syntax-with-place-attr-and-places-prop)
90+
- [Vue I18n (v8) > Component interpolation - Places syntax usage](https://kazupon.github.io/vue-i18n/guide/interpolation.html#places-syntax-usage)
91+
92+
## :mag: Implementation
93+
94+
- [Rule source](https://github.com/intlify/eslint-plugin-vue-i18n/blob/master/lib/rules/no-deprecated-i18n-place-attr.ts)
95+
- [Test source](https://github.com/intlify/eslint-plugin-vue-i18n/tree/master/tests/lib/rules/no-deprecated-i18n-place-attr.ts)

lib/rules.ts

+2
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 noDeprecatedI18nPlaceAttr from './rules/no-deprecated-i18n-place-attr'
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-place-attr': noDeprecatedI18nPlaceAttr,
1618
'no-duplicate-keys-in-locale': noDuplicateKeysInLocale,
1719
'no-dynamic-keys': noDynamicKeys,
1820
'no-html-messages': noHtmlMessages,
+51
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
/**
2+
* @author Yosuke Ota
3+
*/
4+
import {
5+
defineTemplateBodyVisitor,
6+
getAttribute,
7+
getDirective
8+
} from '../utils/index'
9+
import type { RuleContext, RuleListener } from '../types'
10+
import type { AST as VAST } from 'vue-eslint-parser'
11+
12+
function create(context: RuleContext): RuleListener {
13+
return defineTemplateBodyVisitor(context, {
14+
VElement(node: VAST.VElement) {
15+
if (node.name !== 'i18n' && node.name !== 'i18n-t') {
16+
return
17+
}
18+
for (const child of node.children) {
19+
if (child.type !== 'VElement') {
20+
continue
21+
}
22+
const placeAttr =
23+
getAttribute(child, 'place') || getDirective(child, 'bind', 'place')
24+
if (placeAttr) {
25+
context.report({
26+
node: placeAttr.key,
27+
messageId: 'deprecated'
28+
})
29+
}
30+
}
31+
}
32+
})
33+
}
34+
35+
export = {
36+
meta: {
37+
type: 'problem',
38+
docs: {
39+
description:
40+
'disallow using deprecated `place` attribute (Removed in Vue I18n 9.0.0+)',
41+
category: 'Recommended',
42+
recommended: false
43+
},
44+
fixable: null,
45+
schema: [],
46+
messages: {
47+
deprecated: 'Deprecated `place` attribute was found. Use v-slot instead.'
48+
}
49+
},
50+
create
51+
}

lib/utils/index.ts

+48
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,54 @@ export function defineTemplateBodyVisitor(
5757
)
5858
}
5959

60+
/**
61+
* Get the attribute which has the given name.
62+
* @param {VElement} node The start tag node to check.
63+
* @param {string} name The attribute name to check.
64+
* @param {string} [value] The attribute value to check.
65+
* @returns {VAttribute | null} The found attribute.
66+
*/
67+
export function getAttribute(
68+
node: VAST.VElement,
69+
name: string
70+
): VAST.VAttribute | null {
71+
return (
72+
node.startTag.attributes
73+
.map(node => (!node.directive ? node : null))
74+
.find(node => {
75+
return node && node.key.name === name
76+
}) || null
77+
)
78+
}
79+
80+
/**
81+
* Get the directive which has the given name.
82+
* @param {VElement} node The start tag node to check.
83+
* @param {string} name The directive name to check.
84+
* @param {string} [argument] The directive argument to check.
85+
* @returns {VDirective | null} The found directive.
86+
*/
87+
export function getDirective(
88+
node: VAST.VElement,
89+
name: string,
90+
argument: string
91+
): VAST.VDirective | null {
92+
return (
93+
node.startTag.attributes
94+
.map(node => (node.directive ? node : null))
95+
.find(node => {
96+
return (
97+
node &&
98+
node.key.name.name === name &&
99+
(argument === undefined ||
100+
(node.key.argument &&
101+
node.key.argument.type === 'VIdentifier' &&
102+
node.key.argument.name) === argument)
103+
)
104+
}) || null
105+
)
106+
}
107+
60108
function loadLocaleMessages(
61109
localeFilesList: LocaleFiles[],
62110
cwd: string

scripts/update-docs-index.ts

+1
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

+1-1
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)