diff --git a/lib/rules/require-default-prop.js b/lib/rules/require-default-prop.js index d3f4ad9fa..3d75387b3 100644 --- a/lib/rules/require-default-prop.js +++ b/lib/rules/require-default-prop.js @@ -32,7 +32,7 @@ module.exports = { * @return {boolean} */ function propIsRequired (prop) { - const propRequiredNode = prop.value.properties + const propRequiredNode = utils.unwrapTypes(prop.value).properties .find(p => p.type === 'Property' && p.key.name === 'required' && @@ -49,7 +49,7 @@ module.exports = { * @return {boolean} */ function propHasDefault (prop) { - const propDefaultNode = prop.value.properties + const propDefaultNode = utils.unwrapTypes(prop.value).properties .find(p => p.key && (p.key.name === 'default' || p.key.value === 'default') @@ -67,7 +67,7 @@ module.exports = { return propsNode.value.properties .filter(prop => prop.type === 'Property') .filter(prop => { - if (prop.value.type !== 'ObjectExpression') { + if (utils.unwrapTypes(prop.value).type !== 'ObjectExpression') { return true } @@ -98,7 +98,7 @@ module.exports = { * @return {Boolean} */ function isBooleanProp (prop) { - const value = prop.value + const value = utils.unwrapTypes(prop.value) return isValueNodeOfBooleanType(value) || ( value.type === 'ObjectExpression' && diff --git a/lib/rules/require-prop-type-constructor.js b/lib/rules/require-prop-type-constructor.js index c000e4530..abc768485 100644 --- a/lib/rules/require-prop-type-constructor.js +++ b/lib/rules/require-prop-type-constructor.js @@ -81,17 +81,18 @@ module.exports = { node.value.properties .forEach(p => { - if (isForbiddenType(p.value) || p.value.type === 'ArrayExpression') { - checkPropertyNode(p.key, p.value) - } else if (p.value.type === 'ObjectExpression') { - const typeProperty = p.value.properties.find(prop => + const pValue = utils.unwrapTypes(p.value) + if (isForbiddenType(pValue) || pValue.type === 'ArrayExpression') { + checkPropertyNode(p.key, pValue) + } else if (pValue.type === 'ObjectExpression') { + const typeProperty = pValue.properties.find(prop => prop.type === 'Property' && prop.key.name === 'type' ) if (!typeProperty) return - checkPropertyNode(p.key, typeProperty.value) + checkPropertyNode(p.key, utils.unwrapTypes(typeProperty.value)) } }) }) diff --git a/lib/rules/require-prop-types.js b/lib/rules/require-prop-types.js index 33c0e84ac..0899426bf 100644 --- a/lib/rules/require-prop-types.js +++ b/lib/rules/require-prop-types.js @@ -48,11 +48,13 @@ module.exports = { return } let hasType = true - if (cp.value.type === 'ObjectExpression') { // foo: { - hasType = objectHasType(cp.value) - } else if (cp.value.type === 'ArrayExpression') { // foo: [ - hasType = cp.value.elements.length > 0 - } else if (cp.value.type === 'FunctionExpression' || cp.value.type === 'ArrowFunctionExpression') { + const cpValue = utils.unwrapTypes(cp.value) + + if (cpValue.type === 'ObjectExpression') { // foo: { + hasType = objectHasType(cpValue) + } else if (cpValue.type === 'ArrayExpression') { // foo: [ + hasType = cpValue.elements.length > 0 + } else if (cpValue.type === 'FunctionExpression' || cpValue.type === 'ArrowFunctionExpression') { hasType = false } if (!hasType) { diff --git a/lib/rules/require-valid-default-prop.js b/lib/rules/require-valid-default-prop.js index a88fca104..21e1f2537 100644 --- a/lib/rules/require-valid-default-prop.js +++ b/lib/rules/require-valid-default-prop.js @@ -97,11 +97,11 @@ module.exports = { const properties = props.value.properties.filter(p => isPropertyIdentifier(p) && - p.value.type === 'ObjectExpression' + utils.unwrapTypes(p.value).type === 'ObjectExpression' ) for (const prop of properties) { - const type = getPropertyNode(prop.value, 'type') + const type = getPropertyNode(utils.unwrapTypes(prop.value), 'type') if (!type) continue const typeNames = new Set(getTypes(type.value) @@ -111,7 +111,7 @@ module.exports = { // There is no native types detected if (typeNames.size === 0) continue - const def = getPropertyNode(prop.value, 'default') + const def = getPropertyNode(utils.unwrapTypes(prop.value), 'default') if (!def) continue const defType = getValueType(def.value) diff --git a/lib/utils/index.js b/lib/utils/index.js index 8c39c63d8..4a8a8cb11 100644 --- a/lib/utils/index.js +++ b/lib/utils/index.js @@ -426,24 +426,32 @@ module.exports = { * @returns {boolean} */ isVueComponent (node) { - const callee = node.callee + if (node.type === 'CallExpression') { + const callee = node.callee - const isFullVueComponent = node.type === 'CallExpression' && - callee.type === 'MemberExpression' && - callee.object.type === 'Identifier' && - callee.object.name === 'Vue' && - callee.property.type === 'Identifier' && - ['component', 'mixin', 'extend'].indexOf(callee.property.name) > -1 && - node.arguments.length >= 1 && - node.arguments.slice(-1)[0].type === 'ObjectExpression' + if (callee.type === 'MemberExpression') { + const calleeObject = this.unwrapTypes(callee.object) - const isDestructedVueComponent = node.type === 'CallExpression' && - callee.type === 'Identifier' && - callee.name === 'component' && - node.arguments.length >= 1 && - node.arguments.slice(-1)[0].type === 'ObjectExpression' + const isFullVueComponent = calleeObject.type === 'Identifier' && + calleeObject.name === 'Vue' && + callee.property.type === 'Identifier' && + ['component', 'mixin', 'extend'].indexOf(callee.property.name) > -1 && + node.arguments.length >= 1 && + node.arguments.slice(-1)[0].type === 'ObjectExpression' + + return isFullVueComponent + } - return isFullVueComponent || isDestructedVueComponent + if (callee.type === 'Identifier') { + const isDestructedVueComponent = callee.name === 'component' && + node.arguments.length >= 1 && + node.arguments.slice(-1)[0].type === 'ObjectExpression' + + return isDestructedVueComponent + } + } + + return false }, /** @@ -671,7 +679,7 @@ module.exports = { /** * Parse CallExpression or MemberExpression to get simplified version without arguments * - * @param {Object} node The node to parse (MemberExpression | CallExpression) + * @param {ASTNode} node The node to parse (MemberExpression | CallExpression) * @return {String} eg. 'this.asd.qwe().map().filter().test.reduce()' */ parseMemberOrCallExpression (node) { @@ -703,5 +711,14 @@ module.exports = { } return parsedCallee.reverse().join('.').replace(/\.\[/g, '[') + }, + + /** + * Unwrap typescript types like "X as F" + * @param {ASTNode} node + * @return {ASTNode} + */ + unwrapTypes (node) { + return node.type === 'TSAsExpression' ? node.expression : node } } diff --git a/package.json b/package.json index d5bc96733..e713d6dac 100644 --- a/package.json +++ b/package.json @@ -56,6 +56,8 @@ "eslint-plugin-vue-libs": "^3.0.0", "lodash": "^4.17.4", "mocha": "^5.2.0", - "nyc": "^12.0.2" + "nyc": "^12.0.2", + "typescript": "^3.1.3", + "typescript-eslint-parser": "^20.0.0" } } diff --git a/tests/lib/rules/no-side-effects-in-computed-properties.js b/tests/lib/rules/no-side-effects-in-computed-properties.js index 55df66734..7367bc87a 100644 --- a/tests/lib/rules/no-side-effects-in-computed-properties.js +++ b/tests/lib/rules/no-side-effects-in-computed-properties.js @@ -248,6 +248,24 @@ ruleTester.run('no-side-effects-in-computed-properties', rule, { line: 23, message: 'Unexpected side effect in "test4" computed property.' }] + }, + { + filename: 'test.vue', + code: ` + export default Vue.extend({ + computed: { + test1() : string { + return this.something.reverse() + } + } + }); + `, + parserOptions, + errors: [{ + line: 5, + message: 'Unexpected side effect in "test1" computed property.' + }], + parser: 'typescript-eslint-parser' } ] }) diff --git a/tests/lib/rules/require-default-prop.js b/tests/lib/rules/require-default-prop.js index e5f2f3462..cdbecaca6 100644 --- a/tests/lib/rules/require-default-prop.js +++ b/tests/lib/rules/require-default-prop.js @@ -113,6 +113,34 @@ ruleTester.run('require-default-prop', rule, { } } ` + }, + { + filename: 'test.vue', + code: ` + export default (Vue as VueConstructor).extend({ + props: { + a: { + type: String, + required: true + } as PropOptions + } + }); + `, + parser: 'typescript-eslint-parser' + }, + { + filename: 'test.vue', + code: ` + export default Vue.extend({ + props: { + a: { + type: String, + required: true + } as PropOptions + } + }); + `, + parser: 'typescript-eslint-parser' } ], @@ -157,6 +185,40 @@ ruleTester.run('require-default-prop', rule, { message: `Prop 'f' requires default value to be set.`, line: 14 }] + }, + { + filename: 'test.vue', + code: ` + export default (Vue as VueConstructor).extend({ + props: { + a: { + type: String + } as PropOptions + } + }); + `, + parser: 'typescript-eslint-parser', + errors: [{ + message: `Prop 'a' requires default value to be set.`, + line: 4 + }] + }, + { + filename: 'test.vue', + code: ` + export default Vue.extend({ + props: { + a: { + type: String + } as PropOptions + } + }); + `, + parser: 'typescript-eslint-parser', + errors: [{ + message: `Prop 'a' requires default value to be set.`, + line: 4 + }] } ] }) diff --git a/tests/lib/rules/require-prop-type-constructor.js b/tests/lib/rules/require-prop-type-constructor.js index 92dc9877f..81a189ef8 100644 --- a/tests/lib/rules/require-prop-type-constructor.js +++ b/tests/lib/rules/require-prop-type-constructor.js @@ -136,6 +136,34 @@ ruleTester.run('require-prop-type-constructor', rule, { message: 'The "d" property should be a constructor.', line: 7 }] + }, + { + filename: 'SomeComponent.vue', + code: ` + export default { + props: { + a: { + type: 'String', + default: 10 + } as PropOptions, + } + } + `, + output: ` + export default { + props: { + a: { + type: String, + default: 10 + } as PropOptions, + } + } + `, + errors: [{ + message: 'The "a" property should be a constructor.', + line: 5 + }], + parser: 'typescript-eslint-parser' } ] }) diff --git a/tests/lib/rules/require-prop-types.js b/tests/lib/rules/require-prop-types.js index 326fbd56b..3cd2adcd1 100644 --- a/tests/lib/rules/require-prop-types.js +++ b/tests/lib/rules/require-prop-types.js @@ -113,6 +113,34 @@ ruleTester.run('require-prop-types', rule, { } `, parserOptions: { ecmaVersion: 6, sourceType: 'module' } + }, + { + filename: 'test.vue', + code: ` + export default (Vue as VueConstructor).extend({ + props: { + foo: { + type: String + } as PropOptions + } + }); + `, + parserOptions: { ecmaVersion: 6, sourceType: 'module' }, + parser: 'typescript-eslint-parser' + }, + { + filename: 'test.vue', + code: ` + export default Vue.extend({ + props: { + foo: { + type: String + } as PropOptions + } + }); + `, + parserOptions: { ecmaVersion: 6, sourceType: 'module' }, + parser: 'typescript-eslint-parser' } ], @@ -190,6 +218,38 @@ ruleTester.run('require-prop-types', rule, { message: 'Prop "foo" should define at least its type.', line: 4 }] + }, + { + filename: 'test.vue', + code: ` + export default Vue.extend({ + props: { + foo: {} as PropOptions + } + }); + `, + parserOptions: { ecmaVersion: 6, sourceType: 'module' }, + parser: 'typescript-eslint-parser', + errors: [{ + message: 'Prop "foo" should define at least its type.', + line: 4 + }] + }, + { + filename: 'test.vue', + code: ` + export default (Vue as VueConstructor).extend({ + props: { + foo: {} as PropOptions + } + }); + `, + parserOptions: { ecmaVersion: 6, sourceType: 'module' }, + parser: 'typescript-eslint-parser', + errors: [{ + message: 'Prop "foo" should define at least its type.', + line: 4 + }] } ] }) diff --git a/tests/lib/rules/require-valid-default-prop.js b/tests/lib/rules/require-valid-default-prop.js index 241b7ff06..4d434f2a8 100644 --- a/tests/lib/rules/require-valid-default-prop.js +++ b/tests/lib/rules/require-valid-default-prop.js @@ -100,6 +100,21 @@ ruleTester.run('require-valid-default-prop', rule, { } })`, parserOptions + }, + { + filename: 'test.vue', + code: ` + export default (Vue as VueConstructor).extend({ + props: { + foo: { + type: [Object, Number], + default: 10 + } as PropOptions + } + }); + `, + parserOptions: { ecmaVersion: 6, sourceType: 'module' }, + parser: 'typescript-eslint-parser' } ], @@ -415,6 +430,20 @@ ruleTester.run('require-valid-default-prop', rule, { }`, parserOptions, errors: errorMessage('function or number') + }, + { + filename: 'test.vue', + code: `export default (Vue as VueConstructor).extend({ + props: { + foo: { + type: [Object, Number], + default: {} + } as PropOptions + } + });`, + parserOptions: { ecmaVersion: 6, sourceType: 'module' }, + parser: 'typescript-eslint-parser', + errors: errorMessage('function or number') } ] }) diff --git a/tests/lib/utils/vue-component.js b/tests/lib/utils/vue-component.js index 87b8738af..e11dbcb36 100644 --- a/tests/lib/utils/vue-component.js +++ b/tests/lib/utils/vue-component.js @@ -96,6 +96,18 @@ function validTests (ext) { foo: {} }`, parserOptions + }, + { + filename: `test.${ext}`, + code: `export default (Foo as FooConstructor).extend({})`, + parser: 'typescript-eslint-parser', + parserOptions + }, + { + filename: `test.${ext}`, + code: `export default Foo.extend({})`, + parser: 'typescript-eslint-parser', + parserOptions } ] } @@ -132,6 +144,20 @@ function invalidTests (ext) { parserOptions, errors: [makeError(1)] }, + { + filename: `test.${ext}`, + code: `export default (Vue as VueConstructor).extend({})`, + parser: 'typescript-eslint-parser', + parserOptions, + errors: [makeError(1)] + }, + { + filename: `test.${ext}`, + code: `export default Vue.extend({})`, + parser: 'typescript-eslint-parser', + parserOptions, + errors: [makeError(1)] + }, { filename: `test.${ext}`, code: `