eslint-plugin-vue
Advanced tools
Comparing version 5.0.0-beta.3 to 5.0.0-beta.4
@@ -55,2 +55,3 @@ /* | ||
'this-in-template': require('./rules/this-in-template'), | ||
'use-v-on-exact': require('./rules/use-v-on-exact'), | ||
'v-bind-style': require('./rules/v-bind-style'), | ||
@@ -57,0 +58,0 @@ 'v-on-style': require('./rules/v-on-style'), |
@@ -19,3 +19,3 @@ /** | ||
category: 'strongly-recommended', | ||
url: 'https://github.com/vuejs/eslint-plugin-vue/blob/v5.0.0-beta.3/docs/rules/attribute-hyphenation.md' | ||
url: 'https://github.com/vuejs/eslint-plugin-vue/blob/v5.0.0-beta.4/docs/rules/attribute-hyphenation.md' | ||
}, | ||
@@ -22,0 +22,0 @@ fixable: 'code', |
@@ -80,21 +80,6 @@ /** | ||
// If we can upgrade requirements to `eslint@>4.1.0`, this code can be replaced by: | ||
// return shiftAttrs.map((attr, i) => { | ||
// const text = attr === previousNode ? sourceCode.getText(node) : sourceCode.getText(shiftAttrs[i - 1]) | ||
// return fixer.replaceText(attr, text) | ||
// }) | ||
const replaceDataList = shiftAttrs.map((attr, i) => { | ||
return shiftAttrs.map((attr, i) => { | ||
const text = attr === previousNode ? sourceCode.getText(node) : sourceCode.getText(shiftAttrs[i - 1]) | ||
return { | ||
range: attr.range, | ||
text | ||
} | ||
return fixer.replaceText(attr, text) | ||
}) | ||
const replaceRange = [previousNode.range[0], node.range[1]] | ||
let text = sourceCode.text.slice(replaceRange[0], replaceRange[1]) | ||
replaceDataList.reverse().forEach((data) => { | ||
const textRange = data.range.map(r => r - replaceRange[0]) | ||
text = text.slice(0, textRange[0]) + data.text + text.slice(textRange[1], text.length) | ||
}) | ||
return fixer.replaceTextRange(replaceRange, text) | ||
} | ||
@@ -125,3 +110,3 @@ }) | ||
category: 'recommended', | ||
url: 'https://github.com/vuejs/eslint-plugin-vue/blob/v5.0.0-beta.3/docs/rules/attributes-order.md' | ||
url: 'https://github.com/vuejs/eslint-plugin-vue/blob/v5.0.0-beta.4/docs/rules/attributes-order.md' | ||
}, | ||
@@ -128,0 +113,0 @@ fixable: 'code', |
@@ -112,3 +112,3 @@ /** | ||
category: 'base', | ||
url: 'https://github.com/vuejs/eslint-plugin-vue/blob/v5.0.0-beta.3/docs/rules/comment-directive.md' | ||
url: 'https://github.com/vuejs/eslint-plugin-vue/blob/v5.0.0-beta.4/docs/rules/comment-directive.md' | ||
}, | ||
@@ -115,0 +115,0 @@ schema: [] |
@@ -26,3 +26,3 @@ /** | ||
category: undefined, // strongly-recommended | ||
url: 'https://github.com/vuejs/eslint-plugin-vue/blob/v5.0.0-beta.3/docs/rules/component-name-in-template-casing.md' | ||
url: 'https://github.com/vuejs/eslint-plugin-vue/blob/v5.0.0-beta.4/docs/rules/component-name-in-template-casing.md' | ||
}, | ||
@@ -55,3 +55,2 @@ fixable: 'code', | ||
const tokens = context.parserServices.getTemplateBodyTokenStore && context.parserServices.getTemplateBodyTokenStore() | ||
const sourceCode = context.getSourceCode() | ||
@@ -66,3 +65,7 @@ let hasInvalidEOF = false | ||
if (!utils.isCustomComponent(node)) { | ||
if ( | ||
(!utils.isHtmlElementNode(node) && !utils.isSvgElementNode(node)) || | ||
utils.isHtmlWellKnownElementName(node.rawName) || | ||
utils.isSvgWellKnownElementName(node.rawName) | ||
) { | ||
return | ||
@@ -94,9 +97,6 @@ } | ||
const endTagOpen = tokens.getFirstToken(endTag) | ||
// If we can upgrade requirements to `eslint@>4.1.0`, this code can be replaced by: | ||
// return [ | ||
// fixer.replaceText(open, `<${casingName}`), | ||
// fixer.replaceText(endTagOpen, `</${casingName}`) | ||
// ] | ||
const code = `<${casingName}${sourceCode.text.slice(open.range[1], endTagOpen.range[0])}</${casingName}` | ||
return fixer.replaceTextRange([open.range[0], endTagOpen.range[1]], code) | ||
return [ | ||
fixer.replaceText(open, `<${casingName}`), | ||
fixer.replaceText(endTagOpen, `</${casingName}`) | ||
] | ||
} | ||
@@ -103,0 +103,0 @@ }) |
@@ -35,3 +35,3 @@ /** | ||
category: 'strongly-recommended', | ||
url: 'https://github.com/vuejs/eslint-plugin-vue/blob/v5.0.0-beta.3/docs/rules/html-closing-bracket-newline.md' | ||
url: 'https://github.com/vuejs/eslint-plugin-vue/blob/v5.0.0-beta.4/docs/rules/html-closing-bracket-newline.md' | ||
}, | ||
@@ -38,0 +38,0 @@ fixable: 'whitespace', |
@@ -56,3 +56,3 @@ /** | ||
category: 'strongly-recommended', | ||
url: 'https://github.com/vuejs/eslint-plugin-vue/blob/v5.0.0-beta.3/docs/rules/html-closing-bracket-spacing.md' | ||
url: 'https://github.com/vuejs/eslint-plugin-vue/blob/v5.0.0-beta.4/docs/rules/html-closing-bracket-spacing.md' | ||
}, | ||
@@ -59,0 +59,0 @@ schema: [{ |
@@ -23,3 +23,3 @@ /** | ||
category: 'strongly-recommended', | ||
url: 'https://github.com/vuejs/eslint-plugin-vue/blob/v5.0.0-beta.3/docs/rules/html-end-tags.md' | ||
url: 'https://github.com/vuejs/eslint-plugin-vue/blob/v5.0.0-beta.4/docs/rules/html-end-tags.md' | ||
}, | ||
@@ -26,0 +26,0 @@ fixable: 'code', |
@@ -32,3 +32,3 @@ /** | ||
category: 'strongly-recommended', | ||
url: 'https://github.com/vuejs/eslint-plugin-vue/blob/v5.0.0-beta.3/docs/rules/html-indent.md' | ||
url: 'https://github.com/vuejs/eslint-plugin-vue/blob/v5.0.0-beta.4/docs/rules/html-indent.md' | ||
}, | ||
@@ -35,0 +35,0 @@ fixable: 'whitespace', |
@@ -23,3 +23,3 @@ /** | ||
category: 'recommended', | ||
url: 'https://github.com/vuejs/eslint-plugin-vue/blob/v5.0.0-beta.3/docs/rules/html-quotes.md' | ||
url: 'https://github.com/vuejs/eslint-plugin-vue/blob/v5.0.0-beta.4/docs/rules/html-quotes.md' | ||
}, | ||
@@ -26,0 +26,0 @@ fixable: 'code', |
@@ -91,3 +91,3 @@ /** | ||
category: 'strongly-recommended', | ||
url: 'https://github.com/vuejs/eslint-plugin-vue/blob/v5.0.0-beta.2/docs/rules/html-self-closing.md' | ||
url: 'https://github.com/vuejs/eslint-plugin-vue/blob/v5.0.0-beta.4/docs/rules/html-self-closing.md' | ||
}, | ||
@@ -169,3 +169,7 @@ fixable: 'code', | ||
} | ||
return fixer.replaceText(close, `></${node.rawName}>`) | ||
// If only `close` is targeted for replacement, it conflicts with `component-name-in-template-casing`, | ||
// so replace the entire element. | ||
// return fixer.replaceText(close, `></${node.rawName}>`) | ||
const elementPart = sourceCode.text.slice(node.range[0], close.range[0]) | ||
return fixer.replaceText(node, elementPart + `></${node.rawName}>`) | ||
} | ||
@@ -172,0 +176,0 @@ }) |
@@ -42,3 +42,3 @@ // the following rule is based on yannickcr/eslint-plugin-react | ||
category: 'base', | ||
url: 'https://github.com/vuejs/eslint-plugin-vue/blob/v5.0.0-beta.3/docs/rules/jsx-uses-vars.md' | ||
url: 'https://github.com/vuejs/eslint-plugin-vue/blob/v5.0.0-beta.4/docs/rules/jsx-uses-vars.md' | ||
}, | ||
@@ -45,0 +45,0 @@ schema: [] |
@@ -17,3 +17,3 @@ /** | ||
category: 'strongly-recommended', | ||
url: 'https://github.com/vuejs/eslint-plugin-vue/blob/v5.0.0-beta.3/docs/rules/max-attributes-per-line.md' | ||
url: 'https://github.com/vuejs/eslint-plugin-vue/blob/v5.0.0-beta.4/docs/rules/max-attributes-per-line.md' | ||
}, | ||
@@ -74,2 +74,3 @@ fixable: 'whitespace', // or "code" or "whitespace" | ||
const canHaveFirstLine = configuration.allowFirstLine | ||
const template = context.parserServices.getTemplateBodyTokenStore && context.parserServices.getTemplateBodyTokenStore() | ||
@@ -83,3 +84,3 @@ return utils.defineTemplateBodyVisitor(context, { | ||
if (utils.isSingleLine(node) && numberOfAttributes > singlelinemMaximum) { | ||
showErrors(node.attributes.slice(singlelinemMaximum), node) | ||
showErrors(node.attributes.slice(singlelinemMaximum)) | ||
} | ||
@@ -89,3 +90,3 @@ | ||
if (!canHaveFirstLine && node.attributes[0].loc.start.line === node.loc.start.line) { | ||
showErrors([node.attributes[0]], node) | ||
showErrors([node.attributes[0]]) | ||
} | ||
@@ -95,3 +96,3 @@ | ||
.filter(attrs => attrs.length > multilineMaximum) | ||
.forEach(attrs => showErrors(attrs.splice(multilineMaximum), node)) | ||
.forEach(attrs => showErrors(attrs.splice(multilineMaximum))) | ||
} | ||
@@ -136,12 +137,41 @@ } | ||
function showErrors (attributes, node) { | ||
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) => { | ||
if (i !== 0) return null | ||
// Find the closest token before the current prop | ||
// that is not a white space | ||
const prevToken = template.getTokenBefore(prop, { | ||
filter: (token) => token.type !== 'HTMLWhitespace' | ||
}) | ||
const range = [prevToken.range[1], prop.range[0]] | ||
return fixer.replaceTextRange(range, '\n') | ||
} | ||
context.report({ | ||
node: prop, | ||
loc: prop.loc, | ||
message: 'Attribute "{{propName}}" should be on a new line.', | ||
data: { | ||
propName: prop.key.name | ||
}, | ||
fix: i === 0 ? (fixer) => fixer.insertTextBefore(prop, '\n') : undefined | ||
message: '{{propType}} "{{propName}}" should be on a new line.', | ||
data: getPropData(prop), | ||
fix | ||
}) | ||
@@ -148,0 +178,0 @@ }) |
@@ -55,3 +55,3 @@ /** | ||
category: undefined, | ||
url: 'https://github.com/vuejs/eslint-plugin-vue/blob/v5.0.0-beta.3/docs/rules/multiline-html-element-content-newline.md' | ||
url: 'https://github.com/vuejs/eslint-plugin-vue/blob/v5.0.0-beta.4/docs/rules/multiline-html-element-content-newline.md' | ||
}, | ||
@@ -58,0 +58,0 @@ fixable: 'whitespace', |
@@ -22,3 +22,3 @@ /** | ||
category: 'strongly-recommended', | ||
url: 'https://github.com/vuejs/eslint-plugin-vue/blob/v5.0.0-beta.3/docs/rules/mustache-interpolation-spacing.md' | ||
url: 'https://github.com/vuejs/eslint-plugin-vue/blob/v5.0.0-beta.4/docs/rules/mustache-interpolation-spacing.md' | ||
}, | ||
@@ -25,0 +25,0 @@ fixable: 'whitespace', |
@@ -20,3 +20,3 @@ /** | ||
category: 'strongly-recommended', | ||
url: 'https://github.com/vuejs/eslint-plugin-vue/blob/v5.0.0-beta.3/docs/rules/name-property-casing.md' | ||
url: 'https://github.com/vuejs/eslint-plugin-vue/blob/v5.0.0-beta.4/docs/rules/name-property-casing.md' | ||
}, | ||
@@ -23,0 +23,0 @@ fixable: 'code', // or "code" or "whitespace" |
@@ -67,3 +67,3 @@ /** | ||
category: 'essential', | ||
url: 'https://github.com/vuejs/eslint-plugin-vue/blob/v5.0.0-beta.3/docs/rules/no-async-in-computed-properties.md' | ||
url: 'https://github.com/vuejs/eslint-plugin-vue/blob/v5.0.0-beta.4/docs/rules/no-async-in-computed-properties.md' | ||
}, | ||
@@ -70,0 +70,0 @@ fixable: null, |
@@ -42,3 +42,3 @@ /** | ||
category: 'recommended', | ||
url: 'https://github.com/vuejs/eslint-plugin-vue/blob/v5.0.0-beta.3/docs/rules/no-confusing-v-for-v-if.md', | ||
url: 'https://github.com/vuejs/eslint-plugin-vue/blob/v5.0.0-beta.4/docs/rules/no-confusing-v-for-v-if.md', | ||
replacedBy: ['no-use-v-if-with-v-for'] | ||
@@ -45,0 +45,0 @@ }, |
@@ -20,3 +20,3 @@ /** | ||
category: 'essential', | ||
url: 'https://github.com/vuejs/eslint-plugin-vue/blob/v5.0.0-beta.3/docs/rules/no-dupe-keys.md' | ||
url: 'https://github.com/vuejs/eslint-plugin-vue/blob/v5.0.0-beta.4/docs/rules/no-dupe-keys.md' | ||
}, | ||
@@ -23,0 +23,0 @@ fixable: null, // or "code" or "whitespace" |
@@ -42,3 +42,3 @@ /** | ||
category: 'essential', | ||
url: 'https://github.com/vuejs/eslint-plugin-vue/blob/v5.0.0-beta.3/docs/rules/no-duplicate-attributes.md' | ||
url: 'https://github.com/vuejs/eslint-plugin-vue/blob/v5.0.0-beta.4/docs/rules/no-duplicate-attributes.md' | ||
}, | ||
@@ -45,0 +45,0 @@ fixable: null, |
@@ -11,2 +11,7 @@ /** | ||
const isProperty = (context, node) => { | ||
const sourceCode = context.getSourceCode() | ||
return node.type === 'Punctuator' && sourceCode.getText(node) === ':' | ||
} | ||
module.exports = { | ||
@@ -17,6 +22,14 @@ meta: { | ||
category: 'strongly-recommended', | ||
url: 'https://github.com/vuejs/eslint-plugin-vue/blob/v5.0.0-beta.3/docs/rules/no-multi-spaces.md' | ||
url: 'https://github.com/vuejs/eslint-plugin-vue/blob/v5.0.0-beta.4/docs/rules/no-multi-spaces.md' | ||
}, | ||
fixable: 'whitespace', // or "code" or "whitespace" | ||
schema: [] | ||
schema: [{ | ||
type: 'object', | ||
properties: { | ||
ignoreProperties: { | ||
type: 'boolean' | ||
} | ||
}, | ||
additionalProperties: false | ||
}] | ||
}, | ||
@@ -29,5 +42,4 @@ | ||
create (context) { | ||
// ---------------------------------------------------------------------- | ||
// Public | ||
// ---------------------------------------------------------------------- | ||
const options = context.options[0] || {} | ||
const ignoreProperties = options.ignoreProperties === true | ||
@@ -53,3 +65,6 @@ return { | ||
const spaces = token.range[0] - prevToken.range[1] | ||
if (spaces > 1 && token.loc.start.line === prevToken.loc.start.line) { | ||
const shouldIgnore = ignoreProperties && ( | ||
isProperty(context, token) || isProperty(context, prevToken) | ||
) | ||
if (spaces > 1 && token.loc.start.line === prevToken.loc.start.line && !shouldIgnore) { | ||
context.report({ | ||
@@ -56,0 +71,0 @@ node: token, |
@@ -61,3 +61,3 @@ /** | ||
category: 'essential', | ||
url: 'https://github.com/vuejs/eslint-plugin-vue/blob/v5.0.0-beta.3/docs/rules/no-parsing-error.md' | ||
url: 'https://github.com/vuejs/eslint-plugin-vue/blob/v5.0.0-beta.4/docs/rules/no-parsing-error.md' | ||
}, | ||
@@ -64,0 +64,0 @@ fixable: null, |
@@ -21,3 +21,3 @@ /** | ||
category: 'essential', | ||
url: 'https://github.com/vuejs/eslint-plugin-vue/blob/v5.0.0-beta.3/docs/rules/no-reserved-keys.md' | ||
url: 'https://github.com/vuejs/eslint-plugin-vue/blob/v5.0.0-beta.4/docs/rules/no-reserved-keys.md' | ||
}, | ||
@@ -24,0 +24,0 @@ fixable: null, |
@@ -43,3 +43,3 @@ /** | ||
category: 'essential', | ||
url: 'https://github.com/vuejs/eslint-plugin-vue/blob/v5.0.0-beta.3/docs/rules/no-shared-component-data.md' | ||
url: 'https://github.com/vuejs/eslint-plugin-vue/blob/v5.0.0-beta.4/docs/rules/no-shared-component-data.md' | ||
}, | ||
@@ -70,12 +70,6 @@ fixable: 'code', | ||
// If we can upgrade requirements to `eslint@>4.1.0`, this code can be replaced by: | ||
// return [ | ||
// fixer.insertTextBefore(tokens.first, 'function() {\nreturn '), | ||
// fixer.insertTextAfter(tokens.last, ';\n}') | ||
// ] | ||
// See: https://eslint.org/blog/2017/06/eslint-v4.1.0-released#applying-multiple-autofixes-simultaneously | ||
const range = [tokens.first.range[0], tokens.last.range[1]] | ||
const valueText = sourceCode.text.slice(range[0], range[1]) | ||
const replacement = `function() {\nreturn ${valueText};\n}` | ||
return fixer.replaceTextRange(range, replacement) | ||
return [ | ||
fixer.insertTextBefore(tokens.first, 'function() {\nreturn '), | ||
fixer.insertTextAfter(tokens.last, ';\n}') | ||
] | ||
} | ||
@@ -82,0 +76,0 @@ }) |
@@ -18,3 +18,3 @@ /** | ||
category: 'essential', | ||
url: 'https://github.com/vuejs/eslint-plugin-vue/blob/v5.0.0-beta.3/docs/rules/no-side-effects-in-computed-properties.md' | ||
url: 'https://github.com/vuejs/eslint-plugin-vue/blob/v5.0.0-beta.4/docs/rules/no-side-effects-in-computed-properties.md' | ||
}, | ||
@@ -21,0 +21,0 @@ fixable: null, |
@@ -22,3 +22,3 @@ /** | ||
category: undefined, | ||
url: 'https://github.com/vuejs/eslint-plugin-vue/blob/v5.0.0-beta.3/docs/rules/no-spaces-around-equal-signs-in-attribute.md' | ||
url: 'https://github.com/vuejs/eslint-plugin-vue/blob/v5.0.0-beta.4/docs/rules/no-spaces-around-equal-signs-in-attribute.md' | ||
}, | ||
@@ -25,0 +25,0 @@ fixable: 'whitespace', |
@@ -23,3 +23,3 @@ /** | ||
category: 'essential', | ||
url: 'https://github.com/vuejs/eslint-plugin-vue/blob/v5.0.0-beta.3/docs/rules/no-template-key.md' | ||
url: 'https://github.com/vuejs/eslint-plugin-vue/blob/v5.0.0-beta.4/docs/rules/no-template-key.md' | ||
}, | ||
@@ -26,0 +26,0 @@ fixable: null, |
@@ -24,3 +24,3 @@ /** | ||
category: 'strongly-recommended', | ||
url: 'https://github.com/vuejs/eslint-plugin-vue/blob/v5.0.0-beta.3/docs/rules/no-template-shadow.md' | ||
url: 'https://github.com/vuejs/eslint-plugin-vue/blob/v5.0.0-beta.4/docs/rules/no-template-shadow.md' | ||
}, | ||
@@ -27,0 +27,0 @@ fixable: null, |
@@ -23,3 +23,3 @@ /** | ||
category: 'essential', | ||
url: 'https://github.com/vuejs/eslint-plugin-vue/blob/v5.0.0-beta.3/docs/rules/no-textarea-mustache.md' | ||
url: 'https://github.com/vuejs/eslint-plugin-vue/blob/v5.0.0-beta.4/docs/rules/no-textarea-mustache.md' | ||
}, | ||
@@ -26,0 +26,0 @@ fixable: null, |
@@ -23,11 +23,22 @@ /** | ||
category: 'essential', | ||
url: 'https://github.com/vuejs/eslint-plugin-vue/blob/v5.0.0-beta.3/docs/rules/no-unused-components.md' | ||
url: 'https://github.com/vuejs/eslint-plugin-vue/blob/v5.0.0-beta.4/docs/rules/no-unused-components.md' | ||
}, | ||
fixable: null, | ||
schema: [] | ||
schema: [{ | ||
type: 'object', | ||
properties: { | ||
ignoreWhenBindingPresent: { | ||
type: 'boolean' | ||
} | ||
}, | ||
additionalProperties: false | ||
}] | ||
}, | ||
create (context) { | ||
const usedComponents = [] | ||
const options = context.options[0] || {} | ||
const ignoreWhenBindingPresent = options.ignoreWhenBindingPresent !== undefined ? options.ignoreWhenBindingPresent : true | ||
const usedComponents = new Set() | ||
let registeredComponents = [] | ||
let ignoreReporting = false | ||
let templateLocation | ||
@@ -37,23 +48,24 @@ | ||
VElement (node) { | ||
if (!utils.isCustomComponent(node)) return | ||
let usedComponentName | ||
if (utils.hasAttribute(node, 'is')) { | ||
usedComponentName = utils.findAttribute(node, 'is').value.value | ||
} else if (utils.hasDirective(node, 'bind', 'is')) { | ||
const directiveNode = utils.findDirective(node, 'bind', 'is') | ||
if ( | ||
directiveNode.value.type === 'VExpressionContainer' && | ||
directiveNode.value.expression.type === 'Literal' | ||
) { | ||
usedComponentName = directiveNode.value.expression.value | ||
} | ||
} else { | ||
usedComponentName = node.rawName | ||
if ( | ||
(!utils.isHtmlElementNode(node) && !utils.isSvgElementNode(node)) || | ||
utils.isHtmlWellKnownElementName(node.rawName) || | ||
utils.isSvgWellKnownElementName(node.rawName) | ||
) { | ||
return | ||
} | ||
if (usedComponentName) { | ||
usedComponents.push(usedComponentName) | ||
usedComponents.add(node.rawName) | ||
}, | ||
"VAttribute[directive=true][key.name='bind'][key.argument='is']" (node) { | ||
if (node.value.type !== 'VExpressionContainer') return | ||
if (node.value.expression.type === 'Literal') { | ||
usedComponents.add(node.value.expression.value) | ||
} else if (ignoreWhenBindingPresent) { | ||
ignoreReporting = true | ||
} | ||
}, | ||
"VAttribute[directive=false][key.name='is']" (node) { | ||
usedComponents.add(node.value.value) | ||
}, | ||
"VElement[name='template']" (rootNode) { | ||
@@ -63,13 +75,17 @@ templateLocation = templateLocation || rootNode.loc.start | ||
"VElement[name='template']:exit" (rootNode) { | ||
if (rootNode.loc.start !== templateLocation) return | ||
if ( | ||
rootNode.loc.start !== templateLocation || | ||
ignoreReporting || | ||
utils.hasAttribute(rootNode, 'src') | ||
) return | ||
registeredComponents | ||
.filter(({ name }) => { | ||
// If the component name is PascalCase | ||
// it can be used in varoious of ways inside template, | ||
// If the component name is PascalCase or camelCase | ||
// it can be used in various of ways inside template, | ||
// like "theComponent", "The-component" etc. | ||
// but except snake_case | ||
if (casing.pascalCase(name) === name) { | ||
return !usedComponents.some(n => { | ||
return n.indexOf('_') === -1 && name === casing.pascalCase(n) | ||
if (casing.pascalCase(name) === name || casing.camelCase(name) === name) { | ||
return ![...usedComponents].some(n => { | ||
return n.indexOf('_') === -1 && (name === casing.pascalCase(n) || casing.camelCase(n) === name) | ||
}) | ||
@@ -79,3 +95,3 @@ } else { | ||
// the registered name | ||
return usedComponents.indexOf(name) === -1 | ||
return !usedComponents.has(name) | ||
} | ||
@@ -82,0 +98,0 @@ }) |
@@ -18,3 +18,3 @@ /** | ||
category: 'essential', | ||
url: 'https://github.com/vuejs/eslint-plugin-vue/blob/v5.0.0-beta.3/docs/rules/no-unused-vars.md' | ||
url: 'https://github.com/vuejs/eslint-plugin-vue/blob/v5.0.0-beta.4/docs/rules/no-unused-vars.md' | ||
}, | ||
@@ -21,0 +21,0 @@ fixable: null, |
@@ -55,3 +55,3 @@ /** | ||
category: 'essential', | ||
url: 'https://github.com/vuejs/eslint-plugin-vue/blob/v5.0.0-beta.3/docs/rules/no-use-v-if-with-v-for.md' | ||
url: 'https://github.com/vuejs/eslint-plugin-vue/blob/v5.0.0-beta.4/docs/rules/no-use-v-if-with-v-for.md' | ||
}, | ||
@@ -58,0 +58,0 @@ fixable: null, |
@@ -17,3 +17,3 @@ /** | ||
category: 'recommended', | ||
url: 'https://github.com/vuejs/eslint-plugin-vue/blob/v5.0.0-beta.3/docs/rules/no-v-html.md' | ||
url: 'https://github.com/vuejs/eslint-plugin-vue/blob/v5.0.0-beta.4/docs/rules/no-v-html.md' | ||
}, | ||
@@ -20,0 +20,0 @@ fixable: null, |
@@ -138,3 +138,3 @@ /** | ||
category: 'recommended', | ||
url: 'https://github.com/vuejs/eslint-plugin-vue/blob/v5.0.0-beta.3/docs/rules/order-in-components.md' | ||
url: 'https://github.com/vuejs/eslint-plugin-vue/blob/v5.0.0-beta.4/docs/rules/order-in-components.md' | ||
}, | ||
@@ -197,18 +197,18 @@ fixable: 'code', // null or "code" or "whitespace" | ||
} | ||
const comma = sourceCode.getTokenAfter(propertyNode) | ||
const hasAfterComma = isComma(comma) | ||
const afterComma = sourceCode.getTokenAfter(propertyNode) | ||
const hasAfterComma = isComma(afterComma) | ||
const codeStart = sourceCode.getTokenBefore(propertyNode).range[1] // to include comments | ||
const codeEnd = hasAfterComma ? comma.range[1] : propertyNode.range[1] | ||
const beforeComma = sourceCode.getTokenBefore(propertyNode) | ||
const codeStart = beforeComma.range[1] // to include comments | ||
const codeEnd = hasAfterComma ? afterComma.range[1] : propertyNode.range[1] | ||
const propertyCode = sourceCode.text.slice(codeStart, codeEnd) + (hasAfterComma ? '' : ',') | ||
const insertTarget = sourceCode.getTokenBefore(firstUnorderedPropertyNode) | ||
// If we can upgrade requirements to `eslint@>4.1.0`, this code can be replaced by: | ||
// return [ | ||
// fixer.removeRange([codeStart, codeEnd]), | ||
// fixer.insertTextAfter(insertTarget, propertyCode) | ||
// ] | ||
const insertStart = insertTarget.range[1] | ||
const newCode = propertyCode + sourceCode.text.slice(insertStart, codeStart) | ||
return fixer.replaceTextRange([insertStart, codeEnd], newCode) | ||
const removeStart = hasAfterComma ? codeStart : beforeComma.range[0] | ||
return [ | ||
fixer.removeRange([removeStart, codeEnd]), | ||
fixer.insertTextAfter(insertTarget, propertyCode) | ||
] | ||
} | ||
@@ -215,0 +215,0 @@ }) |
@@ -11,3 +11,3 @@ /** | ||
function canFixPropertyName (node, originalName) { | ||
function canFixPropertyName (node, key, originalName) { | ||
// Can not fix of computed property names & shorthand | ||
@@ -17,3 +17,3 @@ if (node.computed || node.shorthand) { | ||
} | ||
const key = node.key | ||
// Can not fix of unknown types | ||
@@ -41,32 +41,15 @@ if (key.type !== 'Literal' && key.type !== 'Identifier') { | ||
return utils.executeOnVue(context, (obj) => { | ||
const node = obj.properties.find(p => | ||
p.type === 'Property' && | ||
p.key.type === 'Identifier' && | ||
p.key.name === 'props' && | ||
(p.value.type === 'ObjectExpression' || p.value.type === 'ArrayExpression') | ||
) | ||
const props = utils.getComponentProps(obj) | ||
.filter(prop => prop.key && prop.key.type === 'Literal' || (prop.key.type === 'Identifier' && !prop.node.computed)) | ||
if (!node) return | ||
const items = node.value.type === 'ObjectExpression' ? node.value.properties : node.value.elements | ||
for (const item of items) { | ||
if (item.type !== 'Property') { | ||
return | ||
for (const item of props) { | ||
const propName = item.key.type === 'Literal' ? item.key.value : item.key.name | ||
if (typeof propName !== 'string') { | ||
// (boolean | null | number | RegExp) Literal | ||
continue | ||
} | ||
if (item.computed) { | ||
if (item.key.type !== 'Literal') { | ||
// TemplateLiteral | Identifier(variable) | Expression(s) | ||
return | ||
} | ||
if (typeof item.key.value !== 'string') { | ||
// (boolean | null | number | RegExp) Literal | ||
return | ||
} | ||
} | ||
const propName = item.key.type === 'Literal' ? item.key.value : item.key.name | ||
const convertedName = converter(propName) | ||
if (convertedName !== propName) { | ||
context.report({ | ||
node: item, | ||
node: item.node, | ||
message: 'Prop "{{name}}" is not in {{caseType}}.', | ||
@@ -77,3 +60,3 @@ data: { | ||
}, | ||
fix: canFixPropertyName(item, propName) ? fixer => { | ||
fix: canFixPropertyName(item.node, item.key, propName) ? fixer => { | ||
return item.key.type === 'Literal' | ||
@@ -98,3 +81,3 @@ ? fixer.replaceText(item.key, item.key.raw.replace(item.key.value, convertedName)) | ||
category: 'strongly-recommended', | ||
url: 'https://github.com/vuejs/eslint-plugin-vue/blob/v5.0.0-beta.3/docs/rules/prop-name-casing.md' | ||
url: 'https://github.com/vuejs/eslint-plugin-vue/blob/v5.0.0-beta.4/docs/rules/prop-name-casing.md' | ||
}, | ||
@@ -101,0 +84,0 @@ fixable: 'code', // null or "code" or "whitespace" |
@@ -23,3 +23,3 @@ /** | ||
category: 'essential', | ||
url: 'https://github.com/vuejs/eslint-plugin-vue/blob/v5.0.0-beta.3/docs/rules/require-component-is.md' | ||
url: 'https://github.com/vuejs/eslint-plugin-vue/blob/v5.0.0-beta.4/docs/rules/require-component-is.md' | ||
}, | ||
@@ -26,0 +26,0 @@ fixable: null, |
@@ -9,2 +9,12 @@ /** | ||
const NATIVE_TYPES = new Set([ | ||
'String', | ||
'Number', | ||
'Boolean', | ||
'Function', | ||
'Object', | ||
'Array', | ||
'Symbol' | ||
]) | ||
// ------------------------------------------------------------------------------ | ||
@@ -19,3 +29,3 @@ // Rule Definition | ||
category: 'strongly-recommended', | ||
url: 'https://github.com/vuejs/eslint-plugin-vue/blob/v5.0.0-beta.3/docs/rules/require-default-prop.md' | ||
url: 'https://github.com/vuejs/eslint-plugin-vue/blob/v5.0.0-beta.4/docs/rules/require-default-prop.md' | ||
}, | ||
@@ -26,3 +36,3 @@ fixable: null, // or "code" or "whitespace" | ||
create: function (context) { | ||
create (context) { | ||
// ---------------------------------------------------------------------- | ||
@@ -66,11 +76,10 @@ // Helpers | ||
* Finds all props that don't have a default value set | ||
* @param {Property} propsNode - Vue component's "props" node | ||
* @param {Array} props - Vue component's "props" node | ||
* @return {Array} Array of props without "default" value | ||
*/ | ||
function findPropsWithoutDefaultValue (propsNode) { | ||
return propsNode.value.properties | ||
.filter(prop => prop.type === 'Property') | ||
function findPropsWithoutDefaultValue (props) { | ||
return props | ||
.filter(prop => { | ||
if (prop.value.type !== 'ObjectExpression') { | ||
return true | ||
return (prop.value.type !== 'CallExpression' && prop.value.type !== 'Identifier') || NATIVE_TYPES.has(prop.value.name) | ||
} | ||
@@ -105,3 +114,3 @@ | ||
function isBooleanProp (prop) { | ||
const value = prop.value | ||
const value = utils.unwrapTypes(prop.value) | ||
@@ -132,18 +141,11 @@ return isValueNodeOfBooleanType(value) || ( | ||
return utils.executeOnVue(context, (obj) => { | ||
const propsNode = obj.properties | ||
.find(p => | ||
p.type === 'Property' && | ||
p.key.type === 'Identifier' && | ||
p.key.name === 'props' && | ||
p.value.type === 'ObjectExpression' | ||
) | ||
const props = utils.getComponentProps(obj) | ||
.filter(prop => prop.key && prop.value && !prop.node.shorthand) | ||
if (!propsNode) return | ||
const propsWithoutDefault = findPropsWithoutDefaultValue(propsNode) | ||
const propsWithoutDefault = findPropsWithoutDefaultValue(props) | ||
const propsToReport = excludeBooleanProps(propsWithoutDefault) | ||
propsToReport.forEach(prop => { | ||
for (const prop of propsToReport) { | ||
context.report({ | ||
node: prop, | ||
node: prop.node, | ||
message: `Prop '{{propName}}' requires default value to be set.`, | ||
@@ -154,5 +156,5 @@ data: { | ||
}) | ||
}) | ||
} | ||
}) | ||
} | ||
} |
@@ -22,3 +22,3 @@ /** | ||
const isForbiddenType = nodeType => forbiddenTypes.indexOf(nodeType) > -1 | ||
const isForbiddenType = node => forbiddenTypes.indexOf(node.type) > -1 && node.raw !== 'null' | ||
@@ -30,3 +30,3 @@ module.exports = { | ||
category: 'essential', | ||
url: 'https://github.com/vuejs/eslint-plugin-vue/blob/v5.0.0-beta.3/docs/rules/require-prop-type-constructor.md' | ||
url: 'https://github.com/vuejs/eslint-plugin-vue/blob/v5.0.0-beta.4/docs/rules/require-prop-type-constructor.md' | ||
}, | ||
@@ -50,15 +50,15 @@ fixable: 'code', // or "code" or "whitespace" | ||
const checkPropertyNode = (p) => { | ||
if (isForbiddenType(p.value.type)) { | ||
const checkPropertyNode = (key, node) => { | ||
if (isForbiddenType(node)) { | ||
context.report({ | ||
node: p.value, | ||
node: node, | ||
message, | ||
data: { | ||
name: utils.getStaticPropertyName(p.key) | ||
name: utils.getStaticPropertyName(key) | ||
}, | ||
fix: fix(p.value) | ||
fix: fix(node) | ||
}) | ||
} else if (p.value.type === 'ArrayExpression') { | ||
p.value.elements | ||
.filter(prop => isForbiddenType(prop.type)) | ||
} else if (node.type === 'ArrayExpression') { | ||
node.elements | ||
.filter(prop => isForbiddenType(prop)) | ||
.forEach(prop => context.report({ | ||
@@ -68,3 +68,3 @@ node: prop, | ||
data: { | ||
name: utils.getStaticPropertyName(p.key) | ||
name: utils.getStaticPropertyName(key) | ||
}, | ||
@@ -77,27 +77,21 @@ fix: fix(prop) | ||
return utils.executeOnVueComponent(context, (obj) => { | ||
const node = obj.properties.find(p => | ||
p.type === 'Property' && | ||
p.key.type === 'Identifier' && | ||
p.key.name === 'props' && | ||
p.value.type === 'ObjectExpression' | ||
) | ||
const props = utils.getComponentProps(obj) | ||
.filter(prop => prop.key && prop.value) | ||
if (!node) return | ||
node.value.properties.forEach(p => { | ||
if (isForbiddenType(p.value.type) || p.value.type === 'ArrayExpression') { | ||
checkPropertyNode(p) | ||
} else if (p.value.type === 'ObjectExpression') { | ||
const typeProperty = p.value.properties.find(prop => | ||
prop.type === 'Property' && | ||
prop.key.name === 'type' | ||
for (const prop of props) { | ||
if (isForbiddenType(prop.value) || prop.value.type === 'ArrayExpression') { | ||
checkPropertyNode(prop.key, prop.value) | ||
} else if (prop.value.type === 'ObjectExpression') { | ||
const typeProperty = prop.value.properties.find(property => | ||
property.type === 'Property' && | ||
property.key.name === 'type' | ||
) | ||
if (!typeProperty) return | ||
if (!typeProperty) continue | ||
checkPropertyNode(typeProperty) | ||
checkPropertyNode(prop.key, typeProperty.value) | ||
} | ||
}) | ||
} | ||
}) | ||
} | ||
} |
@@ -18,3 +18,3 @@ /** | ||
category: 'strongly-recommended', | ||
url: 'https://github.com/vuejs/eslint-plugin-vue/blob/v5.0.0-beta.3/docs/rules/require-prop-types.md' | ||
url: 'https://github.com/vuejs/eslint-plugin-vue/blob/v5.0.0-beta.4/docs/rules/require-prop-types.md' | ||
}, | ||
@@ -46,25 +46,23 @@ fixable: null, // or "code" or "whitespace" | ||
function checkProperties (items) { | ||
for (const cp of items) { | ||
if (cp.type !== 'Property') { | ||
return | ||
} | ||
let hasType = true | ||
if (cp.value.type === 'ObjectExpression') { // foo: { | ||
hasType = objectHasType(cp.value) | ||
} else if (cp.value.type === 'ArrayExpression') { // foo: [ | ||
hasType = cp.value.elements.length > 0 | ||
} else if (cp.value.type === 'FunctionExpression' || cp.value.type === 'ArrowFunctionExpression') { | ||
hasType = false | ||
} | ||
if (!hasType) { | ||
context.report({ | ||
node: cp, | ||
message: 'Prop "{{name}}" should define at least its type.', | ||
data: { | ||
name: cp.key.name | ||
} | ||
}) | ||
} | ||
function checkProperty (key, value, node) { | ||
let hasType = true | ||
if (!value) { | ||
hasType = false | ||
} else if (value.type === 'ObjectExpression') { // foo: { | ||
hasType = objectHasType(value) | ||
} else if (value.type === 'ArrayExpression') { // foo: [ | ||
hasType = value.elements.length > 0 | ||
} else if (value.type === 'FunctionExpression' || value.type === 'ArrowFunctionExpression') { | ||
hasType = false | ||
} | ||
if (!hasType) { | ||
context.report({ | ||
node, | ||
message: 'Prop "{{name}}" should define at least its type.', | ||
data: { | ||
name: utils.getStaticPropertyName(key || node) || 'Unknown prop' | ||
} | ||
}) | ||
} | ||
} | ||
@@ -77,23 +75,9 @@ | ||
return utils.executeOnVue(context, (obj) => { | ||
const node = obj.properties | ||
.find(p => | ||
p.type === 'Property' && | ||
p.key.type === 'Identifier' && | ||
p.key.name === 'props' | ||
) | ||
const props = utils.getComponentProps(obj) | ||
if (!node) return | ||
if (node.value.type === 'ObjectExpression') { | ||
checkProperties(node.value.properties) | ||
for (const prop of props) { | ||
checkProperty(prop.key, prop.value, prop.node) | ||
} | ||
if (node.value.type === 'ArrayExpression') { | ||
context.report({ | ||
node, | ||
message: 'Props should at least define their types.' | ||
}) | ||
} | ||
}) | ||
} | ||
} |
@@ -18,3 +18,3 @@ /** | ||
category: 'essential', | ||
url: 'https://github.com/vuejs/eslint-plugin-vue/blob/v5.0.0-beta.3/docs/rules/require-render-return.md' | ||
url: 'https://github.com/vuejs/eslint-plugin-vue/blob/v5.0.0-beta.4/docs/rules/require-render-return.md' | ||
}, | ||
@@ -21,0 +21,0 @@ fixable: null, // or "code" or "whitespace" |
@@ -23,3 +23,3 @@ /** | ||
category: 'essential', | ||
url: 'https://github.com/vuejs/eslint-plugin-vue/blob/v5.0.0-beta.3/docs/rules/require-v-for-key.md' | ||
url: 'https://github.com/vuejs/eslint-plugin-vue/blob/v5.0.0-beta.4/docs/rules/require-v-for-key.md' | ||
}, | ||
@@ -26,0 +26,0 @@ fixable: null, |
@@ -27,3 +27,3 @@ /** | ||
category: 'essential', | ||
url: 'https://github.com/vuejs/eslint-plugin-vue/blob/v5.0.0-beta.3/docs/rules/require-valid-default-prop.md' | ||
url: 'https://github.com/vuejs/eslint-plugin-vue/blob/v5.0.0-beta.4/docs/rules/require-valid-default-prop.md' | ||
}, | ||
@@ -92,15 +92,6 @@ fixable: null, | ||
return utils.executeOnVue(context, obj => { | ||
const props = obj.properties.find(p => | ||
isPropertyIdentifier(p) && | ||
p.key.name === 'props' && | ||
p.value.type === 'ObjectExpression' | ||
) | ||
if (!props) return | ||
const props = utils.getComponentProps(obj) | ||
.filter(prop => prop.key && prop.value && prop.value.type === 'ObjectExpression') | ||
const properties = props.value.properties.filter(p => | ||
isPropertyIdentifier(p) && | ||
p.value.type === 'ObjectExpression' | ||
) | ||
for (const prop of properties) { | ||
for (const prop of props) { | ||
const type = getPropertyNode(prop.value, 'type') | ||
@@ -107,0 +98,0 @@ if (!type) continue |
@@ -18,3 +18,3 @@ /** | ||
category: 'essential', | ||
url: 'https://github.com/vuejs/eslint-plugin-vue/blob/v5.0.0-beta.3/docs/rules/return-in-computed-property.md' | ||
url: 'https://github.com/vuejs/eslint-plugin-vue/blob/v5.0.0-beta.4/docs/rules/return-in-computed-property.md' | ||
}, | ||
@@ -21,0 +21,0 @@ fixable: null, // or "code" or "whitespace" |
@@ -22,3 +22,3 @@ /** | ||
category: undefined, | ||
url: 'https://github.com/vuejs/eslint-plugin-vue/blob/v5.0.0-beta.3/docs/rules/script-indent.md' | ||
url: 'https://github.com/vuejs/eslint-plugin-vue/blob/v5.0.0-beta.4/docs/rules/script-indent.md' | ||
}, | ||
@@ -25,0 +25,0 @@ fixable: 'whitespace', |
@@ -50,3 +50,3 @@ /** | ||
category: undefined, | ||
url: 'https://github.com/vuejs/eslint-plugin-vue/blob/v5.0.0-beta.3/docs/rules/singleline-html-element-content-newline.md' | ||
url: 'https://github.com/vuejs/eslint-plugin-vue/blob/v5.0.0-beta.4/docs/rules/singleline-html-element-content-newline.md' | ||
}, | ||
@@ -53,0 +53,0 @@ fixable: 'whitespace', |
@@ -23,3 +23,3 @@ /** | ||
category: 'recommended', | ||
url: 'https://github.com/vuejs/eslint-plugin-vue/blob/v5.0.0-beta.3/docs/rules/this-in-template.md' | ||
url: 'https://github.com/vuejs/eslint-plugin-vue/blob/v5.0.0-beta.4/docs/rules/this-in-template.md' | ||
}, | ||
@@ -26,0 +26,0 @@ fixable: null, |
@@ -23,3 +23,3 @@ /** | ||
category: 'strongly-recommended', | ||
url: 'https://github.com/vuejs/eslint-plugin-vue/blob/v5.0.0-beta.3/docs/rules/v-bind-style.md' | ||
url: 'https://github.com/vuejs/eslint-plugin-vue/blob/v5.0.0-beta.4/docs/rules/v-bind-style.md' | ||
}, | ||
@@ -26,0 +26,0 @@ fixable: 'code', |
@@ -23,3 +23,3 @@ /** | ||
category: 'strongly-recommended', | ||
url: 'https://github.com/vuejs/eslint-plugin-vue/blob/v5.0.0-beta.3/docs/rules/v-on-style.md' | ||
url: 'https://github.com/vuejs/eslint-plugin-vue/blob/v5.0.0-beta.4/docs/rules/v-on-style.md' | ||
}, | ||
@@ -26,0 +26,0 @@ fixable: 'code', |
@@ -23,3 +23,3 @@ /** | ||
category: 'essential', | ||
url: 'https://github.com/vuejs/eslint-plugin-vue/blob/v5.0.0-beta.3/docs/rules/valid-template-root.md' | ||
url: 'https://github.com/vuejs/eslint-plugin-vue/blob/v5.0.0-beta.4/docs/rules/valid-template-root.md' | ||
}, | ||
@@ -26,0 +26,0 @@ fixable: null, |
@@ -29,3 +29,3 @@ /** | ||
category: 'essential', | ||
url: 'https://github.com/vuejs/eslint-plugin-vue/blob/v5.0.0-beta.3/docs/rules/valid-v-bind.md' | ||
url: 'https://github.com/vuejs/eslint-plugin-vue/blob/v5.0.0-beta.4/docs/rules/valid-v-bind.md' | ||
}, | ||
@@ -32,0 +32,0 @@ fixable: null, |
@@ -23,3 +23,3 @@ /** | ||
category: 'essential', | ||
url: 'https://github.com/vuejs/eslint-plugin-vue/blob/v5.0.0-beta.3/docs/rules/valid-v-cloak.md' | ||
url: 'https://github.com/vuejs/eslint-plugin-vue/blob/v5.0.0-beta.4/docs/rules/valid-v-cloak.md' | ||
}, | ||
@@ -26,0 +26,0 @@ fixable: null, |
@@ -23,3 +23,3 @@ /** | ||
category: 'essential', | ||
url: 'https://github.com/vuejs/eslint-plugin-vue/blob/v5.0.0-beta.3/docs/rules/valid-v-else-if.md' | ||
url: 'https://github.com/vuejs/eslint-plugin-vue/blob/v5.0.0-beta.4/docs/rules/valid-v-else-if.md' | ||
}, | ||
@@ -26,0 +26,0 @@ fixable: null, |
@@ -23,3 +23,3 @@ /** | ||
category: 'essential', | ||
url: 'https://github.com/vuejs/eslint-plugin-vue/blob/v5.0.0-beta.3/docs/rules/valid-v-else.md' | ||
url: 'https://github.com/vuejs/eslint-plugin-vue/blob/v5.0.0-beta.4/docs/rules/valid-v-else.md' | ||
}, | ||
@@ -26,0 +26,0 @@ fixable: null, |
@@ -109,3 +109,3 @@ /** | ||
category: 'essential', | ||
url: 'https://github.com/vuejs/eslint-plugin-vue/blob/v5.0.0-beta.3/docs/rules/valid-v-for.md' | ||
url: 'https://github.com/vuejs/eslint-plugin-vue/blob/v5.0.0-beta.4/docs/rules/valid-v-for.md' | ||
}, | ||
@@ -112,0 +112,0 @@ fixable: null, |
@@ -23,3 +23,3 @@ /** | ||
category: 'essential', | ||
url: 'https://github.com/vuejs/eslint-plugin-vue/blob/v5.0.0-beta.3/docs/rules/valid-v-html.md' | ||
url: 'https://github.com/vuejs/eslint-plugin-vue/blob/v5.0.0-beta.4/docs/rules/valid-v-html.md' | ||
}, | ||
@@ -26,0 +26,0 @@ fixable: null, |
@@ -23,3 +23,3 @@ /** | ||
category: 'essential', | ||
url: 'https://github.com/vuejs/eslint-plugin-vue/blob/v5.0.0-beta.3/docs/rules/valid-v-if.md' | ||
url: 'https://github.com/vuejs/eslint-plugin-vue/blob/v5.0.0-beta.4/docs/rules/valid-v-if.md' | ||
}, | ||
@@ -26,0 +26,0 @@ fixable: null, |
@@ -85,3 +85,3 @@ /** | ||
category: 'essential', | ||
url: 'https://github.com/vuejs/eslint-plugin-vue/blob/v5.0.0-beta.3/docs/rules/valid-v-model.md' | ||
url: 'https://github.com/vuejs/eslint-plugin-vue/blob/v5.0.0-beta.4/docs/rules/valid-v-model.md' | ||
}, | ||
@@ -151,3 +151,3 @@ fixable: null, | ||
const id = reference.id | ||
if (id.parent.type === 'MemberExpression' || id.parent.type === 'BinaryExpression') { | ||
if (id.parent.type !== 'VExpressionContainer') { | ||
continue | ||
@@ -161,3 +161,4 @@ } | ||
loc: node.loc, | ||
message: "'v-model' directives cannot update the iteration variable 'x' itself." | ||
message: "'v-model' directives cannot update the iteration variable '{{varName}}' itself.", | ||
data: { varName: id.name } | ||
}) | ||
@@ -164,0 +165,0 @@ } |
@@ -96,3 +96,3 @@ /** | ||
function isValidModifier (modifier) { | ||
function isValidModifier (modifier, customModifiers) { | ||
return ( | ||
@@ -106,3 +106,5 @@ // built-in aliases | ||
// keyAlias (special keys) | ||
KEY_ALIASES.has(modifier) | ||
KEY_ALIASES.has(modifier) || | ||
// custom modifiers | ||
customModifiers.has(modifier) | ||
) | ||
@@ -120,13 +122,27 @@ } | ||
category: 'essential', | ||
url: 'https://github.com/vuejs/eslint-plugin-vue/blob/v5.0.0-beta.3/docs/rules/valid-v-on.md' | ||
url: 'https://github.com/vuejs/eslint-plugin-vue/blob/v5.0.0-beta.4/docs/rules/valid-v-on.md' | ||
}, | ||
fixable: null, | ||
schema: [] | ||
schema: [ | ||
{ | ||
type: 'object', | ||
properties: { | ||
modifiers: { | ||
type: 'array' | ||
} | ||
}, | ||
additionalProperties: false | ||
} | ||
] | ||
}, | ||
create (context) { | ||
const options = context.options[0] || {} | ||
const customModifiers = new Set(options.modifiers || []) | ||
const sourceCode = context.getSourceCode() | ||
return utils.defineTemplateBodyVisitor(context, { | ||
"VAttribute[directive=true][key.name='on']" (node) { | ||
for (const modifier of node.key.modifiers) { | ||
if (!isValidModifier(modifier)) { | ||
if (!isValidModifier(modifier, customModifiers)) { | ||
context.report({ | ||
@@ -140,8 +156,22 @@ node, | ||
} | ||
if (!utils.hasAttributeValue(node) && !node.key.modifiers.some(VERB_MODIFIERS.has, VERB_MODIFIERS)) { | ||
context.report({ | ||
node, | ||
loc: node.loc, | ||
message: "'v-on' directives require that attribute value or verb modifiers." | ||
}) | ||
if ( | ||
!utils.hasAttributeValue(node) && | ||
!node.key.modifiers.some(VERB_MODIFIERS.has, VERB_MODIFIERS) | ||
) { | ||
if (node.value && sourceCode.getText(node.value.expression)) { | ||
const value = sourceCode.getText(node.value) | ||
context.report({ | ||
node, | ||
loc: node.loc, | ||
message: 'Avoid using JavaScript keyword as "v-on" value: {{value}}.', | ||
data: { value } | ||
}) | ||
} else { | ||
context.report({ | ||
node, | ||
loc: node.loc, | ||
message: "'v-on' directives require a value or verb modifier (like 'stop' or 'prevent')." | ||
}) | ||
} | ||
} | ||
@@ -148,0 +178,0 @@ } |
@@ -23,3 +23,3 @@ /** | ||
category: 'essential', | ||
url: 'https://github.com/vuejs/eslint-plugin-vue/blob/v5.0.0-beta.3/docs/rules/valid-v-once.md' | ||
url: 'https://github.com/vuejs/eslint-plugin-vue/blob/v5.0.0-beta.4/docs/rules/valid-v-once.md' | ||
}, | ||
@@ -26,0 +26,0 @@ fixable: null, |
@@ -23,3 +23,3 @@ /** | ||
category: 'essential', | ||
url: 'https://github.com/vuejs/eslint-plugin-vue/blob/v5.0.0-beta.3/docs/rules/valid-v-pre.md' | ||
url: 'https://github.com/vuejs/eslint-plugin-vue/blob/v5.0.0-beta.4/docs/rules/valid-v-pre.md' | ||
}, | ||
@@ -26,0 +26,0 @@ fixable: null, |
@@ -23,3 +23,3 @@ /** | ||
category: 'essential', | ||
url: 'https://github.com/vuejs/eslint-plugin-vue/blob/v5.0.0-beta.3/docs/rules/valid-v-show.md' | ||
url: 'https://github.com/vuejs/eslint-plugin-vue/blob/v5.0.0-beta.4/docs/rules/valid-v-show.md' | ||
}, | ||
@@ -26,0 +26,0 @@ fixable: null, |
@@ -23,3 +23,3 @@ /** | ||
category: 'essential', | ||
url: 'https://github.com/vuejs/eslint-plugin-vue/blob/v5.0.0-beta.3/docs/rules/valid-v-text.md' | ||
url: 'https://github.com/vuejs/eslint-plugin-vue/blob/v5.0.0-beta.4/docs/rules/valid-v-text.md' | ||
}, | ||
@@ -26,0 +26,0 @@ fixable: null, |
@@ -12,3 +12,5 @@ const assert = require('assert') | ||
return str | ||
.replace(/([a-z])([A-Z])/g, match => match[0] + '-' + match[1]) | ||
.replace(/[A-Z]/g, match => '-' + match) | ||
.replace(/([^a-zA-Z])-([A-Z])/g, match => match[0] + match[2]) | ||
.replace(/^-/, '') | ||
.replace(invalidChars, '-') | ||
@@ -25,3 +27,5 @@ .toLowerCase() | ||
return str | ||
.replace(/([a-z])([A-Z])/g, match => match[0] + '_' + match[1]) | ||
.replace(/[A-Z]/g, match => '_' + match) | ||
.replace(/([^a-zA-Z])_([A-Z])/g, match => match[0] + match[2]) | ||
.replace(/^_/, '') | ||
.replace(invalidChars, '_') | ||
@@ -28,0 +32,0 @@ .toLowerCase() |
@@ -21,3 +21,2 @@ /** | ||
const BLOCK_COMMENT_PREFIX = /^\s*\*/ | ||
const TRIVIAL_PUNCTUATOR = /^[(){}[\],;]$/ | ||
@@ -250,17 +249,2 @@ /** | ||
/** | ||
* Check whether a given token is trivial or not. | ||
* @param {Token} token The token to check. | ||
* @returns {boolean} `true` if the token is trivial. | ||
*/ | ||
function isTrivialToken (token) { | ||
return token != null && ( | ||
(token.type === 'Punctuator' && TRIVIAL_PUNCTUATOR.test(token.value)) || | ||
token.type === 'HTMLTagOpen' || | ||
token.type === 'HTMLEndTagOpen' || | ||
token.type === 'HTMLTagClose' || | ||
token.type === 'HTMLSelfClosingTagClose' | ||
) | ||
} | ||
/** | ||
* Creates AST event handlers for html-indent. | ||
@@ -569,7 +553,6 @@ * | ||
* @param {Token[]} tokens Tokens which are on the same line. | ||
* @returns {number} Correct indentation. If it failed to calculate then `Number.MAX_SAFE_INTEGER`. | ||
* @returns {object|null} Correct indentation. If it failed to calculate then `null`. | ||
*/ | ||
function getExpectedIndent (tokens) { | ||
const trivial = isTrivialToken(tokens[0]) | ||
let expectedIndent = Number.MAX_SAFE_INTEGER | ||
function getExpectedIndents (tokens) { | ||
const expectedIndents = [] | ||
@@ -580,10 +563,9 @@ for (let i = 0; i < tokens.length; ++i) { | ||
// If the first token is not trivial then ignore trivial following tokens. | ||
if (offsetInfo != null && (trivial || !isTrivialToken(token))) { | ||
if (offsetInfo != null) { | ||
if (offsetInfo.expectedIndent != null) { | ||
expectedIndent = Math.min(expectedIndent, offsetInfo.expectedIndent) | ||
expectedIndents.push(offsetInfo.expectedIndent) | ||
} else { | ||
const baseOffsetInfo = offsets.get(offsetInfo.baseToken) | ||
if (baseOffsetInfo != null && baseOffsetInfo.expectedIndent != null && (i === 0 || !baseOffsetInfo.baseline)) { | ||
expectedIndent = Math.min(expectedIndent, baseOffsetInfo.expectedIndent + offsetInfo.offset * options.indentSize) | ||
expectedIndents.push(baseOffsetInfo.expectedIndent + offsetInfo.offset * options.indentSize) | ||
if (baseOffsetInfo.baseline) { | ||
@@ -596,4 +578,10 @@ break | ||
} | ||
if (!expectedIndents.length) { | ||
return null | ||
} | ||
return expectedIndent | ||
return { | ||
expectedIndent: expectedIndents[0], | ||
expectedBaseIndent: expectedIndents.reduce((a, b) => Math.min(a, b)) | ||
} | ||
} | ||
@@ -754,7 +742,10 @@ | ||
const actualIndent = firstToken.loc.start.column | ||
const expectedIndent = getExpectedIndent(tokens) | ||
if (expectedIndent === Number.MAX_SAFE_INTEGER) { | ||
const expectedIndents = getExpectedIndents(tokens) | ||
if (!expectedIndents) { | ||
return | ||
} | ||
const expectedBaseIndent = expectedIndents.expectedBaseIndent | ||
const expectedIndent = expectedIndents.expectedIndent | ||
// Debug log | ||
@@ -782,7 +773,7 @@ // console.log('line', firstToken.loc.start.line, '=', { actualIndent, expectedIndent }, 'from:') | ||
if (options.indentChar === ' ') { | ||
offsetInfo.expectedIndent = Math.max(0, token.loc.start.column + expectedIndent - actualIndent) | ||
offsetInfo.expectedIndent = Math.max(0, token.loc.start.column + expectedBaseIndent - actualIndent) | ||
} else { | ||
// In hard-tabs mode, it cannot align tokens strictly, so use one additional offset. | ||
// But the additional offset isn't needed if it's at the beginning of the line. | ||
offsetInfo.expectedIndent = expectedIndent + (token === tokens[0] ? 0 : 1) | ||
offsetInfo.expectedIndent = expectedBaseIndent + (token === tokens[0] ? 0 : 1) | ||
} | ||
@@ -796,3 +787,3 @@ baseline.add(token) | ||
// Otherwise, set the expected indent of this line. | ||
offsetInfo.expectedIndent = expectedIndent | ||
offsetInfo.expectedIndent = expectedBaseIndent | ||
} | ||
@@ -799,0 +790,0 @@ } |
@@ -13,2 +13,3 @@ /** | ||
const HTML_ELEMENT_NAMES = new Set(require('./html-elements.json')) | ||
const SVG_ELEMENT_NAMES = new Set(require('./svg-elements.json')) | ||
const VOID_ELEMENT_NAMES = new Set(require('./void-elements.json')) | ||
@@ -237,3 +238,3 @@ const assert = require('assert') | ||
return ( | ||
(this.isHtmlElementNode(node) && !this.isHtmlWellKnownElementName(node.name)) || | ||
(this.isHtmlElementNode(node) && !this.isHtmlWellKnownElementName(node.rawName)) || | ||
this.hasAttribute(node, 'is') || | ||
@@ -285,6 +286,16 @@ this.hasDirective(node, 'bind', 'is') | ||
return HTML_ELEMENT_NAMES.has(name.toLowerCase()) | ||
return HTML_ELEMENT_NAMES.has(name) | ||
}, | ||
/** | ||
* Check whether the given name is an well-known SVG element or not. | ||
* @param {string} name The name to check. | ||
* @returns {boolean} `true` if the name is an well-known SVG element name. | ||
*/ | ||
isSvgWellKnownElementName (name) { | ||
assert(typeof name === 'string') | ||
return SVG_ELEMENT_NAMES.has(name) | ||
}, | ||
/** | ||
* Check whether the given name is a void element name or not. | ||
@@ -297,6 +308,26 @@ * @param {string} name The name to check. | ||
return VOID_ELEMENT_NAMES.has(name.toLowerCase()) | ||
return VOID_ELEMENT_NAMES.has(name) | ||
}, | ||
/** | ||
* Check whether the given attribute node is a binding | ||
* @param {ASTNode} name The attribute to check. | ||
* @returns {boolean} | ||
*/ | ||
isBindingAttribute (attribute) { | ||
return attribute.directive && | ||
attribute.key.name === 'bind' && | ||
attribute.key.argument | ||
}, | ||
/** | ||
* Check whether the given attribute node is an event | ||
* @param {ASTNode} name The attribute to check. | ||
* @returns {boolean} | ||
*/ | ||
isEventAttribute (attribute) { | ||
return attribute.directive && attribute.key.name === 'on' | ||
}, | ||
/** | ||
* Parse member expression node to get array with all of its parts | ||
@@ -373,4 +404,41 @@ * @param {ASTNode} MemberExpression | ||
/** | ||
* Get all props by looking at all component's properties | ||
* @param {ObjectExpression} componentObject Object with component definition | ||
* @return {Array} Array of component props in format: [{key?: String, value?: ASTNode, node: ASTNod}] | ||
*/ | ||
getComponentProps (componentObject) { | ||
const propsNode = componentObject.properties | ||
.find(p => | ||
p.type === 'Property' && | ||
p.key.type === 'Identifier' && | ||
p.key.name === 'props' && | ||
(p.value.type === 'ObjectExpression' || p.value.type === 'ArrayExpression') | ||
) | ||
if (!propsNode) { | ||
return [] | ||
} | ||
let props | ||
if (propsNode.value.type === 'ObjectExpression') { | ||
props = propsNode.value.properties | ||
.filter(prop => prop.type === 'Property') | ||
.map(prop => { | ||
return { key: prop.key, value: this.unwrapTypes(prop.value), node: prop } | ||
}) | ||
} else { | ||
props = propsNode.value.elements | ||
.map(prop => { | ||
const key = prop.type === 'Literal' && typeof prop.value === 'string' ? prop : null | ||
return { key, value: null, node: prop } | ||
}) | ||
} | ||
return props | ||
}, | ||
/** | ||
* Get all computed properties by looking at all component's properties | ||
* @param {ObjectExpression} Object with component definition | ||
* @param {ObjectExpression} componentObject Object with component definition | ||
* @return {Array} Array of computed properties in format: [{key: String, value: ASTNode}] | ||
@@ -434,20 +502,28 @@ */ | ||
isVueComponent (node) { | ||
const callee = node.callee | ||
if (node.type === 'CallExpression') { | ||
const callee = node.callee | ||
const isFullVueComponent = node.type === 'CallExpression' && | ||
callee.type === 'MemberExpression' && | ||
callee.object.type === 'Identifier' && | ||
callee.object.name === 'Vue' && | ||
callee.property.type === 'Identifier' && | ||
['component', 'mixin', 'extend'].indexOf(callee.property.name) > -1 && | ||
node.arguments.length >= 1 && | ||
node.arguments.slice(-1)[0].type === 'ObjectExpression' | ||
if (callee.type === 'MemberExpression') { | ||
const calleeObject = this.unwrapTypes(callee.object) | ||
const isDestructedVueComponent = node.type === 'CallExpression' && | ||
callee.type === 'Identifier' && | ||
callee.name === 'component' && | ||
node.arguments.length >= 1 && | ||
node.arguments.slice(-1)[0].type === 'ObjectExpression' | ||
const isFullVueComponent = calleeObject.type === 'Identifier' && | ||
calleeObject.name === 'Vue' && | ||
callee.property.type === 'Identifier' && | ||
['component', 'mixin', 'extend'].indexOf(callee.property.name) > -1 && | ||
node.arguments.length >= 1 && | ||
node.arguments.slice(-1)[0].type === 'ObjectExpression' | ||
return isFullVueComponent || isDestructedVueComponent | ||
return isFullVueComponent | ||
} | ||
if (callee.type === 'Identifier') { | ||
const isDestructedVueComponent = callee.name === 'component' && | ||
node.arguments.length >= 1 && | ||
node.arguments.slice(-1)[0].type === 'ObjectExpression' | ||
return isDestructedVueComponent | ||
} | ||
} | ||
return false | ||
}, | ||
@@ -680,3 +756,3 @@ | ||
* | ||
* @param {Object} node The node to parse (MemberExpression | CallExpression) | ||
* @param {ASTNode} node The node to parse (MemberExpression | CallExpression) | ||
* @return {String} eg. 'this.asd.qwe().map().filter().test.reduce()' | ||
@@ -713,3 +789,12 @@ */ | ||
return parsedCallee.reverse().join('.').replace(/\.\[/g, '[') | ||
}, | ||
/** | ||
* Unwrap typescript types like "X as F" | ||
* @param {ASTNode} node | ||
* @return {ASTNode} | ||
*/ | ||
unwrapTypes (node) { | ||
return node.type === 'TSAsExpression' ? node.expression : node | ||
} | ||
} |
{ | ||
"name": "eslint-plugin-vue", | ||
"version": "5.0.0-beta.3", | ||
"version": "5.0.0-beta.4", | ||
"description": "Official ESLint plugin for Vue.js", | ||
@@ -55,8 +55,9 @@ "main": "lib/index.js", | ||
"eslint-plugin-eslint-plugin": "^1.4.0", | ||
"eslint-plugin-html": "^4.0.1", | ||
"eslint-plugin-vue-libs": "^3.0.0", | ||
"lodash": "^4.17.4", | ||
"mocha": "^5.2.0", | ||
"nyc": "^12.0.2" | ||
"nyc": "^12.0.2", | ||
"typescript": "^3.1.3", | ||
"typescript-eslint-parser": "^20.0.0" | ||
} | ||
} |
@@ -5,3 +5,4 @@ # eslint-plugin-vue | ||
[![NPM downloads](https://img.shields.io/npm/dm/eslint-plugin-vue.svg?style=flat)](https://npmjs.org/package/eslint-plugin-vue) | ||
[![CircleCI](https://circleci.com/gh/vuejs/eslint-plugin-vue.svg?style=svg)](https://circleci.com/gh/vuejs/eslint-plugin-vue) | ||
[![CircleCI](https://img.shields.io/circleci/project/github/vuejs/eslint-plugin-vue/master.svg?style=flat)](https://circleci.com/gh/vuejs/eslint-plugin-vue) | ||
[![License](https://img.shields.io/github/license/vuejs/eslint-plugin-vue.svg?style=flat)](https://github.com/vuejs/eslint-plugin-vue/blob/master/LICENSE.md) | ||
@@ -240,2 +241,3 @@ > Official ESLint plugin for Vue.js | ||
| :wrench: | [vue/singleline-html-element-content-newline](./docs/rules/singleline-html-element-content-newline.md) | require a line break before and after the contents of a singleline element | | ||
| | [vue/use-v-on-exact](./docs/rules/use-v-on-exact.md) | enforce usage of `exact` modifier on `v-on` | | ||
@@ -289,4 +291,16 @@ ### Deprecated | ||
2. Make sure your tool is set to lint `.vue` files. | ||
- CLI targets only `.js` files by default. You have to specify additional extensions by `--ext` option or glob patterns. E.g. `eslint "src/**/*.{js,vue}"` or `eslint src --ext .vue`. | ||
- CLI targets only `.js` files by default. You have to specify additional extensions by `--ext` option or glob patterns. E.g. `eslint "src/**/*.{js,vue}"` or `eslint src --ext .vue`. If you use `@vue/cli-plugin-eslint` and the `vue-cli-service lint` command - you don't have to worry about it. | ||
- VSCode targets only JavaScript or HTML files by default. You have to add `"vue"` to the `"eslint.validate"` array in vscode settings. e.g. `"eslint.validate": [ "javascript", "javascriptreact", "vue" ]` | ||
- If you use `Vetur` plugin in VSCode - set `"vetur.validation.template": false` to avoid default Vetur template validation. Check out [vetur documentation](https://github.com/vuejs/vetur/blob/master/docs/linting-error.md) for more info. | ||
- For Atom editor, you need to go into Settings -> Packages -> linter-eslint, under the option “List of scopes to run eslint on”, add `text.html.vue`. | ||
- For Sublime Text, you need to open command-pallete via cmd+shift+p (cmd => ctrl for windows) and type "Preferences: SublimeLinter Settings", paste to the config on the right side: | ||
```json | ||
{ | ||
"linters": { | ||
"eslint": { | ||
"selector": "source.js, text.html.vue" | ||
} | ||
} | ||
} | ||
``` | ||
@@ -293,0 +307,0 @@ ## :anchor: Semantic Versioning Policy |
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
289660
79
7484
353
11