Skip to content

Commit 2e3468e

Browse files
committed
Supports Optional Chaining
1 parent bbcc1f0 commit 2e3468e

Some content is hidden

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

44 files changed

+1224
-296
lines changed

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
])

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

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">

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)

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

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
}

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' ||

lib/rules/no-multiple-slot-args.js

+3-6
Original file line numberDiff line numberDiff line change
@@ -100,15 +100,12 @@ module.exports = {
100100
return utils.defineVueVisitor(context, {
101101
/** @param {MemberExpression} node */
102102
MemberExpression(node) {
103-
const object = node.object
103+
const object = utils.unwrapChainExpression(node.object)
104104
if (object.type !== 'MemberExpression') {
105105
return
106106
}
107-
if (
108-
object.property.type !== 'Identifier' ||
109-
(object.property.name !== '$slots' &&
110-
object.property.name !== '$scopedSlots')
111-
) {
107+
const name = utils.getStaticPropertyName(object)
108+
if (!name || (name !== '$slots' && name !== '$scopedSlots')) {
112109
return
113110
}
114111
if (!utils.isThis(object.object, context)) {

lib/rules/no-setup-props-destructure.js

+4-4
Original file line numberDiff line numberDiff line change
@@ -49,18 +49,18 @@ module.exports = {
4949
return
5050
}
5151

52+
const rightNode = utils.unwrapChainExpression(right)
5253
if (
5354
left.type !== 'ArrayPattern' &&
5455
left.type !== 'ObjectPattern' &&
55-
right.type !== 'MemberExpression'
56+
rightNode.type !== 'MemberExpression'
5657
) {
5758
return
5859
}
59-
6060
/** @type {Expression | Super} */
61-
let rightId = right
61+
let rightId = rightNode
6262
while (rightId.type === 'MemberExpression') {
63-
rightId = rightId.object
63+
rightId = utils.unwrapChainExpression(rightId.object)
6464
}
6565
if (rightId.type === 'Identifier' && propsReferenceIds.has(rightId)) {
6666
report(left, 'getProperty')

lib/rules/no-unused-properties.js

+9-1
Original file line numberDiff line numberDiff line change
@@ -221,7 +221,7 @@ function getObjectPatternPropertyPatternTracker(pattern) {
221221
}
222222

223223
/**
224-
* @param {Identifier | MemberExpression | ThisExpression} node
224+
* @param {Identifier | MemberExpression | ChainExpression | ThisExpression} node
225225
* @param {RuleContext} context
226226
* @returns {UsedProps}
227227
*/
@@ -304,6 +304,14 @@ function extractPatternOrThisProperties(node, context) {
304304
}
305305
}
306306
}
307+
} else if (parent.type === 'ChainExpression') {
308+
const { usedNames, unknown, calls } = extractPatternOrThisProperties(
309+
parent,
310+
context
311+
)
312+
result.usedNames.addAll(usedNames)
313+
result.unknown = result.unknown || unknown
314+
result.calls.push(...calls)
307315
}
308316
return result
309317
}

lib/rules/no-watch-after-await.js

+5-1
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,8 @@ const { ReferenceTracker } = require('eslint-utils')
77
const utils = require('../utils')
88

99
/**
10-
* @param {CallExpression} node
10+
* @param {CallExpression | ChainExpression} node
11+
* @returns {boolean}
1112
*/
1213
function isMaybeUsedStopHandle(node) {
1314
const parent = node.parent
@@ -32,6 +33,9 @@ function isMaybeUsedStopHandle(node) {
3233
// [watch()]
3334
return true
3435
}
36+
if (parent.type === 'ChainExpression') {
37+
return isMaybeUsedStopHandle(parent)
38+
}
3539
}
3640
return false
3741
}

lib/rules/order-in-components.js

+3-1
Original file line numberDiff line numberDiff line change
@@ -185,7 +185,9 @@ function isNotSideEffectsNode(node, visitorKeys) {
185185
node.type !== 'ConditionalExpression' &&
186186
// es2015
187187
node.type !== 'SpreadElement' &&
188-
node.type !== 'TemplateLiteral'
188+
node.type !== 'TemplateLiteral' &&
189+
// es2020
190+
node.type !== 'ChainExpression'
189191
) {
190192
// Can not be sure that a node has no side effects
191193
result = false

lib/rules/require-default-prop.js

+13-7
Original file line numberDiff line numberDiff line change
@@ -81,12 +81,18 @@ module.exports = {
8181
function findPropsWithoutDefaultValue(props) {
8282
return props.filter((prop) => {
8383
if (prop.value.type !== 'ObjectExpression') {
84-
return (
85-
(prop.value.type !== 'CallExpression' &&
86-
prop.value.type !== 'Identifier') ||
87-
(prop.value.type === 'Identifier' &&
88-
NATIVE_TYPES.has(prop.value.name))
89-
)
84+
if (prop.value.type === 'Identifier') {
85+
return NATIVE_TYPES.has(prop.value.name)
86+
}
87+
if (
88+
prop.value.type === 'CallExpression' ||
89+
prop.value.type === 'MemberExpression'
90+
) {
91+
// OK
92+
return false
93+
}
94+
// NG
95+
return true
9096
}
9197

9298
return (
@@ -98,7 +104,7 @@ module.exports = {
98104

99105
/**
100106
* Detects whether given value node is a Boolean type
101-
* @param {Expression | Pattern} value
107+
* @param {Expression} value
102108
* @return {Boolean}
103109
*/
104110
function isValueNodeOfBooleanType(value) {

0 commit comments

Comments
 (0)