Skip to content

Commit 062230f

Browse files
authored
Add @intlify/vue-i18n/no-deprecated-i18n-place-attr rule (#165)
1 parent 59fc325 commit 062230f

File tree

6 files changed

+335
-0
lines changed

6 files changed

+335
-0
lines changed

docs/rules/README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
| Rule ID | Description | |
1010
|:--------|:------------|:---|
1111
| [@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-deprecated-i18n-place-attr](./no-deprecated-i18n-place-attr.html) | disallow using deprecated `place` attribute (Removed in Vue I18n 9.0.0+) | |
1213
| [@intlify/vue-i18n/<wbr>no-html-messages](./no-html-messages.html) | disallow use HTML localization messages | :star: |
1314
| [@intlify/vue-i18n/<wbr>no-missing-keys](./no-missing-keys.html) | disallow missing locale message key at localization methods | :star: |
1415
| [@intlify/vue-i18n/<wbr>no-raw-text](./no-raw-text.html) | disallow to string literal in template or JSX | :star: |
Lines changed: 95 additions & 0 deletions
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

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
/** DON'T EDIT THIS FILE; was created by scripts. */
22
import keyFormatStyle from './rules/key-format-style'
33
import noDeprecatedI18nComponent from './rules/no-deprecated-i18n-component'
4+
import noDeprecatedI18nPlaceAttr from './rules/no-deprecated-i18n-place-attr'
45
import noDuplicateKeysInLocale from './rules/no-duplicate-keys-in-locale'
56
import noDynamicKeys from './rules/no-dynamic-keys'
67
import noHtmlMessages from './rules/no-html-messages'
@@ -15,6 +16,7 @@ import validMessageSyntax from './rules/valid-message-syntax'
1516
export = {
1617
'key-format-style': keyFormatStyle,
1718
'no-deprecated-i18n-component': noDeprecatedI18nComponent,
19+
'no-deprecated-i18n-place-attr': noDeprecatedI18nPlaceAttr,
1820
'no-duplicate-keys-in-locale': noDuplicateKeysInLocale,
1921
'no-dynamic-keys': noDynamicKeys,
2022
'no-html-messages': noHtmlMessages,
Lines changed: 51 additions & 0 deletions
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

Lines changed: 48 additions & 0 deletions
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
Lines changed: 138 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,138 @@
1+
/**
2+
* @author Yosuke Ota
3+
*/
4+
import { RuleTester } from 'eslint'
5+
import rule = require('../../../lib/rules/no-deprecated-i18n-place-attr')
6+
7+
const tester = new RuleTester({
8+
parser: require.resolve('vue-eslint-parser'),
9+
parserOptions: { ecmaVersion: 2015 }
10+
})
11+
12+
tester.run('no-deprecated-i18n-place-attr', rule as never, {
13+
valid: [
14+
{
15+
code: `
16+
<template>
17+
<div id="app">
18+
<!-- ... -->
19+
<i18n path="info" tag="p">
20+
<template v-slot:limit>
21+
<span>{{ changeLimit }}</span>
22+
</template>
23+
<template v-slot:action>
24+
<a :href="changeUrl">{{ $t('change') }}</a>
25+
</template>
26+
</i18n>
27+
<!-- ... -->
28+
</div>
29+
</template>
30+
`
31+
},
32+
{
33+
code: `
34+
<template>
35+
<div id="app">
36+
<!-- ... -->
37+
<i18n path="info" tag="p">
38+
<template #limit>
39+
<span>{{ changeLimit }}</span>
40+
</template>
41+
<template #action>
42+
<a :href="changeUrl">{{ $t('change') }}</a>
43+
</template>
44+
</i18n>
45+
<!-- ... -->
46+
</div>
47+
</template>
48+
`
49+
},
50+
{
51+
code: `
52+
<template>
53+
<div id="app">
54+
<!-- ... -->
55+
<unknown-component path="info" tag="p">
56+
<span place="limit">{{ changeLimit }}</span>
57+
<a place="action" :href="changeUrl">{{ $t('change') }}</a>
58+
</unknown-component>
59+
<!-- ... -->
60+
</div>
61+
</template>
62+
`
63+
}
64+
],
65+
invalid: [
66+
{
67+
code: `
68+
<template>
69+
<div id="app">
70+
<!-- ... -->
71+
<i18n path="info" tag="p">
72+
<span place="limit">{{ changeLimit }}</span>
73+
<a place="action" :href="changeUrl">{{ $t('change') }}</a>
74+
</i18n>
75+
<!-- ... -->
76+
</div>
77+
</template>
78+
`,
79+
errors: [
80+
{
81+
message:
82+
'Deprecated `place` attribute was found. Use v-slot instead.',
83+
line: 6,
84+
column: 19
85+
},
86+
{
87+
message:
88+
'Deprecated `place` attribute was found. Use v-slot instead.',
89+
line: 7,
90+
column: 16
91+
}
92+
]
93+
},
94+
95+
{
96+
code: `
97+
<template>
98+
<div id="app">
99+
<!-- ... -->
100+
<i18n-t path="info" tag="p">
101+
<span place="limit">{{ changeLimit }}</span>
102+
<a place="action" :href="changeUrl">{{ $t('change') }}</a>
103+
</i18n-t>
104+
<!-- ... -->
105+
</div>
106+
</template>
107+
`,
108+
errors: [
109+
{
110+
message:
111+
'Deprecated `place` attribute was found. Use v-slot instead.',
112+
line: 6,
113+
column: 19
114+
},
115+
{
116+
message:
117+
'Deprecated `place` attribute was found. Use v-slot instead.',
118+
line: 7,
119+
column: 16
120+
}
121+
]
122+
},
123+
{
124+
code: `
125+
<template>
126+
<div id="app">
127+
<!-- ... -->
128+
<i18n path="info" tag="p" :places="{ limit: refundLimit }">
129+
<a place="action" :href="refundUrl">{{ $t('refund') }}</a>
130+
</i18n>
131+
<!-- ... -->
132+
</div>
133+
</template>
134+
`,
135+
errors: ['Deprecated `place` attribute was found. Use v-slot instead.']
136+
}
137+
]
138+
})

0 commit comments

Comments
 (0)