Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Breaking: support new syntax in Vue.js 2.6 #807

Merged
merged 11 commits into from Feb 27, 2019
2 changes: 1 addition & 1 deletion docs/rules/attributes-order.md
Expand Up @@ -25,7 +25,7 @@ This rule aims to enforce ordering of component attributes. The default order is
- `GLOBAL`
ex: 'id'
- `UNIQUE`
ex: 'ref', 'key', 'slot'
ex: 'ref', 'key', 'v-slot', 'slot'
- `TWO_WAY_BINDING`
ex: 'v-model'
- `OTHER_DIRECTIVES`
Expand Down
7 changes: 5 additions & 2 deletions lib/rules/array-bracket-spacing.js
Expand Up @@ -5,5 +5,8 @@

const { wrapCoreRule } = require('../utils')

// eslint-disable-next-line
module.exports = wrapCoreRule(require('eslint/lib/rules/array-bracket-spacing'))
// eslint-disable-next-line no-invalid-meta
module.exports = wrapCoreRule(
require('eslint/lib/rules/array-bracket-spacing'),
{ skipDynamicArguments: true }
)
5 changes: 4 additions & 1 deletion lib/rules/attribute-hyphenation.js
Expand Up @@ -92,7 +92,10 @@ module.exports = {
VAttribute (node) {
if (!utils.isCustomComponent(node.parent.parent)) return

const name = !node.directive ? node.key.rawName : node.key.name === 'bind' ? node.key.raw.argument : false
const name =
!node.directive ? node.key.rawName
: node.key.name.name === 'bind' ? node.key.argument && node.key.argument.rawName
: /* otherwise */ false
if (!name || isIgnoredAttribute(name)) return

reportIssue(node, name)
Expand Down
22 changes: 14 additions & 8 deletions lib/rules/attributes-order.js
Expand Up @@ -9,8 +9,13 @@ const utils = require('../utils')
// Rule Definition
// ------------------------------------------------------------------------------

function getAttributeType (name, isDirective) {
if (isDirective) {
function getAttributeType (attribute, sourceCode) {
const isBind = attribute.directive && attribute.key.name.name === 'bind'
const name = isBind
? (attribute.key.argument ? sourceCode.getText(attribute.key.argument) : '')
: (attribute.directive ? attribute.key.name.name : attribute.key.name)

if (attribute.directive && !isBind) {
if (name === 'for') {
return 'LIST_RENDERING'
} else if (name === 'if' || name === 'else-if' || name === 'else' || name === 'show' || name === 'cloak') {
Expand All @@ -23,6 +28,8 @@ function getAttributeType (name, isDirective) {
return 'EVENTS'
} else if (name === 'html' || name === 'text') {
return 'CONTENT'
} else if (name === 'slot') {
return 'UNIQUE'
} else {
return 'OTHER_DIRECTIVES'
}
Expand All @@ -38,10 +45,9 @@ function getAttributeType (name, isDirective) {
}
}
}
function getPosition (attribute, attributePosition) {
const attributeType = attribute.directive && attribute.key.name === 'bind'
? getAttributeType(attribute.key.argument, false)
: getAttributeType(attribute.key.name, attribute.directive)

function getPosition (attribute, attributePosition, sourceCode) {
const attributeType = getAttributeType(attribute, sourceCode)
return attributePosition.hasOwnProperty(attributeType) ? attributePosition[attributeType] : -1
}

Expand Down Expand Up @@ -91,8 +97,8 @@ function create (context) {
previousNode = null
},
'VAttribute' (node) {
if ((currentPosition === -1) || (currentPosition <= getPosition(node, attributePosition))) {
currentPosition = getPosition(node, attributePosition)
if ((currentPosition === -1) || (currentPosition <= getPosition(node, attributePosition, sourceCode))) {
currentPosition = getPosition(node, attributePosition, sourceCode)
previousNode = node
} else {
reportIssue(node, previousNode)
Expand Down
7 changes: 5 additions & 2 deletions lib/rules/block-spacing.js
Expand Up @@ -5,5 +5,8 @@

const { wrapCoreRule } = require('../utils')

// eslint-disable-next-line
module.exports = wrapCoreRule(require('eslint/lib/rules/block-spacing'))
// eslint-disable-next-line no-invalid-meta
module.exports = wrapCoreRule(
require('eslint/lib/rules/block-spacing'),
{ skipDynamicArguments: true }
)
8 changes: 6 additions & 2 deletions lib/rules/brace-style.js
Expand Up @@ -5,5 +5,9 @@

const { wrapCoreRule } = require('../utils')

// eslint-disable-next-line
module.exports = wrapCoreRule(require('eslint/lib/rules/brace-style'))
// eslint-disable-next-line no-invalid-meta
module.exports = wrapCoreRule(
require('eslint/lib/rules/brace-style'),
{ skipDynamicArguments: true }
)

7 changes: 5 additions & 2 deletions lib/rules/key-spacing.js
Expand Up @@ -5,5 +5,8 @@

const { wrapCoreRule } = require('../utils')

// eslint-disable-next-line
module.exports = wrapCoreRule(require('eslint/lib/rules/key-spacing'))
// eslint-disable-next-line no-invalid-meta
module.exports = wrapCoreRule(
require('eslint/lib/rules/key-spacing'),
{ skipDynamicArguments: true }
)
22 changes: 3 additions & 19 deletions lib/rules/max-attributes-per-line.js
Expand Up @@ -67,6 +67,7 @@ module.exports = {
},

create: function (context) {
const sourceCode = context.getSourceCode()
const configuration = parseOptions(context.options[0])
const multilineMaximum = configuration.multiline
const singlelinemMaximum = configuration.singleline
Expand Down Expand Up @@ -130,23 +131,6 @@ module.exports = {
return defaults
}

function getPropData (prop) {
let propType = 'Attribute'
let propName = prop.key.name

if (utils.isBindingAttribute(prop)) {
propType = 'Binding'
propName = prop.key.raw.argument
} else if (utils.isEventAttribute(prop)) {
propType = 'Event'
propName = prop.key.raw.argument
} else if (prop.directive) {
propType = 'Directive'
}

return { propType, propName }
}

function showErrors (attributes) {
attributes.forEach((prop, i) => {
const fix = (fixer) => {
Expand All @@ -166,8 +150,8 @@ module.exports = {
context.report({
node: prop,
loc: prop.loc,
message: '{{propType}} "{{propName}}" should be on a new line.',
data: getPropData(prop),
message: '\'{{name}}\' should be on a new line.',
data: { name: sourceCode.getText(prop.key) },
fix
})
})
Expand Down
2 changes: 1 addition & 1 deletion lib/rules/no-confusing-v-for-v-if.js
Expand Up @@ -50,7 +50,7 @@ module.exports = {

create (context) {
return utils.defineTemplateBodyVisitor(context, {
"VAttribute[directive=true][key.name='if']" (node) {
"VAttribute[directive=true][key.name.name='if']" (node) {
const element = node.parent.parent

if (utils.hasDirective(element, 'for') && !isUsingIterationVar(node)) {
Expand Down
4 changes: 2 additions & 2 deletions lib/rules/no-duplicate-attributes.js
Expand Up @@ -24,8 +24,8 @@ function getName (attribute) {
if (!attribute.directive) {
return attribute.key.name
}
if (attribute.key.name === 'bind') {
return attribute.key.argument || null
if (attribute.key.name.name === 'bind') {
return (attribute.key.argument && attribute.key.argument.name) || null
}
return null
}
Expand Down
2 changes: 1 addition & 1 deletion lib/rules/no-unused-components.js
Expand Up @@ -55,7 +55,7 @@ module.exports = {

usedComponents.add(node.rawName)
},
"VAttribute[directive=true][key.name='bind'][key.argument='is']" (node) {
"VAttribute[directive=true][key.name.name='bind'][key.argument.name='is']" (node) {
if (
!node.value || // `<component :is>`
node.value.type !== 'VExpressionContainer' ||
Expand Down
2 changes: 1 addition & 1 deletion lib/rules/no-use-v-if-with-v-for.js
Expand Up @@ -70,7 +70,7 @@ module.exports = {
const options = context.options[0] || {}
const allowUsingIterationVar = options.allowUsingIterationVar === true // default false
return utils.defineTemplateBodyVisitor(context, {
"VAttribute[directive=true][key.name='if']" (node) {
"VAttribute[directive=true][key.name.name='if']" (node) {
const element = node.parent.parent

if (utils.hasDirective(element, 'for')) {
Expand Down
2 changes: 1 addition & 1 deletion lib/rules/no-v-html.js
Expand Up @@ -22,7 +22,7 @@ module.exports = {
},
create (context) {
return utils.defineTemplateBodyVisitor(context, {
"VAttribute[directive=true][key.name='html']" (node) {
"VAttribute[directive=true][key.name.name='html']" (node) {
context.report({
node,
loc: node.loc,
Expand Down
7 changes: 5 additions & 2 deletions lib/rules/object-curly-spacing.js
Expand Up @@ -5,5 +5,8 @@

const { wrapCoreRule } = require('../utils')

// eslint-disable-next-line
module.exports = wrapCoreRule(require('eslint/lib/rules/object-curly-spacing'))
// eslint-disable-next-line no-invalid-meta
module.exports = wrapCoreRule(
require('eslint/lib/rules/object-curly-spacing'),
{ skipDynamicArguments: true }
)
2 changes: 1 addition & 1 deletion lib/rules/require-v-for-key.js
Expand Up @@ -49,7 +49,7 @@ module.exports = {
}

return utils.defineTemplateBodyVisitor(context, {
"VAttribute[directive=true][key.name='for']" (node) {
"VAttribute[directive=true][key.name.name='for']" (node) {
checkKey(node.parent.parent)
}
})
Expand Down
7 changes: 5 additions & 2 deletions lib/rules/space-infix-ops.js
Expand Up @@ -5,5 +5,8 @@

const { wrapCoreRule } = require('../utils')

// eslint-disable-next-line
module.exports = wrapCoreRule(require('eslint/lib/rules/space-infix-ops'))
// eslint-disable-next-line no-invalid-meta
module.exports = wrapCoreRule(
require('eslint/lib/rules/space-infix-ops'),
{ skipDynamicArguments: true }
)
7 changes: 5 additions & 2 deletions lib/rules/space-unary-ops.js
Expand Up @@ -5,5 +5,8 @@

const { wrapCoreRule } = require('../utils')

// eslint-disable-next-line
module.exports = wrapCoreRule(require('eslint/lib/rules/space-unary-ops'))
// eslint-disable-next-line no-invalid-meta
module.exports = wrapCoreRule(
require('eslint/lib/rules/space-unary-ops'),
{ skipDynamicArguments: true }
)
5 changes: 5 additions & 0 deletions lib/rules/this-in-template.js
Expand Up @@ -84,6 +84,11 @@ module.exports = {
}
: {
'VExpressionContainer' (node) {
if (node.parent.type === 'VDirectiveKey') {
// We cannot use `.` in dynamic arguments because the right of the `.` becomes a modifier.
// For example, In `:[this.prop]` case, `:[this` is an argument and `prop]` is a modifier.
return
}
if (node.references) {
for (const reference of node.references) {
if (!scope.nodes.some(el => el.name === reference.id.name)) {
Expand Down
13 changes: 8 additions & 5 deletions lib/rules/use-v-on-exact.js
Expand Up @@ -20,18 +20,19 @@ const SYSTEM_MODIFIERS = new Set(['ctrl', 'shift', 'alt', 'meta'])
* Finds and returns all keys for event directives
*
* @param {array} attributes Element attributes
* @param {SourceCode} sourceCode The source code object.
* @returns {array[object]} [{ name, node, modifiers }]
*/
function getEventDirectives (attributes) {
function getEventDirectives (attributes, sourceCode) {
return attributes
.filter(attribute =>
attribute.directive &&
attribute.key.name === 'on'
attribute.key.name.name === 'on'
)
.map(attribute => ({
name: attribute.key.argument,
name: attribute.key.argument ? sourceCode.getText(attribute.key.argument) : '',
node: attribute.key,
modifiers: attribute.key.modifiers
modifiers: attribute.key.modifiers.map(modifier => modifier.name)
}))
}

Expand Down Expand Up @@ -149,12 +150,14 @@ module.exports = {
* @returns {Object} AST event handlers.
*/
create (context) {
const sourceCode = context.getSourceCode()

return utils.defineTemplateBodyVisitor(context, {
VStartTag (node) {
if (node.attributes.length === 0) return

const isCustomComponent = utils.isCustomComponent(node.parent)
let events = getEventDirectives(node.attributes)
let events = getEventDirectives(node.attributes, sourceCode)

if (isCustomComponent) {
// For components consider only events with `native` modifier
Expand Down
37 changes: 28 additions & 9 deletions lib/rules/v-bind-style.js
Expand Up @@ -30,23 +30,42 @@ module.exports = {
},

create (context) {
const shorthand = context.options[0] !== 'longform'
const preferShorthand = context.options[0] !== 'longform'

return utils.defineTemplateBodyVisitor(context, {
"VAttribute[directive=true][key.name='bind'][key.argument!=null]" (node) {
if (node.key.shorthand === shorthand) {
"VAttribute[directive=true][key.name.name='bind'][key.argument!=null]" (node) {
const shorthandProp = node.key.name.rawName === '.'
const shorthand = node.key.name.rawName === ':' || shorthandProp
if (shorthand === preferShorthand) {
return
}

context.report({
node,
loc: node.loc,
message: shorthand
? "Unexpected 'v-bind' before ':'."
: "Expected 'v-bind' before ':'.",
fix: (fixer) => shorthand
? fixer.removeRange([node.range[0], node.range[0] + 6])
: fixer.insertTextBefore(node, 'v-bind')
message:
preferShorthand ? "Unexpected 'v-bind' before ':'."
: shorthandProp ? "Expected 'v-bind:' instead of '.'."
/* otherwise */ : "Expected 'v-bind' before ':'.",
* fix (fixer) {
if (preferShorthand) {
yield fixer.remove(node.key.name)
} else {
yield fixer.insertTextBefore(node, 'v-bind')

if (shorthandProp) {
// Replace `.` by `:`.
yield fixer.replaceText(node.key.name, ':')

// Insert `.prop` modifier if it doesn't exist.
const modifier = node.key.modifiers[0]
const isAutoGeneratedPropModifier = modifier.name === 'prop' && modifier.rawName === ''
if (isAutoGeneratedPropModifier) {
yield fixer.insertTextBefore(modifier, '.prop')
}
}
}
}
})
}
})
Expand Down
4 changes: 2 additions & 2 deletions lib/rules/v-on-function-call.js
Expand Up @@ -31,7 +31,7 @@ module.exports = {
const always = context.options[0] === 'always'

return utils.defineTemplateBodyVisitor(context, {
"VAttribute[directive=true][key.name='on'][key.argument!=null] > VExpressionContainer > Identifier" (node) {
"VAttribute[directive=true][key.name.name='on'][key.argument!=null] > VExpressionContainer > Identifier" (node) {
if (!always) return
context.report({
node,
Expand All @@ -40,7 +40,7 @@ module.exports = {
})
},

"VAttribute[directive=true][key.name='on'][key.argument!=null] VOnExpression > ExpressionStatement > *" (node) {
"VAttribute[directive=true][key.name.name='on'][key.argument!=null] VOnExpression > ExpressionStatement > *" (node) {
if (!always && node.type === 'CallExpression' && node.arguments.length === 0) {
context.report({
node,
Expand Down
11 changes: 6 additions & 5 deletions lib/rules/v-on-style.js
Expand Up @@ -30,22 +30,23 @@ module.exports = {
},

create (context) {
const shorthand = context.options[0] !== 'longform'
const preferShorthand = context.options[0] !== 'longform'

return utils.defineTemplateBodyVisitor(context, {
"VAttribute[directive=true][key.name='on'][key.argument!=null]" (node) {
if (node.key.shorthand === shorthand) {
"VAttribute[directive=true][key.name.name='on'][key.argument!=null]" (node) {
const shorthand = node.key.name.rawName === '@'
if (shorthand === preferShorthand) {
return
}

const pos = node.range[0]
context.report({
node,
loc: node.loc,
message: shorthand
message: preferShorthand
? "Expected '@' instead of 'v-on:'."
: "Expected 'v-on:' instead of '@'.",
fix: (fixer) => shorthand
fix: (fixer) => preferShorthand
? fixer.replaceTextRange([pos, pos + 5], '@')
: fixer.replaceTextRange([pos, pos + 1], 'v-on:')
})
Expand Down