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 14, 2020
1 parent 752a028 commit 2a1a2a6
Show file tree
Hide file tree
Showing 58 changed files with 1,296 additions and 364 deletions.
4 changes: 3 additions & 1 deletion .eslintrc.js
Expand Up @@ -49,7 +49,9 @@ module.exports = {
{
pattern: `https://eslint.vuejs.org/rules/{{name}}.html`
}
]
],

'eslint-plugin/fixer-return': 'off'
}
}
]
Expand Down
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
2 changes: 1 addition & 1 deletion docs/rules/no-arrow-functions-in-watch.md
Expand Up @@ -40,7 +40,7 @@ export default {
/* ... */
}
],
'e.f': function (val, oldVal) { /* ... */ }
'e.f': function (val, oldVal) { /* ... */ },
/* ✗ BAD */
foo: (val, oldVal) => {
Expand Down
8 changes: 8 additions & 0 deletions docs/rules/no-deprecated-events-api.md
Expand Up @@ -26,7 +26,15 @@ export default {
this.$emit('start')
}
}
</script>
```

</eslint-code-block>

<eslint-code-block :rules="{'vue/no-deprecated-events-api': ['error']}">

```vue
<script>
/* ✓ GOOD */
import mitt from 'mitt'
const emitter = mitt()
Expand Down
22 changes: 11 additions & 11 deletions docs/rules/no-template-target-blank.md
Expand Up @@ -16,10 +16,10 @@ This rule disallows using `target="_blank"` attribute without `rel="noopener nor
```vue
<template>
<!-- ✓ Good -->
<a link="http://example.com" target="_blank" rel="noopener noreferrer">link</a>
<a href="http://example.com" target="_blank" rel="noopener noreferrer">link</a>
<!-- ✗ BAD -->
<a link="http://example.com" target="_blank" >link</a>
<a href="http://example.com" target="_blank" >link</a>
</temlate>
```

Expand All @@ -46,10 +46,10 @@ This rule disallows using `target="_blank"` attribute without `rel="noopener nor
```vue
<template>
<!-- ✓ Good -->
<a link="http://example.com" target="_blank" rel="noopener noreferrer">link</a>
<a href="http://example.com" target="_blank" rel="noopener noreferrer">link</a>
<!-- ✗ BAD -->
<a link="http://example.com" target="_blank" rel="noopener">link</a>
<a href="http://example.com" target="_blank" rel="noopener">link</a>
</temlate>
```

Expand All @@ -62,26 +62,26 @@ This rule disallows using `target="_blank"` attribute without `rel="noopener nor
```vue
<template>
<!-- ✓ Good -->
<a link="http://example.com" target="_blank" rel="noopener">link</a>
<a href="http://example.com" target="_blank" rel="noopener">link</a>
<!-- ✗ BAD -->
<a link="http://example.com" target="_blank" >link</a>
<a href="http://example.com" target="_blank" >link</a>
</temlate>
```

</eslint-code-block>

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

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

```vue
<template>
<!-- ✓ Good -->
<a :link="link" target="_blank" rel="noopener noreferrer">link</a>
<a :href="link" target="_blank" rel="noopener noreferrer">link</a>
<!-- ✗ BAD -->
<a :link="link" target="_blank">link</a>
<a :href="link" target="_blank">link</a>
</temlate>
```

Expand All @@ -94,10 +94,10 @@ This rule disallows using `target="_blank"` attribute without `rel="noopener nor
```vue
<template>
<!-- ✓ Good -->
<a :link="link" target="_blank">link</a>
<a :href="link" target="_blank">link</a>
<!-- ✗ BAD -->
<a link="http://example.com" target="_blank" >link</a>
<a href="http://example.com" target="_blank" >link</a>
</temlate>
```

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
13 changes: 5 additions & 8 deletions lib/rules/component-name-in-template-casing.js
Expand Up @@ -135,16 +135,13 @@ module.exports = {
name,
caseType
},
fix: (fixer) => {
*fix(fixer) {
yield fixer.replaceText(open, `<${casingName}`)
const endTag = node.endTag
if (!endTag) {
return fixer.replaceText(open, `<${casingName}`)
if (endTag) {
const endTagOpen = tokens.getFirstToken(endTag)
yield fixer.replaceText(endTagOpen, `</${casingName}`)
}
const endTagOpen = tokens.getFirstToken(endTag)
return [
fixer.replaceText(open, `<${casingName}`),
fixer.replaceText(endTagOpen, `</${casingName}`)
]
}
})
}
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
4 changes: 2 additions & 2 deletions lib/rules/html-self-closing.js
Expand Up @@ -163,7 +163,7 @@ module.exports = {
elementType: ELEMENT_TYPE_MESSAGES[elementType],
name: node.rawName
},
fix: (fixer) => {
fix(fixer) {
const tokens = context.parserServices.getTemplateBodyTokenStore()
const close = tokens.getLastToken(node.startTag)
if (close.type !== 'HTMLTagClose') {
Expand All @@ -187,7 +187,7 @@ module.exports = {
elementType: ELEMENT_TYPE_MESSAGES[elementType],
name: node.rawName
},
fix: (fixer) => {
fix(fixer) {
const tokens = context.parserServices.getTemplateBodyTokenStore()
const close = tokens.getLastToken(node.startTag)
if (close.type !== 'HTMLSelfClosingTagClose') {
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
2 changes: 1 addition & 1 deletion lib/rules/no-deprecated-v-bind-sync.js
Expand Up @@ -39,7 +39,7 @@ module.exports = {
node,
loc: node.loc,
messageId: 'syncModifierIsDeprecated',
fix: (fixer) => {
fix(fixer) {
if (node.key.argument == null) {
// is using spread syntax
return null
Expand Down
2 changes: 1 addition & 1 deletion lib/rules/no-deprecated-v-on-number-modifiers.js
Expand Up @@ -47,7 +47,7 @@ module.exports = {
context.report({
node: modifier,
messageId: 'numberModifierIsDeprecated',
fix: (fixer) => {
fix(fixer) {
const key = keyCodeToKey[keyCodes]
if (!key) return null

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

0 comments on commit 2a1a2a6

Please sign in to comment.