From b7b4991f7523e71eadc5d3abaebfa14ac9c96e56 Mon Sep 17 00:00:00 2001 From: Armano Date: Sun, 6 Aug 2017 22:43:22 +0200 Subject: [PATCH 1/3] Allow to use @vue/component to set enable parsing vue objects fixes #109 --- README.md | 32 +++- lib/utils/index.js | 34 +++- tests/lib/utils/vue-component.js | 277 +++++++++++++++++++++++++++++++ 3 files changed, 339 insertions(+), 4 deletions(-) create mode 100644 tests/lib/utils/vue-component.js diff --git a/README.md b/README.md index 855ca4200..17efac0a4 100644 --- a/README.md +++ b/README.md @@ -31,7 +31,7 @@ Create `.eslintrc.*` file to configure rules. See also: [http://eslint.org/docs/ Example **.eslintrc.js**: -```javascript +```js module.exports = { extends: [ 'eslint:recommended', @@ -44,7 +44,35 @@ module.exports = { } ``` -## ⚙ Configs +### Attention + +All component-related rules are being applied to code that passes any of the following checks: + +* `Vue.component()` expression +* `export default {}` in `.vue` or `.jsx` file + +If you however want to take advantage of our rules in any of your custom objects that are Vue components, you might need to use special comment `// @vue/component` that marks object in the next line as a Vue component in any file, e.g.: + +```js +// @vue/component +const CustomComponent = { + name: 'custom-component', + template: '
' +} +``` +```js +Vue.component('AsyncComponent', (resolve, reject) => { + setTimeout(() => { + // @vue/component + resolve({ + name: 'async-component', + template: '
' + }) + }, 500) +}) +``` + +## :gear: Configs This plugin provides two predefined configs: - `plugin:vue/base` - contains necessary settings for this plugin to work properly diff --git a/lib/utils/index.js b/lib/utils/index.js index 3a3428268..e64fcef6f 100644 --- a/lib/utils/index.js +++ b/lib/utils/index.js @@ -432,21 +432,51 @@ module.exports = { executeOnVueComponent (context, cb) { const filePath = context.getFilename() const _this = this + const componentComments = [] + const foundNodes = [] + + function isDuplicateNode (node) { + return foundNodes.some(el => el.loc.start.line === node.loc.start.line) + } return { + LineComment (node) { + // line comment with @vue/component + if (!_this.isVueComponentComment(node.value)) return + componentComments.push(node) + }, + BlockComment (node) { + // block comment with @vue/component + if (!_this.isVueComponentComment(node.value)) return + componentComments.push(node) + }, + ObjectExpression (node) { + if (!componentComments.some(el => el.loc.end.line === node.loc.start.line - 1) || isDuplicateNode(node)) return + foundNodes.push(node) + cb(node) + }, 'ExportDefaultDeclaration:exit' (node) { // export default {} in .vue || .jsx - if (!_this.isVueComponentFile(node, filePath)) return + if (!_this.isVueComponentFile(node, filePath) || isDuplicateNode(node)) return cb(node.declaration) }, 'CallExpression:exit' (node) { // Vue.component('xxx', {}) || component('xxx', {}) - if (!_this.isVueComponent(node)) return + if (!_this.isVueComponent(node) || isDuplicateNode(node)) return cb(node.arguments.slice(-1)[0]) } } }, + /** + * Check whether the given comment is a Vue component based + * @param {string} value Conent of comment. + * @return {boolean} + */ + isVueComponentComment (value) { + return /@vue\/component/g.test(value) + }, + /** * Return generator with all properties * @param {ASTNode} node Node to check diff --git a/tests/lib/utils/vue-component.js b/tests/lib/utils/vue-component.js new file mode 100644 index 000000000..38b696333 --- /dev/null +++ b/tests/lib/utils/vue-component.js @@ -0,0 +1,277 @@ +/** + * @author Armano + */ +'use strict' + +const utils = require('../../../lib/utils/index') + +// ------------------------------------------------------------------------------ +// Requirements +// ------------------------------------------------------------------------------ + +const rule = { + create (context) { + return utils.executeOnVueComponent(context, obj => { + context.report({ + node: obj, + message: 'Component detected.' + }) + }) + }, + meta: { + fixable: null, + schema: [] + } +} + +const RuleTester = require('eslint').RuleTester +const parserOptions = { + ecmaVersion: 6, + sourceType: 'module' +} + +function makeError (line) { + return { + message: 'Component detected.', + line, + type: 'ObjectExpression' + } +} + +function validTests (ext) { + return [ + { + filename: `test.${ext}`, + code: `export const foo = {}`, + parserOptions + }, + { + filename: `test.${ext}`, + code: `export var foo = {}`, + parserOptions + }, + { + filename: `test.${ext}`, + code: `const foo = {}`, + parserOptions + }, + { + filename: `test.${ext}`, + code: `var foo = {}`, + parserOptions + }, + { + filename: `test.${ext}`, + code: `let foo = {}`, + parserOptions + }, + { + filename: `test.${ext}`, + code: `foo({ })`, + parserOptions + }, + { + filename: `test.${ext}`, + code: `foo(() => { return {} })`, + parserOptions + }, + { + filename: `test.${ext}`, + code: `Vue.component('async-example', function (resolve, reject) { })`, + parserOptions + }, + { + filename: `test.${ext}`, + code: `Vue.component('async-example', function (resolve, reject) { resolve({}) })`, + parserOptions + }, + { + filename: `test.${ext}`, + code: `new Vue({ })`, + parserOptions + }, + { + filename: `test.${ext}`, + code: `{ + foo: {} + }`, + parserOptions + } + ] +} + +function invalidTests (ext) { + return [ + { + filename: `test.${ext}`, + code: ` + Vue.component('async-example', function (resolve, reject) { + // @vue/component + resolve({}) + }) + // ${ext} + `, + parserOptions, + errors: [makeError(4)] + }, + { + filename: `test.${ext}`, + code: `Vue.component({})`, + parserOptions, + errors: [makeError(1)] + }, + { + filename: `test.${ext}`, + code: ` + // @vue/component + export default { } + // ${ext} + `, + parserOptions, + errors: [makeError(3)] + }, + { + filename: `test.${ext}`, + code: ` + /* @vue/component */ + export default { } + // ${ext} + `, + parserOptions, + errors: [makeError(3)] + }, + { + filename: `test.${ext}`, + code: ` + /* + * ext: ${ext} + * @vue/component + */ + export default { } + // ${ext} + `, + parserOptions, + errors: [makeError(6)] + }, + { + filename: `test.${ext}`, + code: ` + // @vue/component + export default { } + // @vue/component + export var a = { } + // ${ext} + `, + parserOptions, + errors: [makeError(3), makeError(5)] + }, + { + filename: `test.${ext}`, + code: ` + /* @vue/component */ + export const foo = { } + /* @vue/component */ + export default { } + // ${ext} + `, + parserOptions, + errors: [makeError(3), makeError(5)] + }, + { + filename: `test.${ext}`, + code: ` + export default { } + // @vue/component + export let foo = { } + // ${ext} + `, + parserOptions, + errors: (ext === 'js' ? [] : [makeError(2)]).concat([makeError(4)]) + }, + { + filename: `test.${ext}`, + code: ` + let foo = { } + // @vue/component + export let bar = { } + // ${ext} + `, + parserOptions, + errors: [makeError(4)] + }, + { + filename: `test.${ext}`, + code: ` + export var dar = { } + // @vue/component + foo({ }) + bar({ }) + // ${ext} + `, + parserOptions, + errors: [makeError(4)] + }, + { + filename: `test.${ext}`, + code: ` + foo({ }) + export default { + test: {}, + // @vue/component + foo: { } + } + bar({ }) + // ${ext} + `, + parserOptions, + errors: (ext === 'js' ? [] : [makeError(3)]).concat([makeError(6)]) + }, + { + filename: `test.${ext}`, + code: ` + export default { + bar () { + return {} + }, + foo () { + // @vue/component + return {} + } + } + // ${ext} + `, + parserOptions, + errors: (ext === 'js' ? [] : [makeError(2)]).concat([makeError(8)]) + } + ] +} + +// ------------------------------------------------------------------------------ +// Tests +// ------------------------------------------------------------------------------ + +const ruleTester = new RuleTester() +ruleTester.run('vue-component', rule, { + + valid: [ + { + filename: 'test.js', + code: `export default { }`, + parserOptions + } + ].concat(validTests('js')).concat(validTests('jsx')).concat(validTests('vue')), + invalid: [ + { + filename: 'test.vue', + code: `export default { }`, + parserOptions, + errors: [makeError(1)] + }, + { + filename: 'test.jsx', + code: `export default { }`, + parserOptions, + errors: [makeError(1)] + } + ].concat(invalidTests('js')).concat(invalidTests('jsx')).concat(invalidTests('vue')) +}) From 9db65698ce9a7c7b0992ba9380b4255eeb3bba07 Mon Sep 17 00:00:00 2001 From: Armano Date: Sun, 6 Aug 2017 23:49:38 +0200 Subject: [PATCH 2/3] Add support for eslint 4.x LineComment and BlockComment was removed in 4.0.0 --- lib/utils/index.js | 24 ++++++++---------------- 1 file changed, 8 insertions(+), 16 deletions(-) diff --git a/lib/utils/index.js b/lib/utils/index.js index e64fcef6f..83fccaac4 100644 --- a/lib/utils/index.js +++ b/lib/utils/index.js @@ -431,38 +431,30 @@ module.exports = { */ executeOnVueComponent (context, cb) { const filePath = context.getFilename() + const sourceCode = context.getSourceCode() const _this = this - const componentComments = [] + const componentComments = sourceCode.getAllComments().filter(comment => this.isVueComponentComment(comment.value)) const foundNodes = [] - function isDuplicateNode (node) { - return foundNodes.some(el => el.loc.start.line === node.loc.start.line) + const isDuplicateNode = (node) => { + if (foundNodes.some(el => el.loc.start.line === node.loc.start.line)) return true + foundNodes.push(node) + return false } return { - LineComment (node) { - // line comment with @vue/component - if (!_this.isVueComponentComment(node.value)) return - componentComments.push(node) - }, - BlockComment (node) { - // block comment with @vue/component - if (!_this.isVueComponentComment(node.value)) return - componentComments.push(node) - }, ObjectExpression (node) { if (!componentComments.some(el => el.loc.end.line === node.loc.start.line - 1) || isDuplicateNode(node)) return - foundNodes.push(node) cb(node) }, 'ExportDefaultDeclaration:exit' (node) { // export default {} in .vue || .jsx - if (!_this.isVueComponentFile(node, filePath) || isDuplicateNode(node)) return + if (!_this.isVueComponentFile(node, filePath) || isDuplicateNode(node.declaration)) return cb(node.declaration) }, 'CallExpression:exit' (node) { // Vue.component('xxx', {}) || component('xxx', {}) - if (!_this.isVueComponent(node) || isDuplicateNode(node)) return + if (!_this.isVueComponent(node) || isDuplicateNode(node.arguments.slice(-1)[0])) return cb(node.arguments.slice(-1)[0]) } } From ba38e5af25460564ca68346599ebf158c9fb85cd Mon Sep 17 00:00:00 2001 From: Armano Date: Mon, 7 Aug 2017 00:11:58 +0200 Subject: [PATCH 3/3] Remove unessesery function --- lib/utils/index.js | 11 +---------- 1 file changed, 1 insertion(+), 10 deletions(-) diff --git a/lib/utils/index.js b/lib/utils/index.js index 83fccaac4..ccac6cdd4 100644 --- a/lib/utils/index.js +++ b/lib/utils/index.js @@ -433,7 +433,7 @@ module.exports = { const filePath = context.getFilename() const sourceCode = context.getSourceCode() const _this = this - const componentComments = sourceCode.getAllComments().filter(comment => this.isVueComponentComment(comment.value)) + const componentComments = sourceCode.getAllComments().filter(comment => /@vue\/component/g.test(comment.value)) const foundNodes = [] const isDuplicateNode = (node) => { @@ -460,15 +460,6 @@ module.exports = { } }, - /** - * Check whether the given comment is a Vue component based - * @param {string} value Conent of comment. - * @return {boolean} - */ - isVueComponentComment (value) { - return /@vue\/component/g.test(value) - }, - /** * Return generator with all properties * @param {ASTNode} node Node to check