Skip to content

Commit d365068

Browse files
committed
Supports Optional Chaining
1 parent a2614ef commit d365068

File tree

58 files changed

+1296
-364
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

58 files changed

+1296
-364
lines changed

Diff for: .eslintrc.js

+3-1
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,9 @@ module.exports = {
4949
{
5050
pattern: `https://eslint.vuejs.org/rules/{{name}}.html`
5151
}
52-
]
52+
],
53+
54+
'eslint-plugin/fixer-return': 'off'
5355
}
5456
}
5557
]

Diff for: docs/.vuepress/components/eslint-code-block.vue

+12-15
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ export default {
3232
},
3333
rules: {
3434
type: Object,
35-
default () {
35+
default() {
3636
return {}
3737
}
3838
},
@@ -46,7 +46,7 @@ export default {
4646
}
4747
},
4848
49-
data () {
49+
data() {
5050
return {
5151
linter: null,
5252
preprocess: processors['.vue'].preprocess,
@@ -59,7 +59,7 @@ export default {
5959
},
6060
6161
computed: {
62-
config () {
62+
config() {
6363
return {
6464
globals: {
6565
// ES2015 globals
@@ -89,7 +89,7 @@ export default {
8989
rules: this.rules,
9090
parser: 'vue-eslint-parser',
9191
parserOptions: {
92-
ecmaVersion: 2019,
92+
ecmaVersion: 2020,
9393
sourceType: 'module',
9494
ecmaFeatures: {
9595
jsx: true
@@ -98,33 +98,30 @@ export default {
9898
}
9999
},
100100
101-
code () {
101+
code() {
102102
return `${this.computeCodeFromSlot(this.$slots.default).trim()}\n`
103103
},
104104
105-
height () {
105+
height() {
106106
const lines = this.code.split('\n').length
107107
return `${Math.max(120, 19 * lines)}px`
108108
}
109109
},
110110
111111
methods: {
112-
computeCodeFromSlot (nodes) {
112+
computeCodeFromSlot(nodes) {
113113
if (!Array.isArray(nodes)) {
114114
return ''
115115
}
116-
return nodes.map(node =>
117-
node.text || this.computeCodeFromSlot(node.children)
118-
).join('')
116+
return nodes
117+
.map((node) => node.text || this.computeCodeFromSlot(node.children))
118+
.join('')
119119
}
120120
},
121121
122-
async mounted () {
122+
async mounted() {
123123
// Load linter.
124-
const [
125-
{ default: Linter },
126-
{ parseForESLint }
127-
] = await Promise.all([
124+
const [{ default: Linter }, { parseForESLint }] = await Promise.all([
128125
import('eslint4b/dist/linter'),
129126
import('espree').then(() => import('vue-eslint-parser'))
130127
])

Diff for: docs/rules/no-arrow-functions-in-watch.md

+1-1
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,7 @@ export default {
4040
/* ... */
4141
}
4242
],
43-
'e.f': function (val, oldVal) { /* ... */ }
43+
'e.f': function (val, oldVal) { /* ... */ },
4444
4545
/* ✗ BAD */
4646
foo: (val, oldVal) => {

Diff for: docs/rules/no-deprecated-events-api.md

+8
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,15 @@ export default {
2626
this.$emit('start')
2727
}
2828
}
29+
</script>
30+
```
31+
32+
</eslint-code-block>
2933

34+
<eslint-code-block :rules="{'vue/no-deprecated-events-api': ['error']}">
35+
36+
```vue
37+
<script>
3038
/* ✓ GOOD */
3139
import mitt from 'mitt'
3240
const emitter = mitt()

Diff for: docs/rules/no-template-target-blank.md

+11-11
Original file line numberDiff line numberDiff line change
@@ -16,10 +16,10 @@ This rule disallows using `target="_blank"` attribute without `rel="noopener nor
1616
```vue
1717
<template>
1818
<!-- ✓ Good -->
19-
<a link="http://example.com" target="_blank" rel="noopener noreferrer">link</a>
19+
<a href="http://example.com" target="_blank" rel="noopener noreferrer">link</a>
2020
2121
<!-- ✗ BAD -->
22-
<a link="http://example.com" target="_blank" >link</a>
22+
<a href="http://example.com" target="_blank" >link</a>
2323
</temlate>
2424
```
2525

@@ -46,10 +46,10 @@ This rule disallows using `target="_blank"` attribute without `rel="noopener nor
4646
```vue
4747
<template>
4848
<!-- ✓ Good -->
49-
<a link="http://example.com" target="_blank" rel="noopener noreferrer">link</a>
49+
<a href="http://example.com" target="_blank" rel="noopener noreferrer">link</a>
5050
5151
<!-- ✗ BAD -->
52-
<a link="http://example.com" target="_blank" rel="noopener">link</a>
52+
<a href="http://example.com" target="_blank" rel="noopener">link</a>
5353
</temlate>
5454
```
5555

@@ -62,26 +62,26 @@ This rule disallows using `target="_blank"` attribute without `rel="noopener nor
6262
```vue
6363
<template>
6464
<!-- ✓ Good -->
65-
<a link="http://example.com" target="_blank" rel="noopener">link</a>
65+
<a href="http://example.com" target="_blank" rel="noopener">link</a>
6666
6767
<!-- ✗ BAD -->
68-
<a link="http://example.com" target="_blank" >link</a>
68+
<a href="http://example.com" target="_blank" >link</a>
6969
</temlate>
7070
```
7171

7272
</eslint-code-block>
7373

7474
### `{ "enforceDynamicLinks": "always" }` (default)
7575

76-
<eslint-code-block :rules="{'vue/no-template-target-blank': ['error', { enforceDynamicLinks: 'never' }]}">
76+
<eslint-code-block :rules="{'vue/no-template-target-blank': ['error', { enforceDynamicLinks: 'always' }]}">
7777

7878
```vue
7979
<template>
8080
<!-- ✓ Good -->
81-
<a :link="link" target="_blank" rel="noopener noreferrer">link</a>
81+
<a :href="link" target="_blank" rel="noopener noreferrer">link</a>
8282
8383
<!-- ✗ BAD -->
84-
<a :link="link" target="_blank">link</a>
84+
<a :href="link" target="_blank">link</a>
8585
</temlate>
8686
```
8787

@@ -94,10 +94,10 @@ This rule disallows using `target="_blank"` attribute without `rel="noopener nor
9494
```vue
9595
<template>
9696
<!-- ✓ Good -->
97-
<a :link="link" target="_blank">link</a>
97+
<a :href="link" target="_blank">link</a>
9898
9999
<!-- ✗ BAD -->
100-
<a link="http://example.com" target="_blank" >link</a>
100+
<a href="http://example.com" target="_blank" >link</a>
101101
</temlate>
102102
```
103103

Diff for: docs/rules/valid-v-bind-sync.md

+5-1
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,8 @@ This rule checks whether every `.sync` modifier on `v-bind` directives is valid.
1515

1616
This rule reports `.sync` modifier on `v-bind` directives in the following cases:
1717

18-
- The `.sync` modifier does not have the attribute value which is valid as LHS. E.g. `<MyComponent v-bind:aaa.sync="foo() + bar()" />`
18+
- The `.sync` modifier does not have the attribute value which is valid as LHS. E.g. `<MyComponent v-bind:aaa.sync="foo() + bar()" />`, `<MyComponent v-bind:aaa.sync="a?.b" />`
19+
- The `.sync` modifier has potential null object property access. E.g. `<MyComponent v-bind:aaa.sync="(a?.b).c" />`
1920
- The `.sync` modifier is on non Vue-components. E.g. `<input v-bind:aaa.sync="foo"></div>`
2021
- The `.sync` modifier's reference is iteration variables. E.g. `<div v-for="x in list"><MyComponent v-bind:aaa.sync="x" /></div>`
2122

@@ -36,6 +37,9 @@ This rule reports `.sync` modifier on `v-bind` directives in the following cases
3637
<MyComponent v-bind:aaa.sync="foo + bar" />
3738
<MyComponent :aaa.sync="foo + bar" />
3839
40+
<MyComponent :aaa.sync="a?.b.c" />
41+
<MyComponent :aaa.sync="(a?.b).c" />
42+
3943
<input v-bind:aaa.sync="foo">
4044
<input :aaa.sync="foo">
4145

Diff for: docs/rules/valid-v-model.md

+4-1
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,8 @@ This rule reports `v-model` directives in the following cases:
1818
- The directive used on HTMLElement has an argument. E.g. `<input v-model:aaa="foo">`
1919
- The directive used on HTMLElement has modifiers which are not supported. E.g. `<input v-model.bbb="foo">`
2020
- The directive does not have that attribute value. E.g. `<input v-model>`
21-
- The directive does not have the attribute value which is valid as LHS. E.g. `<input v-model="foo() + bar()">`
21+
- The directive does not have the attribute value which is valid as LHS. E.g. `<input v-model="foo() + bar()">`, `<input v-model="a?.b">`
22+
- The directive has potential null object property access. E.g. `<input v-model="(a?.b).c">`
2223
- The directive is on unsupported elements. E.g. `<div v-model="foo"></div>`
2324
- The directive is on `<input>` elements which their types are `file`. E.g. `<input type="file" v-model="foo">`
2425
- The directive's reference is iteration variables. E.g. `<div v-for="x in list"><input type="file" v-model="x"></div>`
@@ -44,6 +45,8 @@ This rule reports `v-model` directives in the following cases:
4445
<input v-model:aaa="foo">
4546
<input v-model.bbb="foo">
4647
<input v-model="foo + bar">
48+
<input v-model="a?.b.c">
49+
<input v-model="(a?.b).c">
4750
<div v-model="foo"/>
4851
<div v-for="todo in todos">
4952
<input v-model="todo">

Diff for: lib/rules/component-name-in-template-casing.js

+5-8
Original file line numberDiff line numberDiff line change
@@ -135,16 +135,13 @@ module.exports = {
135135
name,
136136
caseType
137137
},
138-
fix: (fixer) => {
138+
*fix(fixer) {
139+
yield fixer.replaceText(open, `<${casingName}`)
139140
const endTag = node.endTag
140-
if (!endTag) {
141-
return fixer.replaceText(open, `<${casingName}`)
141+
if (endTag) {
142+
const endTagOpen = tokens.getFirstToken(endTag)
143+
yield fixer.replaceText(endTagOpen, `</${casingName}`)
142144
}
143-
const endTagOpen = tokens.getFirstToken(endTag)
144-
return [
145-
fixer.replaceText(open, `<${casingName}`),
146-
fixer.replaceText(endTagOpen, `</${casingName}`)
147-
]
148145
}
149146
})
150147
}

Diff for: lib/rules/custom-event-name-casing.js

+1-1
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,7 @@ function getNameParamNode(node) {
4848
* @param {CallExpression} node CallExpression
4949
*/
5050
function getCalleeMemberNode(node) {
51-
const callee = node.callee
51+
const callee = utils.unwrapChainExpression(node.callee)
5252

5353
if (callee.type === 'MemberExpression') {
5454
const name = utils.getStaticPropertyName(callee)

Diff for: lib/rules/html-self-closing.js

+2-2
Original file line numberDiff line numberDiff line change
@@ -163,7 +163,7 @@ module.exports = {
163163
elementType: ELEMENT_TYPE_MESSAGES[elementType],
164164
name: node.rawName
165165
},
166-
fix: (fixer) => {
166+
fix(fixer) {
167167
const tokens = context.parserServices.getTemplateBodyTokenStore()
168168
const close = tokens.getLastToken(node.startTag)
169169
if (close.type !== 'HTMLTagClose') {
@@ -187,7 +187,7 @@ module.exports = {
187187
elementType: ELEMENT_TYPE_MESSAGES[elementType],
188188
name: node.rawName
189189
},
190-
fix: (fixer) => {
190+
fix(fixer) {
191191
const tokens = context.parserServices.getTemplateBodyTokenStore()
192192
const close = tokens.getLastToken(node.startTag)
193193
if (close.type !== 'HTMLSelfClosingTagClose') {

Diff for: lib/rules/no-async-in-computed-properties.js

+16-17
Original file line numberDiff line numberDiff line change
@@ -26,16 +26,17 @@ const TIMED_FUNCTIONS = [
2626
* @param {CallExpression} node
2727
*/
2828
function isTimedFunction(node) {
29+
const callee = utils.unwrapChainExpression(node.callee)
2930
return (
3031
((node.type === 'CallExpression' &&
31-
node.callee.type === 'Identifier' &&
32-
TIMED_FUNCTIONS.indexOf(node.callee.name) !== -1) ||
32+
callee.type === 'Identifier' &&
33+
TIMED_FUNCTIONS.indexOf(callee.name) !== -1) ||
3334
(node.type === 'CallExpression' &&
34-
node.callee.type === 'MemberExpression' &&
35-
node.callee.object.type === 'Identifier' &&
36-
node.callee.object.name === 'window' &&
37-
node.callee.property.type === 'Identifier' &&
38-
TIMED_FUNCTIONS.indexOf(node.callee.property.name) !== -1)) &&
35+
callee.type === 'MemberExpression' &&
36+
callee.object.type === 'Identifier' &&
37+
callee.object.name === 'window' &&
38+
callee.property.type === 'Identifier' &&
39+
TIMED_FUNCTIONS.indexOf(callee.property.name) !== -1)) &&
3940
node.arguments.length
4041
)
4142
}
@@ -44,18 +45,16 @@ function isTimedFunction(node) {
4445
* @param {CallExpression} node
4546
*/
4647
function isPromise(node) {
47-
if (
48-
node.type === 'CallExpression' &&
49-
node.callee.type === 'MemberExpression'
50-
) {
48+
const callee = utils.unwrapChainExpression(node.callee)
49+
if (node.type === 'CallExpression' && callee.type === 'MemberExpression') {
5150
return (
5251
// hello.PROMISE_FUNCTION()
53-
(node.callee.property.type === 'Identifier' &&
54-
PROMISE_FUNCTIONS.indexOf(node.callee.property.name) !== -1) || // Promise.PROMISE_METHOD()
55-
(node.callee.object.type === 'Identifier' &&
56-
node.callee.object.name === 'Promise' &&
57-
node.callee.property.type === 'Identifier' &&
58-
PROMISE_METHODS.indexOf(node.callee.property.name) !== -1)
52+
(callee.property.type === 'Identifier' &&
53+
PROMISE_FUNCTIONS.indexOf(callee.property.name) !== -1) || // Promise.PROMISE_METHOD()
54+
(callee.object.type === 'Identifier' &&
55+
callee.object.name === 'Promise' &&
56+
callee.property.type === 'Identifier' &&
57+
PROMISE_METHODS.indexOf(callee.property.name) !== -1)
5958
)
6059
}
6160
return false

Diff for: lib/rules/no-deprecated-events-api.js

+19-6
Original file line numberDiff line numberDiff line change
@@ -32,13 +32,26 @@ module.exports = {
3232
/** @param {RuleContext} context */
3333
create(context) {
3434
return utils.defineVueVisitor(context, {
35-
/** @param {MemberExpression & {parent: CallExpression}} node */
36-
'CallExpression > MemberExpression'(node) {
37-
const call = node.parent
35+
/** @param {MemberExpression & ({parent: CallExpression} | {parent: ChainExpression & {parent: CallExpression}})} node */
36+
'CallExpression > MemberExpression, CallExpression > ChainExpression > MemberExpression'(
37+
node
38+
) {
39+
const call =
40+
node.parent.type === 'ChainExpression'
41+
? node.parent.parent
42+
: node.parent
43+
44+
if (call.optional) {
45+
// It is OK because checking whether it is deprecated.
46+
// e.g. `this.$on?.()`
47+
return
48+
}
49+
3850
if (
39-
call.callee !== node ||
40-
node.property.type !== 'Identifier' ||
41-
!['$on', '$off', '$once'].includes(node.property.name)
51+
utils.unwrapChainExpression(call.callee) !== node ||
52+
!['$on', '$off', '$once'].includes(
53+
utils.getStaticPropertyName(node) || ''
54+
)
4255
) {
4356
return
4457
}

Diff for: lib/rules/no-deprecated-v-bind-sync.js

+1-1
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,7 @@ module.exports = {
3939
node,
4040
loc: node.loc,
4141
messageId: 'syncModifierIsDeprecated',
42-
fix: (fixer) => {
42+
fix(fixer) {
4343
if (node.key.argument == null) {
4444
// is using spread syntax
4545
return null

Diff for: lib/rules/no-deprecated-v-on-number-modifiers.js

+1-1
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,7 @@ module.exports = {
4747
context.report({
4848
node: modifier,
4949
messageId: 'numberModifierIsDeprecated',
50-
fix: (fixer) => {
50+
fix(fixer) {
5151
const key = keyCodeToKey[keyCodes]
5252
if (!key) return null
5353

Diff for: lib/rules/no-deprecated-vue-config-keycodes.js

+3-1
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@
44
*/
55
'use strict'
66

7+
const utils = require('../utils')
8+
79
// ------------------------------------------------------------------------------
810
// Rule Definition
911
// ------------------------------------------------------------------------------
@@ -31,7 +33,7 @@ module.exports = {
3133
"MemberExpression[property.type='Identifier'][property.name='keyCodes']"(
3234
node
3335
) {
34-
const config = node.object
36+
const config = utils.unwrapChainExpression(node.object)
3537
if (
3638
config.type !== 'MemberExpression' ||
3739
config.property.type !== 'Identifier' ||

0 commit comments

Comments
 (0)