From 76b9fc4a4f37d7ea2a4030b06490d9eede0fc339 Mon Sep 17 00:00:00 2001 From: Jake Hassel Date: Sun, 6 Jan 2019 12:55:36 -0500 Subject: [PATCH 1/7] :star: add rule no-reserved-component-names --- docs/rules/no-reserved-component-names.md | 26 ++ lib/rules/no-reserved-component-names.js | 246 +++++++++++ .../lib/rules/no-reserved-component-names.js | 390 ++++++++++++++++++ 3 files changed, 662 insertions(+) create mode 100644 docs/rules/no-reserved-component-names.md create mode 100644 lib/rules/no-reserved-component-names.js 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..b5cc92f97 --- /dev/null +++ b/docs/rules/no-reserved-component-names.md @@ -0,0 +1,26 @@ +# 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) +- [Kebab case elements](https://stackoverflow.com/questions/22545621/do-custom-elements-require-a-dash-in-their-name/22545622#22545622) \ 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..d46d600f4 --- /dev/null +++ b/lib/rules/no-reserved-component-names.js @@ -0,0 +1,246 @@ +/** + * @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 kebabCaseElements = [ + 'annotation-xml', + 'color-profile', + 'font-face', + 'font-face-src', + 'font-face-uri', + 'font-face-format', + 'font-face-name', + 'missing-glyph' +] + +const elementNames = [ + 'a', + 'abbr', + 'acronym', + 'address', + 'applet', + 'area', + 'article', + 'aside', + 'audio', + 'b', + 'base', + 'basefont', + 'bdi', + 'bdo', + 'bgsound', + 'big', + 'blink', + 'blockquote', + 'body', + 'br', + 'button', + 'canvas', + 'caption', + 'center', + 'cite', + 'code', + 'col', + 'colgroup', + 'command', + 'content', + 'data', + 'datalist', + 'dd', + 'del', + 'details', + 'dfn', + 'dialog', + 'dir', + 'div', + 'dl', + 'dt', + 'element', + 'em', + 'embed', + 'fieldset', + 'figcaption', + 'figure', + 'font', + 'footer', + 'form', + 'frame', + 'frameset', + 'h1', + 'head', + 'header', + 'hgroup', + 'hr', + 'html', + 'i', + 'iframe', + 'image', + 'img', + 'input', + 'ins', + 'isindex', + 'kbd', + 'keygen', + 'label', + 'legend', + 'li', + 'link', + 'listing', + 'main', + 'map', + 'mark', + 'marquee', + 'menu', + 'menuitem', + 'meta', + 'meter', + 'multicol', + 'nav', + 'nextid', + 'nobr', + 'noembed', + 'noframes', + 'noscript', + 'object', + 'ol', + 'optgroup', + 'option', + 'output', + 'p', + 'param', + 'picture', + 'plaintext', + 'pre', + 'progress', + 'q', + 'rb', + 'rp', + 'rt', + 'rtc', + 'ruby', + 's', + 'samp', + 'script', + 'section', + 'select', + 'shadow', + 'slot', + 'small', + 'source', + 'spacer', + 'span', + 'strike', + 'strong', + 'style', + 'sub', + 'summary', + 'sup', + 'table', + 'tbody', + 'td', + 'template', + 'textarea', + 'tfoot', + 'th', + 'thead', + 'time', + 'title', + 'tr', + 'track', + 'tt', + 'u', + 'ul', + 'var', + 'video', + 'wbr', + 'xmp' +] + +const RESERVED_NAMES = new Set( + [ + ...kebabCaseElements, + ...kebabCaseElements.map(name => casing.pascalCase(name)), + ...elementNames, + ...elementNames.map(name => name[0].toUpperCase() + name.substring(1, name.length)) + ]) + +// ------------------------------------------------------------------------------ +// Rule Definition +// ------------------------------------------------------------------------------ + +module.exports = { + meta: { + type: 'suggestion', + docs: { + description: 'disallow the use of reserved names in component definitions', + category: '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 nodeValue + if (node.type === 'TemplateLiteral') { + const quasis = node.quasis[0] + nodeValue = quasis.value.cooked + } else { + nodeValue = node.value + } + if (RESERVED_NAMES.has(nodeValue)) { + context.report({ + node: node, + message: 'Name "{{value}}" is reserved.', + data: { + value: nodeValue + } + }) + } + } + + 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') { + if (parent.arguments && parent.arguments.length === 2) { + const argument = parent.arguments[0] + + if (canVerify(argument)) { + reportIfInvalid(argument) + } + } + } + } + }, + utils.executeOnVue(context, (obj) => { + 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/tests/lib/rules/no-reserved-component-names.js b/tests/lib/rules/no-reserved-component-names.js new file mode 100644 index 000000000..2d9a28d08 --- /dev/null +++ b/tests/lib/rules/no-reserved-component-names.js @@ -0,0 +1,390 @@ +/** + * @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', + 'a', + 'abbr', + 'acronym', + 'address', + 'applet', + 'area', + 'article', + 'aside', + 'audio', + 'b', + 'base', + 'basefont', + 'bdi', + 'bdo', + 'bgsound', + 'big', + 'blink', + 'blockquote', + 'body', + 'br', + 'button', + 'canvas', + 'caption', + 'center', + 'cite', + 'code', + 'col', + 'colgroup', + 'command', + 'content', + 'data', + 'datalist', + 'dd', + 'del', + 'details', + 'dfn', + 'dialog', + 'dir', + 'div', + 'dl', + 'dt', + 'element', + 'em', + 'embed', + 'fieldset', + 'figcaption', + 'figure', + 'font', + 'footer', + 'form', + 'frame', + 'frameset', + 'h1', + 'head', + 'header', + 'hgroup', + 'hr', + 'html', + 'i', + 'iframe', + 'image', + 'img', + 'input', + 'ins', + 'isindex', + 'kbd', + 'keygen', + 'label', + 'legend', + 'li', + 'link', + 'listing', + 'main', + 'map', + 'mark', + 'marquee', + 'menu', + 'menuitem', + 'meta', + 'meter', + 'multicol', + 'nav', + 'nextid', + 'nobr', + 'noembed', + 'noframes', + 'noscript', + 'object', + 'ol', + 'optgroup', + 'option', + 'output', + 'p', + 'param', + 'picture', + 'plaintext', + 'pre', + 'progress', + 'q', + 'rb', + 'rp', + 'rt', + 'rtc', + 'ruby', + 's', + 'samp', + 'script', + 'section', + 'select', + 'shadow', + 'slot', + 'small', + 'source', + 'spacer', + 'span', + 'strike', + 'strong', + 'style', + 'sub', + 'summary', + 'sup', + 'table', + 'tbody', + 'td', + 'template', + 'textarea', + 'tfoot', + 'th', + 'thead', + 'time', + 'title', + 'tr', + 'track', + 'tt', + 'u', + 'ul', + 'var', + 'video', + 'wbr', + 'xmp', + + 'A', + 'Abbr', + 'Acronym', + 'Address', + 'Applet', + 'Area', + 'Article', + 'Aside', + 'Audio', + 'B', + 'Base', + 'Basefont', + 'Bdi', + 'Bdo', + 'Bgsound', + 'Big', + 'Blink', + 'Blockquote', + 'Body', + 'Br', + 'Button', + 'Canvas', + 'Caption', + 'Center', + 'Cite', + 'Code', + 'Col', + 'Colgroup', + 'Command', + 'Content', + 'Data', + 'Datalist', + 'Dd', + 'Del', + 'Details', + 'Dfn', + 'Dialog', + 'Dir', + 'Div', + 'Dl', + 'Dt', + 'Element', + 'Em', + 'Embed', + 'Fieldset', + 'Figcaption', + 'Figure', + 'Font', + 'Footer', + 'Form', + 'Frame', + 'Frameset', + 'H1', + 'Head', + 'Header', + 'Hgroup', + 'Hr', + 'Html', + 'I', + 'Iframe', + 'Image', + 'Img', + 'Input', + 'Ins', + 'Isindex', + 'Kbd', + 'Keygen', + 'Label', + 'Legend', + 'Li', + 'Link', + 'Listing', + 'Main', + 'Map', + 'Mark', + 'Marquee', + 'Menu', + 'Menuitem', + 'Meta', + 'Meter', + 'Multicol', + 'Nav', + 'Nextid', + 'Nobr', + 'Noembed', + 'Noframes', + 'Noscript', + 'Object', + 'Ol', + 'Optgroup', + 'Option', + 'Output', + 'P', + 'param', + 'Picture', + 'Plaintext', + 'Pre', + 'Progress', + 'Q', + 'Rb', + 'Rp', + 'Rt', + 'Rtc', + 'Ruby', + 'S', + 'Samp', + 'Script', + 'Section', + 'Select', + 'Shadow', + 'Slot', + 'Small', + 'Source', + 'Spacer', + 'Span', + 'Strike', + 'Strong', + 'Style', + 'Sub', + 'Summary', + 'Sup', + 'Table', + 'Tbody', + 'Td', + 'Template', + 'Textarea', + 'Tfoot', + 'Th', + 'Thead', + 'Time', + 'Title', + 'Tr', + 'Track', + 'Tt', + 'U', + 'Ul', + 'Var', + 'Video', + 'Wbr', + '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 + } + ], + + 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 + }] + } + }) + ] +}) From e5fe0276fd85828cde31d22de4579bd197dbdda5 Mon Sep 17 00:00:00 2001 From: Jake Hassel Date: Sun, 6 Jan 2019 20:29:38 -0500 Subject: [PATCH 2/7] Increase test coverage --- lib/rules/no-reserved-component-names.js | 14 +++++----- .../lib/rules/no-reserved-component-names.js | 26 +++++++++++++++++++ 2 files changed, 34 insertions(+), 6 deletions(-) diff --git a/lib/rules/no-reserved-component-names.js b/lib/rules/no-reserved-component-names.js index d46d600f4..54b2c2b5c 100644 --- a/lib/rules/no-reserved-component-names.js +++ b/lib/rules/no-reserved-component-names.js @@ -219,13 +219,15 @@ module.exports = { const parent = node.parent.parent const calleeObject = utils.unwrapTypes(parent.callee.object) - if (calleeObject.type === 'Identifier' && calleeObject.name === 'Vue') { - if (parent.arguments && parent.arguments.length === 2) { - const argument = parent.arguments[0] + if (calleeObject.type === 'Identifier' && + calleeObject.name === 'Vue' && + parent.arguments && + parent.arguments.length === 2 + ) { + const argument = parent.arguments[0] - if (canVerify(argument)) { - reportIfInvalid(argument) - } + if (canVerify(argument)) { + reportIfInvalid(argument) } } } diff --git a/tests/lib/rules/no-reserved-component-names.js b/tests/lib/rules/no-reserved-component-names.js index 2d9a28d08..e038d1221 100644 --- a/tests/lib/rules/no-reserved-component-names.js +++ b/tests/lib/rules/no-reserved-component-names.js @@ -354,6 +354,20 @@ ruleTester.run('no-reserved-component-names', rule, { 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 } ], @@ -385,6 +399,18 @@ ruleTester.run('no-reserved-component-names', rule, { line: 1 }] } + }), + ...invalidElements.map(name => { + return { + filename: 'test.vue', + code: `Vue.component(\`${name}\`, {})`, + parserOptions, + errors: [{ + message: `Name "${name}" is reserved.`, + type: 'TemplateLiteral', + line: 1 + }] + } }) ] }) From 33f59172ad2c9f4dfca748fb4a581f620ccde89d Mon Sep 17 00:00:00 2001 From: Jake Hassel Date: Mon, 7 Jan 2019 23:17:19 -0500 Subject: [PATCH 3/7] Checking elements against element lists --- docs/rules/no-reserved-component-names.md | 1 + lib/rules/no-reserved-component-names.js | 155 +----- .../lib/rules/no-reserved-component-names.js | 484 +++++++----------- 3 files changed, 199 insertions(+), 441 deletions(-) diff --git a/docs/rules/no-reserved-component-names.md b/docs/rules/no-reserved-component-names.md index b5cc92f97..073ea5edb 100644 --- a/docs/rules/no-reserved-component-names.md +++ b/docs/rules/no-reserved-component-names.md @@ -23,4 +23,5 @@ export default { ## :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) \ No newline at end of file diff --git a/lib/rules/no-reserved-component-names.js b/lib/rules/no-reserved-component-names.js index 54b2c2b5c..0507a8cb4 100644 --- a/lib/rules/no-reserved-component-names.js +++ b/lib/rules/no-reserved-component-names.js @@ -7,6 +7,9 @@ const utils = require('../utils') const casing = require('../utils/casing') +const htmlElements = require('../utils/html-elements.json') +const svgElements = require('../utils/svg-elements.json') + const kebabCaseElements = [ 'annotation-xml', 'color-profile', @@ -18,155 +21,17 @@ const kebabCaseElements = [ 'missing-glyph' ] -const elementNames = [ - 'a', - 'abbr', - 'acronym', - 'address', - 'applet', - 'area', - 'article', - 'aside', - 'audio', - 'b', - 'base', - 'basefont', - 'bdi', - 'bdo', - 'bgsound', - 'big', - 'blink', - 'blockquote', - 'body', - 'br', - 'button', - 'canvas', - 'caption', - 'center', - 'cite', - 'code', - 'col', - 'colgroup', - 'command', - 'content', - 'data', - 'datalist', - 'dd', - 'del', - 'details', - 'dfn', - 'dialog', - 'dir', - 'div', - 'dl', - 'dt', - 'element', - 'em', - 'embed', - 'fieldset', - 'figcaption', - 'figure', - 'font', - 'footer', - 'form', - 'frame', - 'frameset', - 'h1', - 'head', - 'header', - 'hgroup', - 'hr', - 'html', - 'i', - 'iframe', - 'image', - 'img', - 'input', - 'ins', - 'isindex', - 'kbd', - 'keygen', - 'label', - 'legend', - 'li', - 'link', - 'listing', - 'main', - 'map', - 'mark', - 'marquee', - 'menu', - 'menuitem', - 'meta', - 'meter', - 'multicol', - 'nav', - 'nextid', - 'nobr', - 'noembed', - 'noframes', - 'noscript', - 'object', - 'ol', - 'optgroup', - 'option', - 'output', - 'p', - 'param', - 'picture', - 'plaintext', - 'pre', - 'progress', - 'q', - 'rb', - 'rp', - 'rt', - 'rtc', - 'ruby', - 's', - 'samp', - 'script', - 'section', - 'select', - 'shadow', - 'slot', - 'small', - 'source', - 'spacer', - 'span', - 'strike', - 'strong', - 'style', - 'sub', - 'summary', - 'sup', - 'table', - 'tbody', - 'td', - 'template', - 'textarea', - 'tfoot', - 'th', - 'thead', - 'time', - 'title', - 'tr', - 'track', - 'tt', - 'u', - 'ul', - 'var', - 'video', - 'wbr', - 'xmp' -] +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(name => casing.pascalCase(name)), - ...elementNames, - ...elementNames.map(name => name[0].toUpperCase() + name.substring(1, name.length)) + ...kebabCaseElements.map(casing.pascalCase), + ...htmlElements, + ...htmlElements.map(capitalizeFirstLetter), + ...svgElements, + ...svgElements.filter(isLowercase).map(capitalizeFirstLetter) ]) // ------------------------------------------------------------------------------ diff --git a/tests/lib/rules/no-reserved-component-names.js b/tests/lib/rules/no-reserved-component-names.js index e038d1221..9eaab499c 100644 --- a/tests/lib/rules/no-reserved-component-names.js +++ b/tests/lib/rules/no-reserved-component-names.js @@ -16,303 +16,195 @@ const RuleTester = require('eslint').RuleTester // ------------------------------------------------------------------------------ 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', - 'a', - 'abbr', - 'acronym', - 'address', - 'applet', - 'area', - 'article', - 'aside', - 'audio', - 'b', - 'base', - 'basefont', - 'bdi', - 'bdo', - 'bgsound', - 'big', - 'blink', - 'blockquote', - 'body', - 'br', - 'button', - 'canvas', - 'caption', - 'center', - 'cite', - 'code', - 'col', - 'colgroup', - 'command', - 'content', - 'data', - 'datalist', - 'dd', - 'del', - 'details', - 'dfn', - 'dialog', - 'dir', - 'div', - 'dl', - 'dt', - 'element', - 'em', - 'embed', - 'fieldset', - 'figcaption', - 'figure', - 'font', - 'footer', - 'form', - 'frame', - 'frameset', - 'h1', - 'head', - 'header', - 'hgroup', - 'hr', - 'html', - 'i', - 'iframe', - 'image', - 'img', - 'input', - 'ins', - 'isindex', - 'kbd', - 'keygen', - 'label', - 'legend', - 'li', - 'link', - 'listing', - 'main', - 'map', - 'mark', - 'marquee', - 'menu', - 'menuitem', - 'meta', - 'meter', - 'multicol', - 'nav', - 'nextid', - 'nobr', - 'noembed', - 'noframes', - 'noscript', - 'object', - 'ol', - 'optgroup', - 'option', - 'output', - 'p', - 'param', - 'picture', - 'plaintext', - 'pre', - 'progress', - 'q', - 'rb', - 'rp', - 'rt', - 'rtc', - 'ruby', - 's', - 'samp', - 'script', - 'section', - 'select', - 'shadow', - 'slot', - 'small', - 'source', - 'spacer', - 'span', - 'strike', - 'strong', - 'style', - 'sub', - 'summary', - 'sup', - 'table', - 'tbody', - 'td', - 'template', - 'textarea', - 'tfoot', - 'th', - 'thead', - 'time', - 'title', - 'tr', - 'track', - 'tt', - 'u', - 'ul', - 'var', - 'video', - 'wbr', - 'xmp', + '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', - 'A', - 'Abbr', - 'Acronym', - 'Address', - 'Applet', - 'Area', - 'Article', - 'Aside', - 'Audio', - 'B', - 'Base', - 'Basefont', - 'Bdi', - 'Bdo', - 'Bgsound', - 'Big', - 'Blink', - 'Blockquote', - 'Body', - 'Br', - 'Button', - 'Canvas', - 'Caption', - 'Center', - 'Cite', - 'Code', - 'Col', - 'Colgroup', - 'Command', - 'Content', - 'Data', - 'Datalist', - 'Dd', - 'Del', - 'Details', - 'Dfn', - 'Dialog', - 'Dir', - 'Div', - 'Dl', - 'Dt', - 'Element', - 'Em', - 'Embed', - 'Fieldset', - 'Figcaption', - 'Figure', - 'Font', - 'Footer', - 'Form', - 'Frame', - 'Frameset', - 'H1', - 'Head', - 'Header', - 'Hgroup', - 'Hr', - 'Html', - 'I', - 'Iframe', - 'Image', - 'Img', - 'Input', - 'Ins', - 'Isindex', - 'Kbd', - 'Keygen', - 'Label', - 'Legend', - 'Li', - 'Link', - 'Listing', - 'Main', - 'Map', - 'Mark', - 'Marquee', - 'Menu', - 'Menuitem', - 'Meta', - 'Meter', - 'Multicol', - 'Nav', - 'Nextid', - 'Nobr', - 'Noembed', - 'Noframes', - 'Noscript', - 'Object', - 'Ol', - 'Optgroup', - 'Option', - 'Output', - 'P', - 'param', - 'Picture', - 'Plaintext', - 'Pre', - 'Progress', - 'Q', - 'Rb', - 'Rp', - 'Rt', - 'Rtc', - 'Ruby', - 'S', - 'Samp', - 'Script', - 'Section', - 'Select', - 'Shadow', - 'Slot', - 'Small', - 'Source', - 'Spacer', - 'Span', - 'Strike', - 'Strong', - 'Style', - 'Sub', - 'Summary', - 'Sup', - 'Table', - 'Tbody', - 'Td', - 'Template', - 'Textarea', - 'Tfoot', - 'Th', - 'Thead', - 'Time', - 'Title', - 'Tr', - 'Track', - 'Tt', - 'U', - 'Ul', - 'Var', - 'Video', - 'Wbr', - 'Xmp' + // 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' ] const parserOptions = { From 792c29b7405d2f2d454e924c1971a7c55dbf11f1 Mon Sep 17 00:00:00 2001 From: Jake Hassel Date: Tue, 8 Jan 2019 09:21:19 -0500 Subject: [PATCH 4/7] :hammer: Update PR with ota-meshi suggestions --- docs/rules/no-reserved-component-names.md | 3 ++- lib/rules/no-reserved-component-names.js | 4 ++-- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/docs/rules/no-reserved-component-names.md b/docs/rules/no-reserved-component-names.md index 073ea5edb..57a008592 100644 --- a/docs/rules/no-reserved-component-names.md +++ b/docs/rules/no-reserved-component-names.md @@ -24,4 +24,5 @@ export default { - [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) \ No newline at end of file +- [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 index 0507a8cb4..67a7452d2 100644 --- a/lib/rules/no-reserved-component-names.js +++ b/lib/rules/no-reserved-component-names.js @@ -21,7 +21,7 @@ const kebabCaseElements = [ 'missing-glyph' ] -const isLowercase = (word) => (/[a-z]/.test(word)) +const isLowercase = (word) => /^[a-z]*$/.test(word) const capitalizeFirstLetter = (word) => word[0].toUpperCase() + word.substring(1, word.length) const RESERVED_NAMES = new Set( @@ -43,7 +43,7 @@ module.exports = { type: 'suggestion', docs: { description: 'disallow the use of reserved names in component definitions', - category: 'essential', + category: undefined, // 'essential' url: 'https://eslint.vuejs.org/rules/no-reserved-component-names.html' }, fixable: null, From 8975ab019ac090434a53ef7395789b6febd427f5 Mon Sep 17 00:00:00 2001 From: Jake Hassel Date: Wed, 9 Jan 2019 15:40:48 -0500 Subject: [PATCH 5/7] :hammer: Lint locally registered components --- lib/rules/no-reserved-component-names.js | 31 ++++++++++++------- .../lib/rules/no-reserved-component-names.js | 16 ++++++++++ 2 files changed, 36 insertions(+), 11 deletions(-) diff --git a/lib/rules/no-reserved-component-names.js b/lib/rules/no-reserved-component-names.js index 67a7452d2..ef7d7b900 100644 --- a/lib/rules/no-reserved-component-names.js +++ b/lib/rules/no-reserved-component-names.js @@ -60,24 +60,28 @@ module.exports = { } function reportIfInvalid (node) { - let nodeValue + let name if (node.type === 'TemplateLiteral') { const quasis = node.quasis[0] - nodeValue = quasis.value.cooked + name = quasis.value.cooked } else { - nodeValue = node.value + name = node.value } - if (RESERVED_NAMES.has(nodeValue)) { - context.report({ - node: node, - message: 'Name "{{value}}" is reserved.', - data: { - value: nodeValue - } - }) + 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) { @@ -98,6 +102,11 @@ module.exports = { } }, 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' && diff --git a/tests/lib/rules/no-reserved-component-names.js b/tests/lib/rules/no-reserved-component-names.js index 9eaab499c..a6f42d429 100644 --- a/tests/lib/rules/no-reserved-component-names.js +++ b/tests/lib/rules/no-reserved-component-names.js @@ -303,6 +303,22 @@ ruleTester.run('no-reserved-component-names', rule, { 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 52d5508f03e270cff1f084882e0e3c5d0a8ef390 Mon Sep 17 00:00:00 2001 From: Jake Hassel Date: Wed, 9 Jan 2019 22:57:29 -0500 Subject: [PATCH 6/7] :ok_hand: Adding tests to validate slot and template --- .../lib/rules/no-reserved-component-names.js | 24 +++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/tests/lib/rules/no-reserved-component-names.js b/tests/lib/rules/no-reserved-component-names.js index a6f42d429..9dd9cd79a 100644 --- a/tests/lib/rules/no-reserved-component-names.js +++ b/tests/lib/rules/no-reserved-component-names.js @@ -260,6 +260,30 @@ ruleTester.run('no-reserved-component-names', rule, { 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 } ], From c4328d113b9e9c432991cb91e701a465a51e790d Mon Sep 17 00:00:00 2001 From: Jake Hassel Date: Wed, 9 Jan 2019 23:37:50 -0500 Subject: [PATCH 7/7] :hammer: Linting for deprecated elements --- lib/rules/no-reserved-component-names.js | 3 ++ lib/utils/deprecated-html-elements.json | 1 + .../lib/rules/no-reserved-component-names.js | 29 ++++++++++++++++++- 3 files changed, 32 insertions(+), 1 deletion(-) create mode 100644 lib/utils/deprecated-html-elements.json diff --git a/lib/rules/no-reserved-component-names.js b/lib/rules/no-reserved-component-names.js index ef7d7b900..c08b34f5d 100644 --- a/lib/rules/no-reserved-component-names.js +++ b/lib/rules/no-reserved-component-names.js @@ -8,6 +8,7 @@ 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 = [ @@ -30,6 +31,8 @@ const RESERVED_NAMES = new Set( ...kebabCaseElements.map(casing.pascalCase), ...htmlElements, ...htmlElements.map(capitalizeFirstLetter), + ...deprecatedHtmlElements, + ...deprecatedHtmlElements.map(capitalizeFirstLetter), ...svgElements, ...svgElements.filter(isLowercase).map(capitalizeFirstLetter) ]) 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 index 9dd9cd79a..6d592396d 100644 --- a/tests/lib/rules/no-reserved-component-names.js +++ b/tests/lib/rules/no-reserved-component-names.js @@ -204,7 +204,34 @@ const invalidElements = [ 'tspan', 'Tspan', 'unknown', 'Unknown', 'use', 'Use', - 'view', 'View' + '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 = {