From 3643c9affa7c82061fdf93177d5d36be32a25fa5 Mon Sep 17 00:00:00 2001 From: Yosuke Ota Date: Tue, 12 Nov 2019 22:11:32 +0900 Subject: [PATCH 0001/1050] Upgrade vue-eslint-parser@^6.0.5 (#984) --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 188853211..33ba17f52 100644 --- a/package.json +++ b/package.json @@ -46,7 +46,7 @@ "eslint": "^5.0.0 || ^6.0.0" }, "dependencies": { - "vue-eslint-parser": "^6.0.4" + "vue-eslint-parser": "^6.0.5" }, "devDependencies": { "@types/node": "^4.2.16", From fc7afd7e9f492826a9a5b3255fd6d0404717dfb2 Mon Sep 17 00:00:00 2001 From: michalsnik Date: Wed, 13 Nov 2019 16:24:09 +0700 Subject: [PATCH 0002/1050] 6.0.1 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 33ba17f52..7dabf02aa 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "eslint-plugin-vue", - "version": "6.0.0", + "version": "6.0.1", "description": "Official ESLint plugin for Vue.js", "main": "lib/index.js", "scripts": { From e8f130c20c0ba6213009e1230a294b0f091e57bb Mon Sep 17 00:00:00 2001 From: Andrew Date: Tue, 19 Nov 2019 02:55:58 +0200 Subject: [PATCH 0003/1050] Typescript doc for vue/html-indent (#989) https://github.com/vuejs/eslint-plugin-vue/issues/987 --- docs/rules/script-indent.md | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/rules/script-indent.md b/docs/rules/script-indent.md index 59934faec..c28120b7d 100644 --- a/docs/rules/script-indent.md +++ b/docs/rules/script-indent.md @@ -115,6 +115,7 @@ This rule only checks `.vue` files and does not interfere with other `.js` files - [indent](https://eslint.org/docs/rules/indent) - [vue/html-indent](./html-indent.md) +- [@typescript-eslint/indent](https://github.com/typescript-eslint/typescript-eslint/blob/master/packages/eslint-plugin/docs/rules/indent.md). The `vue/script-indent` rule does not understand TypeScript AST. Please use `@typescript-eslint/indent` rule instead of this one inside of ` +``` + + + +## :wrench: Options + +Nothing. + +## :mag: Implementation + +- [Rule source](https://github.com/vuejs/eslint-plugin-vue/blob/master/lib/rules/require-name-property.js) +- [Test source](https://github.com/vuejs/eslint-plugin-vue/blob/master/tests/lib/rules/require-name-property.js) diff --git a/lib/rules/require-name-property.js b/lib/rules/require-name-property.js new file mode 100644 index 000000000..126c1bfe6 --- /dev/null +++ b/lib/rules/require-name-property.js @@ -0,0 +1,37 @@ +/** + * @fileoverview Require a name property in Vue components + * @author LukeeeeBennett + */ +'use strict' + +const utils = require('../utils') + +function isNameProperty (node) { + return node.type === 'Property' && + node.key.name === 'name' && + !node.computed +} + +module.exports = { + meta: { + type: 'suggestion', + docs: { + description: 'require a name property in Vue components', + category: undefined, + url: 'https://eslint.vuejs.org/rules/require-name-property.html' + }, + fixable: null, + schema: [] + }, + + create (context) { + return utils.executeOnVue(context, component => { + if (component.properties.some(isNameProperty)) return + + context.report({ + node: component, + message: 'Required name property is not set.' + }) + }) + } +} diff --git a/tests/lib/rules/require-name-property.js b/tests/lib/rules/require-name-property.js new file mode 100644 index 000000000..7b55e2400 --- /dev/null +++ b/tests/lib/rules/require-name-property.js @@ -0,0 +1,102 @@ +/** + * @fileoverview Require a name property in Vue components + * @author LukeeeeBennett + */ +'use strict' + +const rule = require('../../../lib/rules/require-name-property') +const RuleTester = require('eslint').RuleTester + +const parserOptions = { + ecmaVersion: 2018, + sourceType: 'module' +} + +const ruleTester = new RuleTester() +ruleTester.run('require-name-property', rule, { + valid: [ + { + filename: 'ValidComponent.vue', + code: ` + export default { + name: 'IssaName' + } + `, + parserOptions + }, + { + filename: 'ValidComponent.vue', + code: ` + export default { + name: undefined + } + `, + parserOptions + }, + { + filename: 'ValidComponent.vue', + code: ` + export default { + name: '' + } + `, + parserOptions + } + ], + + invalid: [ + { + filename: 'InvalidComponent.vue', + code: ` + export default { + } + `, + parserOptions, + errors: [{ + message: 'Required name property is not set.', + type: 'ObjectExpression' + }] + }, + { + filename: 'InvalidComponent.vue', + code: ` + export default { + nameNot: 'IssaNameNot' + } + `, + parserOptions, + errors: [{ + message: 'Required name property is not set.', + type: 'ObjectExpression' + }] + }, + { + filename: 'InvalidComponent.vue', + code: ` + export default { + computed: { + name() { return 'name' } + } + } + `, + parserOptions, + errors: [{ + message: 'Required name property is not set.', + type: 'ObjectExpression' + }] + }, + { + filename: 'InvalidComponent.vue', + code: ` + export default { + [name]: 'IssaName' + } + `, + parserOptions, + errors: [{ + message: 'Required name property is not set.', + type: 'ObjectExpression' + }] + } + ] +}) From b19843c698110d9dfb08fe8dffdbd0aaffbcce36 Mon Sep 17 00:00:00 2001 From: Jake Date: Wed, 25 Dec 2019 22:27:37 -0500 Subject: [PATCH 0013/1050] =?UTF-8?q?=20=E2=AD=90=EF=B8=8F=20New:=20Add=20?= =?UTF-8?q?rule=20`no-reserved-component-names`=20(#757)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * :star: add rule no-reserved-component-names * Increase test coverage * Checking elements against element lists * :hammer: Update PR with ota-meshi suggestions * :hammer: Lint locally registered components * :ok_hand: Adding tests to validate slot and template * :hammer: Linting for deprecated elements --- docs/rules/no-reserved-component-names.md | 28 ++ lib/rules/no-reserved-component-names.js | 125 ++++++ lib/utils/deprecated-html-elements.json | 1 + .../lib/rules/no-reserved-component-names.js | 375 ++++++++++++++++++ 4 files changed, 529 insertions(+) create mode 100644 docs/rules/no-reserved-component-names.md create mode 100644 lib/rules/no-reserved-component-names.js create mode 100644 lib/utils/deprecated-html-elements.json create mode 100644 tests/lib/rules/no-reserved-component-names.js diff --git a/docs/rules/no-reserved-component-names.md b/docs/rules/no-reserved-component-names.md new file mode 100644 index 000000000..57a008592 --- /dev/null +++ b/docs/rules/no-reserved-component-names.md @@ -0,0 +1,28 @@ +# vue/no-reserved-component-names +> disallow the use of reserved names in component definitions + +- :gear: This rule is included in all of `"plugin:vue/essential"`, `"plugin:vue/recommended"`, and `"plugin:vue/strongly-recommended"`. + +## :book: Rule Details + +This rule prevents name collisions between vue components and standard html elements. + + + +```vue + +``` + + + +## :books: Further reading + +- [List of html elements](https://developer.mozilla.org/en-US/docs/Web/HTML/Element) +- [List of SVG elements](https://developer.mozilla.org/en-US/docs/Web/SVG/Element) +- [Kebab case elements](https://stackoverflow.com/questions/22545621/do-custom-elements-require-a-dash-in-their-name/22545622#22545622) +- [Valid custom element name](https://w3c.github.io/webcomponents/spec/custom/#valid-custom-element-name) \ No newline at end of file diff --git a/lib/rules/no-reserved-component-names.js b/lib/rules/no-reserved-component-names.js new file mode 100644 index 000000000..c08b34f5d --- /dev/null +++ b/lib/rules/no-reserved-component-names.js @@ -0,0 +1,125 @@ +/** + * @fileoverview disallow the use of reserved names in component definitions + * @author Jake Hassel + */ +'use strict' + +const utils = require('../utils') +const casing = require('../utils/casing') + +const htmlElements = require('../utils/html-elements.json') +const deprecatedHtmlElements = require('../utils/deprecated-html-elements.json') +const svgElements = require('../utils/svg-elements.json') + +const kebabCaseElements = [ + 'annotation-xml', + 'color-profile', + 'font-face', + 'font-face-src', + 'font-face-uri', + 'font-face-format', + 'font-face-name', + 'missing-glyph' +] + +const isLowercase = (word) => /^[a-z]*$/.test(word) +const capitalizeFirstLetter = (word) => word[0].toUpperCase() + word.substring(1, word.length) + +const RESERVED_NAMES = new Set( + [ + ...kebabCaseElements, + ...kebabCaseElements.map(casing.pascalCase), + ...htmlElements, + ...htmlElements.map(capitalizeFirstLetter), + ...deprecatedHtmlElements, + ...deprecatedHtmlElements.map(capitalizeFirstLetter), + ...svgElements, + ...svgElements.filter(isLowercase).map(capitalizeFirstLetter) + ]) + +// ------------------------------------------------------------------------------ +// Rule Definition +// ------------------------------------------------------------------------------ + +module.exports = { + meta: { + type: 'suggestion', + docs: { + description: 'disallow the use of reserved names in component definitions', + category: undefined, // 'essential' + url: 'https://eslint.vuejs.org/rules/no-reserved-component-names.html' + }, + fixable: null, + schema: [] + }, + + create (context) { + function canVerify (node) { + return node.type === 'Literal' || ( + node.type === 'TemplateLiteral' && + node.expressions.length === 0 && + node.quasis.length === 1 + ) + } + + function reportIfInvalid (node) { + let name + if (node.type === 'TemplateLiteral') { + const quasis = node.quasis[0] + name = quasis.value.cooked + } else { + name = node.value + } + if (RESERVED_NAMES.has(name)) { + report(node, name) + } + } + + function report (node, name) { + context.report({ + node: node, + message: 'Name "{{name}}" is reserved.', + data: { + name: name + } + }) + } + + return Object.assign({}, + { + "CallExpression > MemberExpression > Identifier[name='component']" (node) { + const parent = node.parent.parent + const calleeObject = utils.unwrapTypes(parent.callee.object) + + if (calleeObject.type === 'Identifier' && + calleeObject.name === 'Vue' && + parent.arguments && + parent.arguments.length === 2 + ) { + const argument = parent.arguments[0] + + if (canVerify(argument)) { + reportIfInvalid(argument) + } + } + } + }, + utils.executeOnVue(context, (obj) => { + // Report if a component has been registered locally with a reserved name. + utils.getRegisteredComponents(obj) + .filter(({ name }) => RESERVED_NAMES.has(name)) + .forEach(({ node, name }) => report(node, name)) + + const node = obj.properties + .find(item => ( + item.type === 'Property' && + item.key.name === 'name' && + canVerify(item.value) + )) + + if (!node) return + reportIfInvalid(node.value) + }) + ) + } +} diff --git a/lib/utils/deprecated-html-elements.json b/lib/utils/deprecated-html-elements.json new file mode 100644 index 000000000..daf23f512 --- /dev/null +++ b/lib/utils/deprecated-html-elements.json @@ -0,0 +1 @@ +["acronym","applet","basefont","bgsound","big","blink","center","command","content","dir","element","font","frame","frameset","image","isindex","keygen","listing","marquee","menuitem","multicol","nextid","nobr","noembed","noframes","plaintext","shadow","spacer","strike","tt","xmp"] \ No newline at end of file diff --git a/tests/lib/rules/no-reserved-component-names.js b/tests/lib/rules/no-reserved-component-names.js new file mode 100644 index 000000000..6d592396d --- /dev/null +++ b/tests/lib/rules/no-reserved-component-names.js @@ -0,0 +1,375 @@ +/** + * @fileoverview disallow the use of reserved names in component definitions + * @author Jake Hassel + */ +'use strict' + +// ------------------------------------------------------------------------------ +// Requirements +// ------------------------------------------------------------------------------ + +const rule = require('../../../lib/rules/no-reserved-component-names') +const RuleTester = require('eslint').RuleTester + +// ------------------------------------------------------------------------------ +// Tests +// ------------------------------------------------------------------------------ + +const invalidElements = [ + 'annotation-xml', 'AnnotationXml', + 'color-profile', 'ColorProfile', + 'font-face', 'FontFace', + 'font-face-src', 'FontFaceSrc', + 'font-face-uri', 'FontFaceUri', + 'font-face-format', 'FontFaceFormat', + 'font-face-name', 'FontFaceName', + 'missing-glyph', 'MissingGlyph', + 'html', 'Html', + 'body', 'Body', + 'base', 'Base', + 'head', 'Head', + 'link', 'Link', + 'meta', 'Meta', + 'style', 'Style', + 'title', 'Title', + 'address', 'Address', + 'article', 'Article', + 'aside', 'Aside', + 'footer', 'Footer', + 'header', 'Header', + 'h1', 'H1', + 'h2', 'H2', + 'h3', 'H3', + 'h4', 'H4', + 'h5', 'H5', + 'h6', 'H6', + 'hgroup', 'Hgroup', + 'nav', 'Nav', + 'section', 'Section', + 'div', 'Div', + 'dd', 'Dd', + 'dl', 'Dl', + 'dt', 'Dt', + 'figcaption', 'Figcaption', + 'figure', 'Figure', + 'hr', 'Hr', + 'img', 'Img', + 'li', 'Li', + 'main', 'Main', + 'ol', 'Ol', + 'p', 'P', + 'pre', 'Pre', + 'ul', 'Ul', + 'a', 'A', + 'b', 'B', + 'abbr', 'Abbr', + 'bdi', 'Bdi', + 'bdo', 'Bdo', + 'br', 'Br', + 'cite', 'Cite', + 'code', 'Code', + 'data', 'Data', + 'dfn', 'Dfn', + 'em', 'Em', + 'i', 'I', + 'kbd', 'Kbd', + 'mark', 'Mark', + 'q', 'Q', + 'rp', 'Rp', + 'rt', 'Rt', + 'rtc', 'Rtc', + 'ruby', 'Ruby', + 's', 'S', + 'samp', 'Samp', + 'small', 'Small', + 'span', 'Span', + 'strong', 'Strong', + 'sub', 'Sub', + 'sup', 'Sup', + 'time', 'Time', + 'u', 'U', + 'var', 'Var', + 'wbr', 'Wbr', + 'area', 'Area', + 'audio', 'Audio', + 'map', 'Map', + 'track', 'Track', + 'video', 'Video', + 'embed', 'Embed', + 'object', 'Object', + 'param', 'Param', + 'source', 'Source', + 'canvas', 'Canvas', + 'script', 'Script', + 'noscript', 'Noscript', + 'del', 'Del', + 'ins', 'Ins', + 'caption', 'Caption', + 'col', 'Col', + 'colgroup', 'Colgroup', + 'table', 'Table', + 'thead', 'Thead', + 'tbody', 'Tbody', + 'tfoot', 'Tfoot', + 'td', 'Td', + 'th', 'Th', + 'tr', 'Tr', + 'button', 'Button', + 'datalist', 'Datalist', + 'fieldset', 'Fieldset', + 'form', 'Form', + 'input', 'Input', + 'label', 'Label', + 'legend', 'Legend', + 'meter', 'Meter', + 'optgroup', 'Optgroup', + 'option', 'Option', + 'output', 'Output', + 'progress', 'Progress', + 'select', 'Select', + 'textarea', 'Textarea', + 'details', 'Details', + 'dialog', 'Dialog', + 'menu', 'Menu', + 'menuitem', 'menuitem', + 'summary', 'Summary', + 'content', 'Content', + 'element', 'Element', + 'shadow', 'Shadow', + 'template', 'Template', + 'slot', 'Slot', + 'blockquote', 'Blockquote', + 'iframe', 'Iframe', + 'noframes', 'Noframes', + 'picture', 'Picture', + + // SVG elements + 'animate', 'Animate', + 'animateMotion', + 'animateTransform', + 'circle', 'Circle', + 'clipPath', + 'defs', 'Defs', + 'desc', 'Desc', + 'discard', 'Discard', + 'ellipse', 'Ellipse', + 'feBlend', + 'feColorMatrix', + 'feComponentTransfer', + 'feComposite', + 'feConvolveMatrix', + 'feDiffuseLighting', + 'feDisplacementMap', + 'feDistantLight', + 'feDropShadow', + 'feFlood', + 'feFuncA', + 'feFuncB', + 'feFuncG', + 'feFuncR', + 'feGaussianBlur', + 'feImage', + 'feMerge', + 'feMergeNode', + 'feMorphology', + 'feOffset', + 'fePointLight', + 'feSpecularLighting', + 'feSpotLight', + 'feTile', + 'feTurbulence', + 'filter', 'Filter', + 'foreignObject', + 'g', 'G', + 'image', 'Image', + 'line', 'Line', + 'linearGradient', + 'marker', 'Marker', + 'mask', 'Mask', + 'metadata', 'Metadata', + 'mpath', 'Mpath', + 'path', 'Path', + 'pattern', 'Pattern', + 'polygon', 'Polygon', + 'polyline', 'Polyline', + 'radialGradient', + 'rect', 'Rect', + 'set', 'Set', + 'stop', 'Stop', + 'svg', 'Svg', + 'switch', 'Switch', + 'symbol', 'Symbol', + 'text', 'Text', + 'textPath', + 'tspan', 'Tspan', + 'unknown', 'Unknown', + 'use', 'Use', + 'view', 'View', + + // Deprecated + 'acronym', 'Acronym', + 'applet', 'Applet', + 'basefont', 'Basefont', + 'bgsound', 'Bgsound', + 'big', 'Big', + 'blink', 'Blink', + 'center', 'Center', + 'command', 'Command', + 'dir', 'Dir', + 'font', 'Font', + 'frame', 'Frame', + 'frameset', 'Frameset', + 'isindex', 'Isindex', + 'keygen', 'Keygen', + 'listing', 'Listing', + 'marquee', 'Marquee', + 'multicol', 'Multicol', + 'nextid', 'Nextid', + 'nobr', 'Nobr', + 'noembed', 'Noembed', + 'plaintext', 'Plaintext', + 'spacer', 'Spacer', + 'strike', 'Strike', + 'tt', 'Tt', + 'xmp', 'Xmp' +] + +const parserOptions = { + ecmaVersion: 2018, + sourceType: 'module' +} + +const ruleTester = new RuleTester() +ruleTester.run('no-reserved-component-names', rule, { + + valid: [ + { + filename: 'test.vue', + code: ` + export default { + } + `, + parserOptions + }, + { + filename: 'test.vue', + code: ` + export default { + ...name + } + `, + parserOptions + }, + { + filename: 'test.vue', + code: ` + export default { + name: 'FooBar' + } + `, + parserOptions + }, + { + filename: 'test.vue', + code: `Vue.component('FooBar', {})`, + parserOptions + }, + { + filename: 'test.js', + code: ` + new Vue({ + name: 'foo!bar' + }) + `, + parserOptions + }, + { + filename: 'test.vue', + code: `Vue.component(\`fooBar\${foo}\`, component)`, + parserOptions + }, + { + filename: 'test.vue', + code: ` + + `, + parser: 'vue-eslint-parser', + parserOptions + }, + { + filename: 'test.vue', + code: ` + + `, + parser: 'vue-eslint-parser', + parserOptions + } + ], + + invalid: [ + ...invalidElements.map(name => { + return { + filename: `${name}.vue`, + code: ` + export default { + name: '${name}' + } + `, + parserOptions, + errors: [{ + message: `Name "${name}" is reserved.`, + type: 'Literal', + line: 3 + }] + } + }), + ...invalidElements.map(name => { + return { + filename: 'test.vue', + code: `Vue.component('${name}', component)`, + parserOptions, + errors: [{ + message: `Name "${name}" is reserved.`, + type: 'Literal', + line: 1 + }] + } + }), + ...invalidElements.map(name => { + return { + filename: 'test.vue', + code: `Vue.component(\`${name}\`, {})`, + parserOptions, + errors: [{ + message: `Name "${name}" is reserved.`, + type: 'TemplateLiteral', + line: 1 + }] + } + }), + ...invalidElements.map(name => { + return { + filename: 'test.vue', + code: `export default { + components: { + '${name}': {}, + } + }`, + parserOptions, + errors: [{ + message: `Name "${name}" is reserved.`, + type: 'Property', + line: 3 + }] + } + }) + ] +}) From 8d7cadf0344b24787a30477f243c0292fa4ca512 Mon Sep 17 00:00:00 2001 From: Yosuke Ota Date: Thu, 26 Dec 2019 12:55:47 +0900 Subject: [PATCH 0014/1050] Change document style for new rules added & Fixed new rule test cases to work with eslint v6 (#1011) --- docs/rules/README.md | 2 ++ docs/rules/no-reserved-component-names.md | 15 +++++++++++--- docs/rules/require-name-property.md | 20 +++++++++++++++++-- docs/rules/static-class-names-order.md | 10 +++++----- lib/index.js | 2 ++ .../lib/rules/no-reserved-component-names.js | 4 ++-- 6 files changed, 41 insertions(+), 12 deletions(-) diff --git a/docs/rules/README.md b/docs/rules/README.md index 520e84fcb..b77661eb0 100644 --- a/docs/rules/README.md +++ b/docs/rules/README.md @@ -154,9 +154,11 @@ For example: | [vue/no-boolean-default](./no-boolean-default.md) | disallow boolean defaults | :wrench: | | [vue/no-deprecated-scope-attribute](./no-deprecated-scope-attribute.md) | disallow deprecated `scope` attribute (in Vue.js 2.5.0+) | :wrench: | | [vue/no-empty-pattern](./no-empty-pattern.md) | disallow empty destructuring patterns | | +| [vue/no-reserved-component-names](./no-reserved-component-names.md) | disallow the use of reserved names in component definitions | | | [vue/no-restricted-syntax](./no-restricted-syntax.md) | disallow specified syntax | | | [vue/object-curly-spacing](./object-curly-spacing.md) | enforce consistent spacing inside braces | :wrench: | | [vue/require-direct-export](./require-direct-export.md) | require the component to be directly exported | | +| [vue/require-name-property](./require-name-property.md) | require a name property in Vue components | | | [vue/script-indent](./script-indent.md) | enforce consistent indentation in ` +``` + + + + +```vue + +``` + + + + +```vue + `, - parser: 'vue-eslint-parser', + parser: require.resolve('vue-eslint-parser'), parserOptions }, { @@ -309,7 +309,7 @@ ruleTester.run('no-reserved-component-names', rule, { } `, - parser: 'vue-eslint-parser', + parser: require.resolve('vue-eslint-parser'), parserOptions } ], From c360057d4e6fce2375d0e2b9ca353ce733073b60 Mon Sep 17 00:00:00 2001 From: Yosuke Ota Date: Thu, 26 Dec 2019 12:58:12 +0900 Subject: [PATCH 0015/1050] =?UTF-8?q?=E2=AD=90=EF=B8=8FNew:=20Add=20`vue/c?= =?UTF-8?q?omponent-tags-order`=20rule=20(#763)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * ⭐️New: Add `vue/component-tags-order` rule * Upgrade vue-eslint-parser@^7.0.0 * Use parserServices.getDocumentFragment --- docs/rules/README.md | 1 + docs/rules/component-tags-order.md | 96 +++++++++++ lib/index.js | 1 + lib/rules/component-tags-order.js | 93 ++++++++++ tests/lib/rules/component-tags-order.js | 217 ++++++++++++++++++++++++ 5 files changed, 408 insertions(+) create mode 100644 docs/rules/component-tags-order.md create mode 100644 lib/rules/component-tags-order.js create mode 100644 tests/lib/rules/component-tags-order.js diff --git a/docs/rules/README.md b/docs/rules/README.md index b77661eb0..481f39099 100644 --- a/docs/rules/README.md +++ b/docs/rules/README.md @@ -146,6 +146,7 @@ For example: | [vue/camelcase](./camelcase.md) | enforce camelcase naming convention | | | [vue/comma-dangle](./comma-dangle.md) | require or disallow trailing commas | :wrench: | | [vue/component-name-in-template-casing](./component-name-in-template-casing.md) | enforce specific casing for the component naming style in template | :wrench: | +| [vue/component-tags-order](./component-tags-order.md) | enforce order of component top-level elements | | | [vue/dot-location](./dot-location.md) | enforce consistent newlines before and after dots | :wrench: | | [vue/eqeqeq](./eqeqeq.md) | require the use of `===` and `!==` | :wrench: | | [vue/key-spacing](./key-spacing.md) | enforce consistent spacing between keys and values in object literal properties | :wrench: | diff --git a/docs/rules/component-tags-order.md b/docs/rules/component-tags-order.md new file mode 100644 index 000000000..d8c45447b --- /dev/null +++ b/docs/rules/component-tags-order.md @@ -0,0 +1,96 @@ +--- +pageClass: rule-details +sidebarDepth: 0 +title: vue/component-tags-order +description: enforce order of component top-level elements +--- +# vue/component-tags-order +> enforce order of component top-level elements + +## :book: Rule Details + +This rule warns about the order of the ` + + +``` + + + + + +```vue + + + + +``` + + + +### `{ "order": ["template", "script", "style"] }` + + + +```vue + + + + +``` + + + +### `{ "order": ["docs", "template", "script", "style"] }` + + + +```vue + + documents + + + +``` + + + + + +```vue + + + + documents + +``` + + + +## :books: Further reading + +- [Style guide - Single-file component top-level element order](https://vuejs.org/v2/style-guide/#Single-file-component-top-level-element-order-recommended) + +## :mag: Implementation + +- [Rule source](https://github.com/vuejs/eslint-plugin-vue/blob/master/lib/rules/component-tags-order.js) +- [Test source](https://github.com/vuejs/eslint-plugin-vue/blob/master/tests/lib/rules/component-tags-order.js) diff --git a/lib/index.js b/lib/index.js index 8a2fd8033..28343da6d 100644 --- a/lib/index.js +++ b/lib/index.js @@ -17,6 +17,7 @@ module.exports = { 'comma-dangle': require('./rules/comma-dangle'), 'comment-directive': require('./rules/comment-directive'), 'component-name-in-template-casing': require('./rules/component-name-in-template-casing'), + 'component-tags-order': require('./rules/component-tags-order'), 'dot-location': require('./rules/dot-location'), 'eqeqeq': require('./rules/eqeqeq'), 'html-closing-bracket-newline': require('./rules/html-closing-bracket-newline'), diff --git a/lib/rules/component-tags-order.js b/lib/rules/component-tags-order.js new file mode 100644 index 000000000..6f63c2cf0 --- /dev/null +++ b/lib/rules/component-tags-order.js @@ -0,0 +1,93 @@ +/** + * @author Yosuke Ota + * issue https://github.com/vuejs/eslint-plugin-vue/issues/140 + */ +'use strict' + +// ------------------------------------------------------------------------------ +// Requirements +// ------------------------------------------------------------------------------ + +const utils = require('../utils') + +const DEFAULT_ORDER = Object.freeze(['script', 'template', 'style']) + +// ------------------------------------------------------------------------------ +// Rule Definition +// ------------------------------------------------------------------------------ + +module.exports = { + meta: { + type: 'suggestion', + docs: { + description: 'enforce order of component top-level elements', + category: undefined, + url: 'https://eslint.vuejs.org/rules/component-tags-order.html' + }, + fixable: null, + schema: { + type: 'array', + properties: { + order: { + type: 'array' + } + } + }, + messages: { + unexpected: 'The <{{name}}> should be above the <{{firstUnorderedName}}> on line {{line}}.' + } + }, + create (context) { + const order = (context.options[0] && context.options[0].order) || DEFAULT_ORDER + const documentFragment = context.parserServices.getDocumentFragment && context.parserServices.getDocumentFragment() + + function getTopLevelHTMLElements () { + if (documentFragment) { + return documentFragment.children + } + return [] + } + + function report (element, firstUnorderedElement) { + context.report({ + node: element, + loc: element.loc, + messageId: 'unexpected', + data: { + name: element.name, + firstUnorderedName: firstUnorderedElement.name, + line: firstUnorderedElement.loc.start.line + } + }) + } + + return utils.defineTemplateBodyVisitor( + context, + {}, + { + Program (node) { + if (utils.hasInvalidEOF(node)) { + return + } + const elements = getTopLevelHTMLElements() + + elements.forEach((element, index) => { + const expectedIndex = order.indexOf(element.name) + if (expectedIndex < 0) { + return + } + const firstUnordered = elements + .slice(0, index) + .filter(e => expectedIndex < order.indexOf(e.name)) + .sort( + (e1, e2) => order.indexOf(e1.name) - order.indexOf(e2.name) + )[0] + if (firstUnordered) { + report(element, firstUnordered) + } + }) + } + } + ) + } +} diff --git a/tests/lib/rules/component-tags-order.js b/tests/lib/rules/component-tags-order.js new file mode 100644 index 000000000..a9108fb76 --- /dev/null +++ b/tests/lib/rules/component-tags-order.js @@ -0,0 +1,217 @@ +/** + * @author Yosuke Ota + */ +'use strict' + +// ------------------------------------------------------------------------------ +// Requirements +// ------------------------------------------------------------------------------ + +const rule = require('../../../lib/rules/component-tags-order') +const RuleTester = require('eslint').RuleTester + +// ------------------------------------------------------------------------------ +// Tests +// ------------------------------------------------------------------------------ + +const tester = new RuleTester({ + parser: require.resolve('vue-eslint-parser') +}) + +tester.run('component-tags-order', rule, { + valid: [ + // default + '', + '', + '', + '', + '', + ` + + + + + + `, + + // order + { + code: '', + output: null, + options: [{ order: ['template', 'script', 'style'] }] + }, + { + code: '', + output: null, + options: [{ order: ['style', 'template', 'script'] }] + }, + { + code: '', + output: null, + options: [{ order: ['template', 'docs', 'script', 'style'] }] + }, + { + code: '', + output: null, + options: [{ order: ['template', 'script', 'style'] }] + }, + { + code: '
text

', + output: null, + options: [{ order: ['docs', 'script', 'template', 'style'] }] + }, + + ``, + + // Invalid EOF + ' `, - errors: [{ messageId: 'expectedLongform', data: { actual: '#foo', argument: 'foo' }}], + errors: [ + { + messageId: 'expectedLongform', + data: { actual: '#foo', argument: 'foo' } + } + ], options: [{ named: 'longform' }] }, @@ -419,7 +489,12 @@ tester.run('v-slot-style', rule, { `, - errors: [{ messageId: 'expectedShorthand', data: { actual: 'v-slot:[foo]', argument: '[foo]' }}] + errors: [ + { + messageId: 'expectedShorthand', + data: { actual: 'v-slot:[foo]', argument: '[foo]' } + } + ] }, { code: ` @@ -436,7 +511,12 @@ tester.run('v-slot-style', rule, { `, - errors: [{ messageId: 'expectedLongform', data: { actual: '#[foo]', argument: '[foo]' }}], + errors: [ + { + messageId: 'expectedLongform', + data: { actual: '#[foo]', argument: '[foo]' } + } + ], options: [{ named: 'longform' }] } ] diff --git a/tests/lib/rules/valid-template-root.js b/tests/lib/rules/valid-template-root.js index 033188cca..212fefc8d 100644 --- a/tests/lib/rules/valid-template-root.js +++ b/tests/lib/rules/valid-template-root.js @@ -41,11 +41,13 @@ tester.run('valid-template-root', rule, { }, { filename: 'test.vue', - code: '' + code: + '' }, { filename: 'test.vue', - code: '' + code: + '' }, { filename: 'test.vue', @@ -57,7 +59,8 @@ tester.run('valid-template-root', rule, { }, { filename: 'test.vue', - code: '' + code: + '' }, { filename: 'test.vue', @@ -117,12 +120,16 @@ tester.run('valid-template-root', rule, { { filename: 'test.vue', code: '', - errors: ["The template root with 'src' attribute is required to be empty."] + errors: [ + "The template root with 'src' attribute is required to be empty." + ] }, { filename: 'test.vue', code: '', - errors: ["The template root with 'src' attribute is required to be empty."] + errors: [ + "The template root with 'src' attribute is required to be empty." + ] } ] }) diff --git a/tests/lib/rules/valid-v-bind-sync.js b/tests/lib/rules/valid-v-bind-sync.js index 4f939aa16..385f5d991 100644 --- a/tests/lib/rules/valid-v-bind-sync.js +++ b/tests/lib/rules/valid-v-bind-sync.js @@ -51,51 +51,63 @@ tester.run('valid-v-bind-sync', rule, { }, { filename: 'test.vue', - code: '' + code: + '' }, { filename: 'test.vue', - code: '' + code: + '' }, { filename: 'test.vue', - code: '' + code: + '' }, { filename: 'test.vue', - code: '' + code: + '' }, { filename: 'test.vue', - code: '' + code: + '' }, { filename: 'test.vue', - code: '' + code: + '' }, { filename: 'test.vue', - code: '' + code: + '' }, { filename: 'test.vue', - code: '' + code: + '' }, { filename: 'test.vue', - code: '' + code: + '' }, { filename: 'test.vue', - code: '' + code: + '' }, { filename: 'test.vue', - code: '' + code: + '' }, { filename: 'test.vue', - code: '' + code: + '' }, // not .sync { @@ -128,12 +140,15 @@ tester.run('valid-v-bind-sync', rule, { `, - errors: [{ - message: "'.sync' modifiers require the attribute value which is valid as LHS.", - line: 3, - column: 24, - endColumn: 41 - }] + errors: [ + { + message: + "'.sync' modifiers require the attribute value which is valid as LHS.", + line: 3, + column: 24, + endColumn: 41 + } + ] }, { filename: 'test.vue', @@ -142,12 +157,15 @@ tester.run('valid-v-bind-sync', rule, { `, - errors: [{ - message: "'.sync' modifiers require the attribute value which is valid as LHS.", - line: 3, - column: 24, - endColumn: 47 - }] + errors: [ + { + message: + "'.sync' modifiers require the attribute value which is valid as LHS.", + line: 3, + column: 24, + endColumn: 47 + } + ] }, { filename: 'test.vue', @@ -156,12 +174,15 @@ tester.run('valid-v-bind-sync', rule, { `, - errors: [{ - message: "'.sync' modifiers aren't supported on non Vue-components.", - line: 3, - column: 18, - endColumn: 33 - }] + errors: [ + { + message: + "'.sync' modifiers aren't supported on non Vue-components.", + line: 3, + column: 18, + endColumn: 33 + } + ] }, { filename: 'test.vue', @@ -170,12 +191,15 @@ tester.run('valid-v-bind-sync', rule, { `, - errors: [{ - message: "'.sync' modifiers require the attribute value which is valid as LHS.", - line: 3, - column: 24, - endColumn: 41 - }] + errors: [ + { + message: + "'.sync' modifiers require the attribute value which is valid as LHS.", + line: 3, + column: 24, + endColumn: 41 + } + ] }, { filename: 'test.vue', @@ -184,12 +208,15 @@ tester.run('valid-v-bind-sync', rule, { `, - errors: [{ - message: "'.sync' modifiers require the attribute value which is valid as LHS.", - line: 3, - column: 24, - endColumn: 46 - }] + errors: [ + { + message: + "'.sync' modifiers require the attribute value which is valid as LHS.", + line: 3, + column: 24, + endColumn: 46 + } + ] }, { filename: 'test.vue', @@ -198,12 +225,15 @@ tester.run('valid-v-bind-sync', rule, { `, - errors: [{ - message: "'.sync' modifiers aren't supported on non Vue-components.", - line: 3, - column: 18, - endColumn: 39 - }] + errors: [ + { + message: + "'.sync' modifiers aren't supported on non Vue-components.", + line: 3, + column: 18, + endColumn: 39 + } + ] }, { filename: 'test.vue', @@ -214,12 +244,15 @@ tester.run('valid-v-bind-sync', rule, { `, - errors: [{ - message: "'.sync' modifiers cannot update the iteration variable 'x' itself.", - line: 4, - column: 26, - endColumn: 39 - }] + errors: [ + { + message: + "'.sync' modifiers cannot update the iteration variable 'x' itself.", + line: 4, + column: 26, + endColumn: 39 + } + ] }, { filename: 'test.vue', @@ -230,12 +263,15 @@ tester.run('valid-v-bind-sync', rule, { `, - errors: [{ - message: "'.sync' modifiers cannot update the iteration variable 'e' itself.", - line: 4, - column: 26, - endColumn: 45 - }] + errors: [ + { + message: + "'.sync' modifiers cannot update the iteration variable 'e' itself.", + line: 4, + column: 26, + endColumn: 45 + } + ] }, { filename: 'test.vue', @@ -250,10 +286,13 @@ tester.run('valid-v-bind-sync', rule, { `, - errors: [{ - message: "'.sync' modifiers cannot update the iteration variable 'e1' itself.", - line: 6 - }] + errors: [ + { + message: + "'.sync' modifiers cannot update the iteration variable 'e1' itself.", + line: 6 + } + ] }, { filename: 'test.vue', @@ -264,10 +303,13 @@ tester.run('valid-v-bind-sync', rule, { `, - errors: [{ - message: "'.sync' modifiers cannot update the iteration variable 'index' itself.", - line: 4 - }] + errors: [ + { + message: + "'.sync' modifiers cannot update the iteration variable 'index' itself.", + line: 4 + } + ] }, { filename: 'test.vue', @@ -276,17 +318,23 @@ tester.run('valid-v-bind-sync', rule, {
`, - errors: ["'.sync' modifiers aren't supported on
non Vue-components."] + errors: [ + "'.sync' modifiers aren't supported on
non Vue-components." + ] }, { filename: 'test.vue', code: '', - errors: ["'.sync' modifiers require the attribute value which is valid as LHS."] + errors: [ + "'.sync' modifiers require the attribute value which is valid as LHS." + ] }, { filename: 'test.vue', code: '', - errors: ["'.sync' modifiers require the attribute value which is valid as LHS."] + errors: [ + "'.sync' modifiers require the attribute value which is valid as LHS." + ] } ] }) diff --git a/tests/lib/rules/valid-v-else-if.js b/tests/lib/rules/valid-v-else-if.js index b0c18093f..778d07339 100644 --- a/tests/lib/rules/valid-v-else-if.js +++ b/tests/lib/rules/valid-v-else-if.js @@ -29,11 +29,13 @@ tester.run('valid-v-else-if', rule, { }, { filename: 'test.vue', - code: '' + code: + '' }, { filename: 'test.vue', - code: '' + code: + '' }, { filename: 'test.vue', @@ -43,57 +45,82 @@ tester.run('valid-v-else-if', rule, { invalid: [ { filename: 'test.vue', - code: '', - errors: ["'v-else-if' directives require being preceded by the element which has a 'v-if' or 'v-else-if' directive."] + code: + '', + errors: [ + "'v-else-if' directives require being preceded by the element which has a 'v-if' or 'v-else-if' directive." + ] }, { filename: 'test.vue', code: '', - errors: ["'v-else-if' directives require being preceded by the element which has a 'v-if' or 'v-else-if' directive."] + errors: [ + "'v-else-if' directives require being preceded by the element which has a 'v-if' or 'v-else-if' directive." + ] }, { filename: 'test.vue', code: '', - errors: ["'v-else-if' directives require being preceded by the element which has a 'v-if' or 'v-else-if' directive."] + errors: [ + "'v-else-if' directives require being preceded by the element which has a 'v-if' or 'v-else-if' directive." + ] }, { filename: 'test.vue', - code: '', - errors: ["'v-else-if' directives require being preceded by the element which has a 'v-if' or 'v-else-if' directive."] + code: + '', + errors: [ + "'v-else-if' directives require being preceded by the element which has a 'v-if' or 'v-else-if' directive." + ] }, { filename: 'test.vue', - code: '', - errors: ["'v-else-if' directives require being preceded by the element which has a 'v-if' or 'v-else-if' directive."] + code: + '', + errors: [ + "'v-else-if' directives require being preceded by the element which has a 'v-if' or 'v-else-if' directive." + ] }, { filename: 'test.vue', - code: '', - errors: ["'v-else-if' directives require being preceded by the element which has a 'v-if' or 'v-else-if' directive."] + code: + '', + errors: [ + "'v-else-if' directives require being preceded by the element which has a 'v-if' or 'v-else-if' directive." + ] }, { filename: 'test.vue', - code: '', - errors: ["'v-else-if' and 'v-if' directives can't exist on the same element."] + code: + '', + errors: [ + "'v-else-if' and 'v-if' directives can't exist on the same element." + ] }, { filename: 'test.vue', - code: '', - errors: ["'v-else-if' and 'v-else' directives can't exist on the same element."] + code: + '', + errors: [ + "'v-else-if' and 'v-else' directives can't exist on the same element." + ] }, { filename: 'test.vue', - code: '', + code: + '', errors: ["'v-else-if' directives require no argument."] }, { filename: 'test.vue', - code: '', + code: + '', errors: ["'v-else-if' directives require no modifier."] }, { filename: 'test.vue', - code: '', + code: + '', errors: ["'v-else-if' directives require that attribute value."] } ] diff --git a/tests/lib/rules/valid-v-else.js b/tests/lib/rules/valid-v-else.js index 7825e4dfb..1e235f9b5 100644 --- a/tests/lib/rules/valid-v-else.js +++ b/tests/lib/rules/valid-v-else.js @@ -29,11 +29,13 @@ tester.run('valid-v-else', rule, { }, { filename: 'test.vue', - code: '' + code: + '' }, { filename: 'test.vue', - code: '' + code: + '' }, { filename: 'test.vue', @@ -44,56 +46,79 @@ tester.run('valid-v-else', rule, { { filename: 'test.vue', code: '', - errors: ["'v-else' directives require being preceded by the element which has a 'v-if' or 'v-else-if' directive."] + errors: [ + "'v-else' directives require being preceded by the element which has a 'v-if' or 'v-else-if' directive." + ] }, { filename: 'test.vue', code: '', - errors: ["'v-else' directives require being preceded by the element which has a 'v-if' or 'v-else-if' directive."] + errors: [ + "'v-else' directives require being preceded by the element which has a 'v-if' or 'v-else-if' directive." + ] }, { filename: 'test.vue', code: '', - errors: ["'v-else' directives require being preceded by the element which has a 'v-if' or 'v-else-if' directive."] + errors: [ + "'v-else' directives require being preceded by the element which has a 'v-if' or 'v-else-if' directive." + ] }, { filename: 'test.vue', code: '', - errors: ["'v-else' directives require being preceded by the element which has a 'v-if' or 'v-else-if' directive."] + errors: [ + "'v-else' directives require being preceded by the element which has a 'v-if' or 'v-else-if' directive." + ] }, { filename: 'test.vue', - code: '', - errors: ["'v-else' directives require being preceded by the element which has a 'v-if' or 'v-else-if' directive."] + code: + '', + errors: [ + "'v-else' directives require being preceded by the element which has a 'v-if' or 'v-else-if' directive." + ] }, { filename: 'test.vue', - code: '', - errors: ["'v-else' directives require being preceded by the element which has a 'v-if' or 'v-else-if' directive."] + code: + '', + errors: [ + "'v-else' directives require being preceded by the element which has a 'v-if' or 'v-else-if' directive." + ] }, { filename: 'test.vue', - code: '', - errors: ["'v-else' and 'v-if' directives can't exist on the same element. You may want 'v-else-if' directives."] + code: + '', + errors: [ + "'v-else' and 'v-if' directives can't exist on the same element. You may want 'v-else-if' directives." + ] }, { filename: 'test.vue', - code: '', - errors: ["'v-else' and 'v-else-if' directives can't exist on the same element."] + code: + '', + errors: [ + "'v-else' and 'v-else-if' directives can't exist on the same element." + ] }, { filename: 'test.vue', - code: '', + code: + '', errors: ["'v-else' directives require no argument."] }, { filename: 'test.vue', - code: '', + code: + '', errors: ["'v-else' directives require no modifier."] }, { filename: 'test.vue', - code: '', + code: + '', errors: ["'v-else' directives require no attribute value."] } ] diff --git a/tests/lib/rules/valid-v-for.js b/tests/lib/rules/valid-v-for.js index d83b634a3..74a1ea614 100644 --- a/tests/lib/rules/valid-v-for.js +++ b/tests/lib/rules/valid-v-for.js @@ -37,55 +37,68 @@ tester.run('valid-v-for', rule, { }, { filename: 'test.vue', - code: '' + code: + '' }, { filename: 'test.vue', - code: '' + code: + '' }, { filename: 'test.vue', - code: '' + code: + '' }, { filename: 'test.vue', - code: '' + code: + '' }, { filename: 'test.vue', - code: '' + code: + '' }, { filename: 'test.vue', - code: '' + code: + '' }, { filename: 'test.vue', - code: '' + code: + '' }, { filename: 'test.vue', - code: '' + code: + '' }, { filename: 'test.vue', - code: '' + code: + '' }, { filename: 'test.vue', - code: '' + code: + '' }, { filename: 'test.vue', - code: '' + code: + '' }, { filename: 'test.vue', - code: '' + code: + '' }, { filename: 'test.vue', - code: '' + code: + '' }, { filename: 'test.vue', @@ -134,92 +147,123 @@ tester.run('valid-v-for', rule, { }, { filename: 'test.vue', - code: '', + code: + '', errors: ["Invalid alias ''."] }, { filename: 'test.vue', - code: '', + code: + '', errors: ["Invalid alias ''."] }, { filename: 'test.vue', - code: '', + code: + '', errors: ["Invalid alias ''."] }, { filename: 'test.vue', - code: '', + code: + '', errors: ["Invalid alias '{b,c}'."] }, { filename: 'test.vue', - code: '', + code: + '', errors: ["Invalid alias '{c,d}'."] }, { filename: 'test.vue', - code: '', + code: + '', errors: ["Custom elements in iteration require 'v-bind:key' directives."] }, { filename: 'test.vue', - code: '', + code: + '', errors: ["Custom elements in iteration require 'v-bind:key' directives."] }, { filename: 'test.vue', - code: '', + code: + '', errors: ["Custom elements in iteration require 'v-bind:key' directives."] }, { filename: 'test.vue', - code: '', + code: + '', errors: ["Custom elements in iteration require 'v-bind:key' directives."] }, { filename: 'test.vue', - code: '', - errors: ["Expected 'v-bind:key' directive to use the variables which are defined by the 'v-for' directive."] + code: + '', + errors: [ + "Expected 'v-bind:key' directive to use the variables which are defined by the 'v-for' directive." + ] }, { filename: 'test.vue', - code: '', - errors: ["Expected 'v-bind:key' directive to use the variables which are defined by the 'v-for' directive."] + code: + '', + errors: [ + "Expected 'v-bind:key' directive to use the variables which are defined by the 'v-for' directive." + ] }, { filename: 'test.vue', - code: '', - errors: ["Expected 'v-bind:key' directive to use the variables which are defined by the 'v-for' directive."] + code: + '', + errors: [ + "Expected 'v-bind:key' directive to use the variables which are defined by the 'v-for' directive." + ] }, { filename: 'test.vue', - code: '', - errors: ["Expected 'v-bind:key' directive to use the variables which are defined by the 'v-for' directive."] + code: + '', + errors: [ + "Expected 'v-bind:key' directive to use the variables which are defined by the 'v-for' directive." + ] }, { filename: 'test.vue', - code: '', - errors: ["Expected 'v-bind:key' directive to use the variables which are defined by the 'v-for' directive."] + code: + '', + errors: [ + "Expected 'v-bind:key' directive to use the variables which are defined by the 'v-for' directive." + ] }, { filename: 'test.vue', - code: '', + code: + '', errors: ["Custom elements in iteration require 'v-bind:key' directives."] }, { filename: 'test.vue', - code: '', + code: + '', errors: ["'v-for' directives require that attribute value."] }, { filename: 'test.vue', - code: '', - errors: ["Expected 'v-bind:key' directive to use the variables which are defined by the 'v-for' directive."] + code: + '', + errors: [ + "Expected 'v-bind:key' directive to use the variables which are defined by the 'v-for' directive." + ] }, { filename: 'test.vue', - errors: ["Expected 'v-bind:key' directive to use the variables which are defined by the 'v-for' directive."], + errors: [ + "Expected 'v-bind:key' directive to use the variables which are defined by the 'v-for' directive." + ], code: ` ` + }, + // empty value + { + filename: 'empty-value.vue', + code: '', + errors: ["'v-for' directives require that attribute value."] } ] }) diff --git a/tests/lib/rules/valid-v-html.js b/tests/lib/rules/valid-v-html.js index 9a30bcf48..afb312f74 100644 --- a/tests/lib/rules/valid-v-html.js +++ b/tests/lib/rules/valid-v-html.js @@ -30,6 +30,16 @@ tester.run('valid-v-html', rule, { { filename: 'test.vue', code: '' + }, + // parsing error + { + filename: 'parsing-error.vue', + code: '' + }, + // comment value (parsing error) + { + filename: 'comment-value.vue', + code: '' } ], invalid: [ @@ -47,6 +57,12 @@ tester.run('valid-v-html', rule, { filename: 'test.vue', code: '', errors: ["'v-html' directives require that attribute value."] + }, + // empty value + { + filename: 'empty-value.vue', + code: '', + errors: ["'v-html' directives require that attribute value."] } ] }) diff --git a/tests/lib/rules/valid-v-if.js b/tests/lib/rules/valid-v-if.js index effe04836..d3afe5780 100644 --- a/tests/lib/rules/valid-v-if.js +++ b/tests/lib/rules/valid-v-if.js @@ -30,6 +30,16 @@ tester.run('valid-v-if', rule, { { filename: 'test.vue', code: '' + }, + // parsing error + { + filename: 'parsing-error.vue', + code: '' + }, + // comment value (parsing error) + { + filename: 'comment-value.vue', + code: '' } ], invalid: [ @@ -62,6 +72,12 @@ tester.run('valid-v-if', rule, { filename: 'test.vue', code: '', errors: ["'v-if' directives require that attribute value."] + }, + // empty value + { + filename: 'empty-value.vue', + code: '', + errors: ["'v-if' directives require that attribute value."] } ] }) diff --git a/tests/lib/rules/valid-v-model.js b/tests/lib/rules/valid-v-model.js index c9c28585a..9e78ef47a 100644 --- a/tests/lib/rules/valid-v-model.js +++ b/tests/lib/rules/valid-v-model.js @@ -150,6 +150,16 @@ tester.run('valid-v-model', rule, { filename: 'test.vue', code: '' + }, + // parsing error + { + filename: 'parsing-error.vue', + code: '' + }, + // comment value (parsing error) + { + filename: 'comment-value.vue', + code: '' } ], invalid: [ @@ -216,6 +226,12 @@ tester.run('valid-v-model', rule, { errors: [ "'v-model' directives cannot update the iteration variable 'e' itself." ] + }, + // empty value + { + filename: 'empty-value.vue', + code: '', + errors: ["'v-model' directives require that attribute value."] } ] }) diff --git a/tests/lib/rules/valid-v-on.js b/tests/lib/rules/valid-v-on.js index 911039ea5..6f162e1df 100644 --- a/tests/lib/rules/valid-v-on.js +++ b/tests/lib/rules/valid-v-on.js @@ -96,6 +96,33 @@ tester.run('valid-v-on', rule, { filename: 'test.vue', code: '', options: [{ modifiers: ['bar', 'aaa'] }] + }, + // parsing error + { + filename: 'parsing-error.vue', + code: '' + }, + // comment value (valid) + { + filename: 'comment-value.vue', + code: '' + }, + { + filename: 'comment-value.vue', + code: '' + }, + { + filename: 'comment-value.vue', + code: '' + }, + { + filename: 'comment-value.vue', + code: '' + }, + // empty value + { + filename: 'empty-value.vue', + code: '' } ], invalid: [ @@ -139,6 +166,14 @@ tester.run('valid-v-on', rule, { filename: 'test.vue', code: '', errors: ['Avoid using JavaScript keyword as "v-on" value: "delete".'] + }, + // empty value + { + filename: 'empty-value.vue', + code: '', + errors: [ + "'v-on' directives require a value or verb modifier (like 'stop' or 'prevent')." + ] } ] }) diff --git a/tests/lib/rules/valid-v-once.js b/tests/lib/rules/valid-v-once.js index 0c6a71a20..c9f3750c3 100644 --- a/tests/lib/rules/valid-v-once.js +++ b/tests/lib/rules/valid-v-once.js @@ -47,6 +47,24 @@ tester.run('valid-v-once', rule, { filename: 'test.vue', code: '', errors: ["'v-once' directives require no attribute value."] + }, + // parsing error + { + filename: 'parsing-error.vue', + code: '', + errors: ["'v-once' directives require no attribute value."] + }, + // comment value + { + filename: 'comment-value.vue', + code: '', + errors: ["'v-once' directives require no attribute value."] + }, + // empty value + { + filename: 'comment-value.vue', + code: '', + errors: ["'v-once' directives require no attribute value."] } ] }) diff --git a/tests/lib/rules/valid-v-pre.js b/tests/lib/rules/valid-v-pre.js index ae9d38e6f..3d9c9d883 100644 --- a/tests/lib/rules/valid-v-pre.js +++ b/tests/lib/rules/valid-v-pre.js @@ -47,6 +47,24 @@ tester.run('valid-v-pre', rule, { filename: 'test.vue', code: '', errors: ["'v-pre' directives require no attribute value."] + }, + // parsing error + { + filename: 'parsing-error.vue', + code: '', + errors: ["'v-pre' directives require no attribute value."] + }, + // comment value + { + filename: 'comment-value.vue', + code: '', + errors: ["'v-pre' directives require no attribute value."] + }, + // empty value + { + filename: 'empty-value.vue', + code: '', + errors: ["'v-pre' directives require no attribute value."] } ] }) diff --git a/tests/lib/rules/valid-v-show.js b/tests/lib/rules/valid-v-show.js index 0471ccd9f..4220e8749 100644 --- a/tests/lib/rules/valid-v-show.js +++ b/tests/lib/rules/valid-v-show.js @@ -30,6 +30,16 @@ tester.run('valid-v-show', rule, { { filename: 'test.vue', code: '' + }, + // parsing error + { + filename: 'parsing-error.vue', + code: '' + }, + // comment value (parsing error) + { + filename: 'comment-value.vue', + code: '' } ], invalid: [ @@ -48,8 +58,9 @@ tester.run('valid-v-show', rule, { code: '', errors: ["'v-show' directives require that attribute value."] }, + // empty value { - filename: 'test.vue', + filename: 'empty-value.vue', code: '', errors: ["'v-show' directives require that attribute value."] } diff --git a/tests/lib/rules/valid-v-slot.js b/tests/lib/rules/valid-v-slot.js index ab8307fed..0043756f1 100644 --- a/tests/lib/rules/valid-v-slot.js +++ b/tests/lib/rules/valid-v-slot.js @@ -83,7 +83,13 @@ tester.run('valid-v-slot', rule, { - ` + `, + // parsing error + { + filename: 'parsing-error.vue', + code: + '' + } ], invalid: [ // Verify location. @@ -294,6 +300,26 @@ tester.run('valid-v-slot', rule, { `, errors: [{ messageId: 'requireAttributeValue' }] + }, + // comment value + { + filename: 'comment-value1.vue', + code: + '', + errors: [{ messageId: 'requireAttributeValue' }] + }, + { + filename: 'comment-value2.vue', + code: + '', + errors: [{ messageId: 'requireAttributeValue' }] + }, + // empty value + { + filename: 'empty-value.vue', + code: + '', + errors: [{ messageId: 'requireAttributeValue' }] } ] }) diff --git a/tests/lib/rules/valid-v-text.js b/tests/lib/rules/valid-v-text.js index 3956462ce..611b1a31a 100644 --- a/tests/lib/rules/valid-v-text.js +++ b/tests/lib/rules/valid-v-text.js @@ -34,6 +34,16 @@ tester.run('valid-v-text', rule, { { filename: 'test.vue', code: '' + }, + // parsing error + { + filename: 'parsing-error.vue', + code: '' + }, + // comment value (parsing error) + { + filename: 'parsing-error.vue', + code: '' } ], invalid: [ @@ -51,6 +61,12 @@ tester.run('valid-v-text', rule, { filename: 'test.vue', code: '', errors: ["'v-text' directives require that attribute value."] + }, + // empty value + { + filename: 'empty-value.vue', + code: '', + errors: ["'v-text' directives require that attribute value."] } ] }) From e5c835eb957340b153ea2047e3ba274b88ba01bb Mon Sep 17 00:00:00 2001 From: Yosuke Ota Date: Fri, 5 Jun 2020 14:29:35 +0900 Subject: [PATCH 0148/1050] Add `vue/no-bare-strings-in-template` rule (#1185) --- docs/rules/README.md | 1 + docs/rules/no-bare-strings-in-template.md | 88 ++++++ lib/index.js | 1 + lib/rules/no-bare-strings-in-template.js | 279 +++++++++++++++++ lib/utils/regexp.js | 14 +- package.json | 2 + .../lib/rules/no-bare-strings-in-template.js | 290 ++++++++++++++++++ 7 files changed, 673 insertions(+), 2 deletions(-) create mode 100644 docs/rules/no-bare-strings-in-template.md create mode 100644 lib/rules/no-bare-strings-in-template.js create mode 100644 tests/lib/rules/no-bare-strings-in-template.js diff --git a/docs/rules/README.md b/docs/rules/README.md index f3e2bfdf6..55b3c4e0d 100644 --- a/docs/rules/README.md +++ b/docs/rules/README.md @@ -282,6 +282,7 @@ For example: | [vue/html-comment-content-spacing](./html-comment-content-spacing.md) | enforce unified spacing in HTML comments | :wrench: | | [vue/html-comment-indent](./html-comment-indent.md) | enforce consistent indentation in HTML comments | :wrench: | | [vue/match-component-file-name](./match-component-file-name.md) | require component name property to match its file name | | +| [vue/no-bare-strings-in-template](./no-bare-strings-in-template.md) | disallow the use of bare strings in ` `, - options: [{ ignoreWhenEmpty: false, ignoreWhenNoAttributes: false }], output: ` `, + options: [{ ignoreWhenEmpty: false, ignoreWhenNoAttributes: false }], errors: [ 'Expected 1 line break after opening tag (`
`), but no line breaks found.' ] @@ -473,13 +473,13 @@ singleline element
`, - options: [{ ignoreWhenEmpty: false, ignoreWhenNoAttributes: false }], output: ` `, + options: [{ ignoreWhenEmpty: false, ignoreWhenNoAttributes: false }], errors: [ 'Expected 1 line break after opening tag (`
`), but no line breaks found.' ] diff --git a/tests/lib/rules/space-in-parens.js b/tests/lib/rules/space-in-parens.js index 8791652ed..ef6fe628e 100644 --- a/tests/lib/rules/space-in-parens.js +++ b/tests/lib/rules/space-in-parens.js @@ -94,13 +94,13 @@ tester.run('space-in-parens', rule, { @click="foo(arg)" /> `, - options: ['always'], output: ` `, + options: ['always'], errors: [ errorMessage({ messageId: 'missingOpeningSpace', @@ -143,13 +143,13 @@ tester.run('space-in-parens', rule, { :value="(1 + 2) + 3" > `, - options: ['always'], output: ` `, + options: ['always'], errors: [ errorMessage({ messageId: 'missingOpeningSpace', @@ -192,13 +192,13 @@ tester.run('space-in-parens', rule, { :[(1+2)]="(1 + 2) + 3" > `, - options: ['always'], output: ` `, + options: ['always'], errors: [ errorMessage({ messageId: 'missingOpeningSpace', diff --git a/tests/lib/rules/space-unary-ops.js b/tests/lib/rules/space-unary-ops.js index 6b3e7c25f..63539a5a7 100644 --- a/tests/lib/rules/space-unary-ops.js +++ b/tests/lib/rules/space-unary-ops.js @@ -50,8 +50,8 @@ tester.run('space-unary-ops', rule, { }, { code: '', - options: [{ nonwords: true }], output: '', + options: [{ nonwords: true }], errors: ["Unary operator '!' must be followed by whitespace."] }, diff --git a/tests/lib/rules/template-curly-spacing.js b/tests/lib/rules/template-curly-spacing.js index 6c4376969..0fe530511 100644 --- a/tests/lib/rules/template-curly-spacing.js +++ b/tests/lib/rules/template-curly-spacing.js @@ -41,14 +41,13 @@ tester.run('template-curly-spacing', rule, { }, // CSS vars injection - { - code: ` + ` ` - } + + ` ], invalid: [ { @@ -79,12 +78,12 @@ tester.run('template-curly-spacing', rule, {
`, - options: ['always'], output: ` `, + options: ['always'], errors: [ { message: "Expected space(s) after '${'.", diff --git a/tests/lib/rules/this-in-template.js b/tests/lib/rules/this-in-template.js index 904aa00c4..5686bf137 100644 --- a/tests/lib/rules/this-in-template.js +++ b/tests/lib/rules/this-in-template.js @@ -248,14 +248,14 @@ ruleTester.run('this-in-template', rule, { { code: ``, output: ``, - errors: ["Unexpected usage of 'this'."], - options: ['never'] + options: ['never'], + errors: ["Unexpected usage of 'this'."] }, { code: ``, output: ``, - errors: ["Unexpected usage of 'this'."], - options: ['never'] + options: ['never'], + errors: ["Unexpected usage of 'this'."] } ] }) diff --git a/tests/lib/rules/use-v-on-exact.js b/tests/lib/rules/use-v-on-exact.js index 73ac2408b..929fd746a 100644 --- a/tests/lib/rules/use-v-on-exact.js +++ b/tests/lib/rules/use-v-on-exact.js @@ -15,92 +15,48 @@ const ruleTester = new RuleTester({ ruleTester.run('use-v-on-exact', rule, { valid: [ - { - code: `` - }, - { - code: `` - }, - { - code: `` - }, - { - code: `` - }, - { - code: `` - }, - { - code: `` - }, - { - code: `` - }, - { - code: `` - }, - { - code: `` - }, - { - code: `` - }, - { - code: `` - }, - { - code: `` - }, - { - code: `` - }, - { - code: `` - }, - { - code: `` - }, - { - code: `` - }, - { - code: `` - }, - { - code: `` - }, - { - code: `` - }, - { - code: `` - }, - { - code: `` - }, - { - code: `` - }, - { - code: ` + ` ], invalid: [ diff --git a/tests/lib/rules/v-bind-style.js b/tests/lib/rules/v-bind-style.js index 2fd1ce7c2..0cb67f62f 100644 --- a/tests/lib/rules/v-bind-style.js +++ b/tests/lib/rules/v-bind-style.js @@ -67,44 +67,44 @@ tester.run('v-bind-style', rule, { }, { filename: 'test.vue', - options: ['shorthand'], code: '', output: '', + options: ['shorthand'], errors: ["Unexpected 'v-bind' before ':'."] }, { filename: 'test.vue', - options: ['longform'], code: '', output: '', + options: ['longform'], errors: ["Expected 'v-bind' before ':'."] }, { filename: 'test.vue', - options: ['longform'], code: '', output: '', + options: ['longform'], errors: ["Expected 'v-bind:' instead of '.'."] }, { filename: 'test.vue', - options: ['longform'], code: '', output: '', + options: ['longform'], errors: ["Expected 'v-bind:' instead of '.'."] }, { filename: 'test.vue', - options: ['longform'], code: '', output: '', + options: ['longform'], errors: ["Expected 'v-bind:' instead of '.'."] }, { filename: 'test.vue', - options: ['longform'], code: '', output: '', + options: ['longform'], errors: ["Expected 'v-bind:' instead of '.'."] } ] diff --git a/tests/lib/rules/v-for-delimiter-style.js b/tests/lib/rules/v-for-delimiter-style.js index b1500af50..23665a32c 100644 --- a/tests/lib/rules/v-for-delimiter-style.js +++ b/tests/lib/rules/v-for-delimiter-style.js @@ -94,9 +94,9 @@ tester.run('v-for-delimiter-style', rule, { }, { filename: 'test.vue', - options: ['in'], code: '', output: '', + options: ['in'], errors: [ { message: "Expected 'in' instead of 'of' in 'v-for'.", @@ -106,9 +106,9 @@ tester.run('v-for-delimiter-style', rule, { }, { filename: 'test.vue', - options: ['of'], code: '', output: '', + options: ['of'], errors: [ { message: "Expected 'of' instead of 'in' in 'v-for'.", diff --git a/tests/lib/rules/v-on-event-hyphenation.js b/tests/lib/rules/v-on-event-hyphenation.js index 087e5d00a..beaa5e3fa 100644 --- a/tests/lib/rules/v-on-event-hyphenation.js +++ b/tests/lib/rules/v-on-event-hyphenation.js @@ -73,12 +73,12 @@ tester.run('v-on-event-hyphenation', rule, { `, - options: ['always', { autofix: true }], output: ` `, + options: ['always', { autofix: true }], errors: [ { message: "v-on event '@customEvent' must be hyphenated.", @@ -95,12 +95,12 @@ tester.run('v-on-event-hyphenation', rule, { `, - options: ['never', { autofix: true }], output: ` `, + options: ['never', { autofix: true }], errors: ["v-on event 'v-on:custom-event' can't be hyphenated."] }, { @@ -110,13 +110,13 @@ tester.run('v-on-event-hyphenation', rule, { `, - options: ['always', { autofix: true }], output: ` `, + options: ['always', { autofix: true }], errors: ["v-on event '@update:modelValue' must be hyphenated."] }, { @@ -126,13 +126,13 @@ tester.run('v-on-event-hyphenation', rule, { `, - options: ['never', { autofix: true }], output: ` `, + options: ['never', { autofix: true }], errors: ["v-on event '@update:model-value' can't be hyphenated."] }, { @@ -144,7 +144,6 @@ tester.run('v-on-event-hyphenation', rule, { `, - options: ['always', { autofix: true }], output: ` `, + options: ['always', { autofix: true }], errors: [ "v-on event '@upDate:modelValue' must be hyphenated.", "v-on event '@up-date:modelValue' must be hyphenated.", @@ -168,7 +168,6 @@ tester.run('v-on-event-hyphenation', rule, { `, - options: ['never', { autofix: true }], output: ` `, + options: ['never', { autofix: true }], errors: [ "v-on event '@up-date:modelValue' can't be hyphenated.", "v-on event '@upDate:model-value' can't be hyphenated.", diff --git a/tests/lib/rules/v-on-function-call.js b/tests/lib/rules/v-on-function-call.js index d8486a448..d06bbe231 100644 --- a/tests/lib/rules/v-on-function-call.js +++ b/tests/lib/rules/v-on-function-call.js @@ -161,37 +161,37 @@ tester.run('v-on-function-call', rule, { filename: 'test.vue', code: '', output: null, + options: ['always'], errors: [ "Method calls inside of 'v-on' directives must have parentheses." - ], - options: ['always'] + ] }, { filename: 'test.vue', code: '', output: ``, + options: ['never'], errors: [ "Method calls without arguments inside of 'v-on' directives must not have parentheses." - ], - options: ['never'] + ] }, { filename: 'test.vue', code: '', output: ``, + options: ['never'], errors: [ "Method calls without arguments inside of 'v-on' directives must not have parentheses." - ], - options: ['never'] + ] }, { filename: 'test.vue', code: '', output: null, + options: ['never'], errors: [ "Method calls without arguments inside of 'v-on' directives must not have parentheses." - ], - options: ['never'] + ] }, { filename: 'test.vue', @@ -204,13 +204,13 @@ tester.run('v-on-function-call', rule, { ">
`, output: null, + options: ['never'], errors: [ "Method calls without arguments inside of 'v-on' directives must not have parentheses.", "Method calls without arguments inside of 'v-on' directives must not have parentheses.", "Method calls without arguments inside of 'v-on' directives must not have parentheses.", "Method calls without arguments inside of 'v-on' directives must not have parentheses." - ], - options: ['never'] + ] }, { filename: 'test.vue', @@ -222,10 +222,10 @@ tester.run('v-on-function-call', rule, { `, + options: ['never'], errors: [ "Method calls without arguments inside of 'v-on' directives must not have parentheses." - ], - options: ['never'] + ] }, { filename: 'test.vue', @@ -237,10 +237,10 @@ tester.run('v-on-function-call', rule, { `, + options: ['never'], errors: [ "Method calls without arguments inside of 'v-on' directives must not have parentheses." - ], - options: ['never'] + ] }, { filename: 'test.vue', @@ -254,11 +254,11 @@ tester.run('v-on-function-call', rule, {
`, + options: ['never'], errors: [ "Method calls without arguments inside of 'v-on' directives must not have parentheses.", "Method calls without arguments inside of 'v-on' directives must not have parentheses." - ], - options: ['never'] + ] }, { filename: 'test.vue', @@ -270,10 +270,10 @@ tester.run('v-on-function-call', rule, { `, + options: ['never'], errors: [ "Method calls without arguments inside of 'v-on' directives must not have parentheses." - ], - options: ['never'] + ] }, { filename: 'test.vue', @@ -285,19 +285,19 @@ tester.run('v-on-function-call', rule, { `, + options: ['never'], errors: [ "Method calls without arguments inside of 'v-on' directives must not have parentheses." - ], - options: ['never'] + ] }, { filename: 'test.vue', code: '', output: '', + options: ['never'], errors: [ "Method calls without arguments inside of 'v-on' directives must not have parentheses." - ], - options: ['never'] + ] }, { filename: 'test.vue', @@ -319,10 +319,10 @@ tester.run('v-on-function-call', rule, { } } `, + options: ['never'], errors: [ "Method calls without arguments inside of 'v-on' directives must not have parentheses." - ], - options: ['never'] + ] }, { filename: 'test.vue', @@ -344,10 +344,10 @@ tester.run('v-on-function-call', rule, { } } `, + options: ['never'], errors: [ "Method calls without arguments inside of 'v-on' directives must not have parentheses." - ], - options: ['never'] + ] } ] }) diff --git a/tests/lib/rules/v-on-handler-style.js b/tests/lib/rules/v-on-handler-style.js index e3afd05d5..314bbf8c8 100644 --- a/tests/lib/rules/v-on-handler-style.js +++ b/tests/lib/rules/v-on-handler-style.js @@ -78,12 +78,12 @@ tester.run('v-on-handler-style', rule, {