Skip to content

Commit

Permalink
Supports Optional Chaining
Browse files Browse the repository at this point in the history
  • Loading branch information
ota-meshi committed Jun 13, 2020
1 parent bbcc1f0 commit 2e3468e
Show file tree
Hide file tree
Showing 44 changed files with 1,224 additions and 296 deletions.
27 changes: 12 additions & 15 deletions docs/.vuepress/components/eslint-code-block.vue
Expand Up @@ -32,7 +32,7 @@ export default {
},
rules: {
type: Object,
default () {
default() {
return {}
}
},
Expand All @@ -46,7 +46,7 @@ export default {
}
},
data () {
data() {
return {
linter: null,
preprocess: processors['.vue'].preprocess,
Expand All @@ -59,7 +59,7 @@ export default {
},
computed: {
config () {
config() {
return {
globals: {
// ES2015 globals
Expand Down Expand Up @@ -89,7 +89,7 @@ export default {
rules: this.rules,
parser: 'vue-eslint-parser',
parserOptions: {
ecmaVersion: 2019,
ecmaVersion: 2020,
sourceType: 'module',
ecmaFeatures: {
jsx: true
Expand All @@ -98,33 +98,30 @@ export default {
}
},
code () {
code() {
return `${this.computeCodeFromSlot(this.$slots.default).trim()}\n`
},
height () {
height() {
const lines = this.code.split('\n').length
return `${Math.max(120, 19 * lines)}px`
}
},
methods: {
computeCodeFromSlot (nodes) {
computeCodeFromSlot(nodes) {
if (!Array.isArray(nodes)) {
return ''
}
return nodes.map(node =>
node.text || this.computeCodeFromSlot(node.children)
).join('')
return nodes
.map((node) => node.text || this.computeCodeFromSlot(node.children))
.join('')
}
},
async mounted () {
async mounted() {
// Load linter.
const [
{ default: Linter },
{ parseForESLint }
] = await Promise.all([
const [{ default: Linter }, { parseForESLint }] = await Promise.all([
import('eslint4b/dist/linter'),
import('espree').then(() => import('vue-eslint-parser'))
])
Expand Down
6 changes: 5 additions & 1 deletion docs/rules/valid-v-bind-sync.md
Expand Up @@ -15,7 +15,8 @@ This rule checks whether every `.sync` modifier on `v-bind` directives is valid.

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

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

Expand All @@ -36,6 +37,9 @@ This rule reports `.sync` modifier on `v-bind` directives in the following cases
<MyComponent v-bind:aaa.sync="foo + bar" />
<MyComponent :aaa.sync="foo + bar" />
<MyComponent :aaa.sync="a?.b.c" />
<MyComponent :aaa.sync="(a?.b).c" />
<input v-bind:aaa.sync="foo">
<input :aaa.sync="foo">
Expand Down
5 changes: 4 additions & 1 deletion docs/rules/valid-v-model.md
Expand Up @@ -18,7 +18,8 @@ This rule reports `v-model` directives in the following cases:
- The directive used on HTMLElement has an argument. E.g. `<input v-model:aaa="foo">`
- The directive used on HTMLElement has modifiers which are not supported. E.g. `<input v-model.bbb="foo">`
- The directive does not have that attribute value. E.g. `<input v-model>`
- The directive does not have the attribute value which is valid as LHS. E.g. `<input v-model="foo() + bar()">`
- 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">`
- The directive has potential null object property access. E.g. `<input v-model="(a?.b).c">`
- The directive is on unsupported elements. E.g. `<div v-model="foo"></div>`
- The directive is on `<input>` elements which their types are `file`. E.g. `<input type="file" v-model="foo">`
- The directive's reference is iteration variables. E.g. `<div v-for="x in list"><input type="file" v-model="x"></div>`
Expand All @@ -44,6 +45,8 @@ This rule reports `v-model` directives in the following cases:
<input v-model:aaa="foo">
<input v-model.bbb="foo">
<input v-model="foo + bar">
<input v-model="a?.b.c">
<input v-model="(a?.b).c">
<div v-model="foo"/>
<div v-for="todo in todos">
<input v-model="todo">
Expand Down
2 changes: 1 addition & 1 deletion lib/rules/custom-event-name-casing.js
Expand Up @@ -48,7 +48,7 @@ function getNameParamNode(node) {
* @param {CallExpression} node CallExpression
*/
function getCalleeMemberNode(node) {
const callee = node.callee
const callee = utils.unwrapChainExpression(node.callee)

if (callee.type === 'MemberExpression') {
const name = utils.getStaticPropertyName(callee)
Expand Down
33 changes: 16 additions & 17 deletions lib/rules/no-async-in-computed-properties.js
Expand Up @@ -26,16 +26,17 @@ const TIMED_FUNCTIONS = [
* @param {CallExpression} node
*/
function isTimedFunction(node) {
const callee = utils.unwrapChainExpression(node.callee)
return (
((node.type === 'CallExpression' &&
node.callee.type === 'Identifier' &&
TIMED_FUNCTIONS.indexOf(node.callee.name) !== -1) ||
callee.type === 'Identifier' &&
TIMED_FUNCTIONS.indexOf(callee.name) !== -1) ||
(node.type === 'CallExpression' &&
node.callee.type === 'MemberExpression' &&
node.callee.object.type === 'Identifier' &&
node.callee.object.name === 'window' &&
node.callee.property.type === 'Identifier' &&
TIMED_FUNCTIONS.indexOf(node.callee.property.name) !== -1)) &&
callee.type === 'MemberExpression' &&
callee.object.type === 'Identifier' &&
callee.object.name === 'window' &&
callee.property.type === 'Identifier' &&
TIMED_FUNCTIONS.indexOf(callee.property.name) !== -1)) &&
node.arguments.length
)
}
Expand All @@ -44,18 +45,16 @@ function isTimedFunction(node) {
* @param {CallExpression} node
*/
function isPromise(node) {
if (
node.type === 'CallExpression' &&
node.callee.type === 'MemberExpression'
) {
const callee = utils.unwrapChainExpression(node.callee)
if (node.type === 'CallExpression' && callee.type === 'MemberExpression') {
return (
// hello.PROMISE_FUNCTION()
(node.callee.property.type === 'Identifier' &&
PROMISE_FUNCTIONS.indexOf(node.callee.property.name) !== -1) || // Promise.PROMISE_METHOD()
(node.callee.object.type === 'Identifier' &&
node.callee.object.name === 'Promise' &&
node.callee.property.type === 'Identifier' &&
PROMISE_METHODS.indexOf(node.callee.property.name) !== -1)
(callee.property.type === 'Identifier' &&
PROMISE_FUNCTIONS.indexOf(callee.property.name) !== -1) || // Promise.PROMISE_METHOD()
(callee.object.type === 'Identifier' &&
callee.object.name === 'Promise' &&
callee.property.type === 'Identifier' &&
PROMISE_METHODS.indexOf(callee.property.name) !== -1)
)
}
return false
Expand Down
25 changes: 19 additions & 6 deletions lib/rules/no-deprecated-events-api.js
Expand Up @@ -32,13 +32,26 @@ module.exports = {
/** @param {RuleContext} context */
create(context) {
return utils.defineVueVisitor(context, {
/** @param {MemberExpression & {parent: CallExpression}} node */
'CallExpression > MemberExpression'(node) {
const call = node.parent
/** @param {MemberExpression & ({parent: CallExpression} | {parent: ChainExpression & {parent: CallExpression}})} node */
'CallExpression > MemberExpression, CallExpression > ChainExpression > MemberExpression'(
node
) {
const call =
node.parent.type === 'ChainExpression'
? node.parent.parent
: node.parent

if (call.optional) {
// It is OK because checking whether it is deprecated.
// e.g. `this.$on?.()`
return
}

if (
call.callee !== node ||
node.property.type !== 'Identifier' ||
!['$on', '$off', '$once'].includes(node.property.name)
utils.unwrapChainExpression(call.callee) !== node ||
!['$on', '$off', '$once'].includes(
utils.getStaticPropertyName(node) || ''
)
) {
return
}
Expand Down
4 changes: 3 additions & 1 deletion lib/rules/no-deprecated-vue-config-keycodes.js
Expand Up @@ -4,6 +4,8 @@
*/
'use strict'

const utils = require('../utils')

// ------------------------------------------------------------------------------
// Rule Definition
// ------------------------------------------------------------------------------
Expand Down Expand Up @@ -31,7 +33,7 @@ module.exports = {
"MemberExpression[property.type='Identifier'][property.name='keyCodes']"(
node
) {
const config = node.object
const config = utils.unwrapChainExpression(node.object)
if (
config.type !== 'MemberExpression' ||
config.property.type !== 'Identifier' ||
Expand Down
9 changes: 3 additions & 6 deletions lib/rules/no-multiple-slot-args.js
Expand Up @@ -100,15 +100,12 @@ module.exports = {
return utils.defineVueVisitor(context, {
/** @param {MemberExpression} node */
MemberExpression(node) {
const object = node.object
const object = utils.unwrapChainExpression(node.object)
if (object.type !== 'MemberExpression') {
return
}
if (
object.property.type !== 'Identifier' ||
(object.property.name !== '$slots' &&
object.property.name !== '$scopedSlots')
) {
const name = utils.getStaticPropertyName(object)
if (!name || (name !== '$slots' && name !== '$scopedSlots')) {
return
}
if (!utils.isThis(object.object, context)) {
Expand Down
8 changes: 4 additions & 4 deletions lib/rules/no-setup-props-destructure.js
Expand Up @@ -49,18 +49,18 @@ module.exports = {
return
}

const rightNode = utils.unwrapChainExpression(right)
if (
left.type !== 'ArrayPattern' &&
left.type !== 'ObjectPattern' &&
right.type !== 'MemberExpression'
rightNode.type !== 'MemberExpression'
) {
return
}

/** @type {Expression | Super} */
let rightId = right
let rightId = rightNode
while (rightId.type === 'MemberExpression') {
rightId = rightId.object
rightId = utils.unwrapChainExpression(rightId.object)
}
if (rightId.type === 'Identifier' && propsReferenceIds.has(rightId)) {
report(left, 'getProperty')
Expand Down
10 changes: 9 additions & 1 deletion lib/rules/no-unused-properties.js
Expand Up @@ -221,7 +221,7 @@ function getObjectPatternPropertyPatternTracker(pattern) {
}

/**
* @param {Identifier | MemberExpression | ThisExpression} node
* @param {Identifier | MemberExpression | ChainExpression | ThisExpression} node
* @param {RuleContext} context
* @returns {UsedProps}
*/
Expand Down Expand Up @@ -304,6 +304,14 @@ function extractPatternOrThisProperties(node, context) {
}
}
}
} else if (parent.type === 'ChainExpression') {
const { usedNames, unknown, calls } = extractPatternOrThisProperties(
parent,
context
)
result.usedNames.addAll(usedNames)
result.unknown = result.unknown || unknown
result.calls.push(...calls)
}
return result
}
Expand Down
6 changes: 5 additions & 1 deletion lib/rules/no-watch-after-await.js
Expand Up @@ -7,7 +7,8 @@ const { ReferenceTracker } = require('eslint-utils')
const utils = require('../utils')

/**
* @param {CallExpression} node
* @param {CallExpression | ChainExpression} node
* @returns {boolean}
*/
function isMaybeUsedStopHandle(node) {
const parent = node.parent
Expand All @@ -32,6 +33,9 @@ function isMaybeUsedStopHandle(node) {
// [watch()]
return true
}
if (parent.type === 'ChainExpression') {
return isMaybeUsedStopHandle(parent)
}
}
return false
}
Expand Down
4 changes: 3 additions & 1 deletion lib/rules/order-in-components.js
Expand Up @@ -185,7 +185,9 @@ function isNotSideEffectsNode(node, visitorKeys) {
node.type !== 'ConditionalExpression' &&
// es2015
node.type !== 'SpreadElement' &&
node.type !== 'TemplateLiteral'
node.type !== 'TemplateLiteral' &&
// es2020
node.type !== 'ChainExpression'
) {
// Can not be sure that a node has no side effects
result = false
Expand Down
20 changes: 13 additions & 7 deletions lib/rules/require-default-prop.js
Expand Up @@ -81,12 +81,18 @@ module.exports = {
function findPropsWithoutDefaultValue(props) {
return props.filter((prop) => {
if (prop.value.type !== 'ObjectExpression') {
return (
(prop.value.type !== 'CallExpression' &&
prop.value.type !== 'Identifier') ||
(prop.value.type === 'Identifier' &&
NATIVE_TYPES.has(prop.value.name))
)
if (prop.value.type === 'Identifier') {
return NATIVE_TYPES.has(prop.value.name)
}
if (
prop.value.type === 'CallExpression' ||
prop.value.type === 'MemberExpression'
) {
// OK
return false
}
// NG
return true
}

return (
Expand All @@ -98,7 +104,7 @@ module.exports = {

/**
* Detects whether given value node is a Boolean type
* @param {Expression | Pattern} value
* @param {Expression} value
* @return {Boolean}
*/
function isValueNodeOfBooleanType(value) {
Expand Down

0 comments on commit 2e3468e

Please sign in to comment.