Skip to content

Commit badd051

Browse files
armano2michalsnik
authored andcommittedNov 6, 2018
#564 #575 Fix issues with Typescript (#613)
* Add typescript specific changes * Add "as" helper * Fix vue/require-default-prop * Fix vue/require-prop-types * Allow to use `export default (Vue as VueConstructor<Vue>).extend({` syntax Fix issues: #564 #575 * Fix vue/require-prop-type-constructor * Fix vue/require-valid-default-prop * Add more tests in utils
1 parent 2ddcec9 commit badd051

12 files changed

+279
-34
lines changed
 

‎lib/rules/require-default-prop.js

+4-4
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ module.exports = {
3232
* @return {boolean}
3333
*/
3434
function propIsRequired (prop) {
35-
const propRequiredNode = prop.value.properties
35+
const propRequiredNode = utils.unwrapTypes(prop.value).properties
3636
.find(p =>
3737
p.type === 'Property' &&
3838
p.key.name === 'required' &&
@@ -49,7 +49,7 @@ module.exports = {
4949
* @return {boolean}
5050
*/
5151
function propHasDefault (prop) {
52-
const propDefaultNode = prop.value.properties
52+
const propDefaultNode = utils.unwrapTypes(prop.value).properties
5353
.find(p =>
5454
p.key &&
5555
(p.key.name === 'default' || p.key.value === 'default')
@@ -67,7 +67,7 @@ module.exports = {
6767
return propsNode.value.properties
6868
.filter(prop => prop.type === 'Property')
6969
.filter(prop => {
70-
if (prop.value.type !== 'ObjectExpression') {
70+
if (utils.unwrapTypes(prop.value).type !== 'ObjectExpression') {
7171
return true
7272
}
7373

@@ -98,7 +98,7 @@ module.exports = {
9898
* @return {Boolean}
9999
*/
100100
function isBooleanProp (prop) {
101-
const value = prop.value
101+
const value = utils.unwrapTypes(prop.value)
102102

103103
return isValueNodeOfBooleanType(value) || (
104104
value.type === 'ObjectExpression' &&

‎lib/rules/require-prop-type-constructor.js

+6-5
Original file line numberDiff line numberDiff line change
@@ -81,17 +81,18 @@ module.exports = {
8181

8282
node.value.properties
8383
.forEach(p => {
84-
if (isForbiddenType(p.value) || p.value.type === 'ArrayExpression') {
85-
checkPropertyNode(p.key, p.value)
86-
} else if (p.value.type === 'ObjectExpression') {
87-
const typeProperty = p.value.properties.find(prop =>
84+
const pValue = utils.unwrapTypes(p.value)
85+
if (isForbiddenType(pValue) || pValue.type === 'ArrayExpression') {
86+
checkPropertyNode(p.key, pValue)
87+
} else if (pValue.type === 'ObjectExpression') {
88+
const typeProperty = pValue.properties.find(prop =>
8889
prop.type === 'Property' &&
8990
prop.key.name === 'type'
9091
)
9192

9293
if (!typeProperty) return
9394

94-
checkPropertyNode(p.key, typeProperty.value)
95+
checkPropertyNode(p.key, utils.unwrapTypes(typeProperty.value))
9596
}
9697
})
9798
})

‎lib/rules/require-prop-types.js

+7-5
Original file line numberDiff line numberDiff line change
@@ -48,11 +48,13 @@ module.exports = {
4848
return
4949
}
5050
let hasType = true
51-
if (cp.value.type === 'ObjectExpression') { // foo: {
52-
hasType = objectHasType(cp.value)
53-
} else if (cp.value.type === 'ArrayExpression') { // foo: [
54-
hasType = cp.value.elements.length > 0
55-
} else if (cp.value.type === 'FunctionExpression' || cp.value.type === 'ArrowFunctionExpression') {
51+
const cpValue = utils.unwrapTypes(cp.value)
52+
53+
if (cpValue.type === 'ObjectExpression') { // foo: {
54+
hasType = objectHasType(cpValue)
55+
} else if (cpValue.type === 'ArrayExpression') { // foo: [
56+
hasType = cpValue.elements.length > 0
57+
} else if (cpValue.type === 'FunctionExpression' || cpValue.type === 'ArrowFunctionExpression') {
5658
hasType = false
5759
}
5860
if (!hasType) {

‎lib/rules/require-valid-default-prop.js

+3-3
Original file line numberDiff line numberDiff line change
@@ -97,11 +97,11 @@ module.exports = {
9797

9898
const properties = props.value.properties.filter(p =>
9999
isPropertyIdentifier(p) &&
100-
p.value.type === 'ObjectExpression'
100+
utils.unwrapTypes(p.value).type === 'ObjectExpression'
101101
)
102102

103103
for (const prop of properties) {
104-
const type = getPropertyNode(prop.value, 'type')
104+
const type = getPropertyNode(utils.unwrapTypes(prop.value), 'type')
105105
if (!type) continue
106106

107107
const typeNames = new Set(getTypes(type.value)
@@ -111,7 +111,7 @@ module.exports = {
111111
// There is no native types detected
112112
if (typeNames.size === 0) continue
113113

114-
const def = getPropertyNode(prop.value, 'default')
114+
const def = getPropertyNode(utils.unwrapTypes(prop.value), 'default')
115115
if (!def) continue
116116

117117
const defType = getValueType(def.value)

‎lib/utils/index.js

+33-16
Original file line numberDiff line numberDiff line change
@@ -426,24 +426,32 @@ module.exports = {
426426
* @returns {boolean}
427427
*/
428428
isVueComponent (node) {
429-
const callee = node.callee
429+
if (node.type === 'CallExpression') {
430+
const callee = node.callee
430431

431-
const isFullVueComponent = node.type === 'CallExpression' &&
432-
callee.type === 'MemberExpression' &&
433-
callee.object.type === 'Identifier' &&
434-
callee.object.name === 'Vue' &&
435-
callee.property.type === 'Identifier' &&
436-
['component', 'mixin', 'extend'].indexOf(callee.property.name) > -1 &&
437-
node.arguments.length >= 1 &&
438-
node.arguments.slice(-1)[0].type === 'ObjectExpression'
432+
if (callee.type === 'MemberExpression') {
433+
const calleeObject = this.unwrapTypes(callee.object)
439434

440-
const isDestructedVueComponent = node.type === 'CallExpression' &&
441-
callee.type === 'Identifier' &&
442-
callee.name === 'component' &&
443-
node.arguments.length >= 1 &&
444-
node.arguments.slice(-1)[0].type === 'ObjectExpression'
435+
const isFullVueComponent = calleeObject.type === 'Identifier' &&
436+
calleeObject.name === 'Vue' &&
437+
callee.property.type === 'Identifier' &&
438+
['component', 'mixin', 'extend'].indexOf(callee.property.name) > -1 &&
439+
node.arguments.length >= 1 &&
440+
node.arguments.slice(-1)[0].type === 'ObjectExpression'
441+
442+
return isFullVueComponent
443+
}
445444

446-
return isFullVueComponent || isDestructedVueComponent
445+
if (callee.type === 'Identifier') {
446+
const isDestructedVueComponent = callee.name === 'component' &&
447+
node.arguments.length >= 1 &&
448+
node.arguments.slice(-1)[0].type === 'ObjectExpression'
449+
450+
return isDestructedVueComponent
451+
}
452+
}
453+
454+
return false
447455
},
448456

449457
/**
@@ -671,7 +679,7 @@ module.exports = {
671679
/**
672680
* Parse CallExpression or MemberExpression to get simplified version without arguments
673681
*
674-
* @param {Object} node The node to parse (MemberExpression | CallExpression)
682+
* @param {ASTNode} node The node to parse (MemberExpression | CallExpression)
675683
* @return {String} eg. 'this.asd.qwe().map().filter().test.reduce()'
676684
*/
677685
parseMemberOrCallExpression (node) {
@@ -703,5 +711,14 @@ module.exports = {
703711
}
704712

705713
return parsedCallee.reverse().join('.').replace(/\.\[/g, '[')
714+
},
715+
716+
/**
717+
* Unwrap typescript types like "X as F"
718+
* @param {ASTNode} node
719+
* @return {ASTNode}
720+
*/
721+
unwrapTypes (node) {
722+
return node.type === 'TSAsExpression' ? node.expression : node
706723
}
707724
}

‎package.json

+3-1
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,8 @@
5555
"eslint-plugin-vue-libs": "^3.0.0",
5656
"lodash": "^4.17.4",
5757
"mocha": "^5.2.0",
58-
"nyc": "^12.0.2"
58+
"nyc": "^12.0.2",
59+
"typescript": "^3.1.3",
60+
"typescript-eslint-parser": "^20.0.0"
5961
}
6062
}

‎tests/lib/rules/no-side-effects-in-computed-properties.js

+18
Original file line numberDiff line numberDiff line change
@@ -248,6 +248,24 @@ ruleTester.run('no-side-effects-in-computed-properties', rule, {
248248
line: 23,
249249
message: 'Unexpected side effect in "test4" computed property.'
250250
}]
251+
},
252+
{
253+
filename: 'test.vue',
254+
code: `
255+
export default Vue.extend({
256+
computed: {
257+
test1() : string {
258+
return this.something.reverse()
259+
}
260+
}
261+
});
262+
`,
263+
parserOptions,
264+
errors: [{
265+
line: 5,
266+
message: 'Unexpected side effect in "test1" computed property.'
267+
}],
268+
parser: 'typescript-eslint-parser'
251269
}
252270
]
253271
})

‎tests/lib/rules/require-default-prop.js

+62
Original file line numberDiff line numberDiff line change
@@ -113,6 +113,34 @@ ruleTester.run('require-default-prop', rule, {
113113
}
114114
}
115115
`
116+
},
117+
{
118+
filename: 'test.vue',
119+
code: `
120+
export default (Vue as VueConstructor<Vue>).extend({
121+
props: {
122+
a: {
123+
type: String,
124+
required: true
125+
} as PropOptions<string>
126+
}
127+
});
128+
`,
129+
parser: 'typescript-eslint-parser'
130+
},
131+
{
132+
filename: 'test.vue',
133+
code: `
134+
export default Vue.extend({
135+
props: {
136+
a: {
137+
type: String,
138+
required: true
139+
} as PropOptions<string>
140+
}
141+
});
142+
`,
143+
parser: 'typescript-eslint-parser'
116144
}
117145
],
118146

@@ -157,6 +185,40 @@ ruleTester.run('require-default-prop', rule, {
157185
message: `Prop 'f' requires default value to be set.`,
158186
line: 14
159187
}]
188+
},
189+
{
190+
filename: 'test.vue',
191+
code: `
192+
export default (Vue as VueConstructor<Vue>).extend({
193+
props: {
194+
a: {
195+
type: String
196+
} as PropOptions<string>
197+
}
198+
});
199+
`,
200+
parser: 'typescript-eslint-parser',
201+
errors: [{
202+
message: `Prop 'a' requires default value to be set.`,
203+
line: 4
204+
}]
205+
},
206+
{
207+
filename: 'test.vue',
208+
code: `
209+
export default Vue.extend({
210+
props: {
211+
a: {
212+
type: String
213+
} as PropOptions<string>
214+
}
215+
});
216+
`,
217+
parser: 'typescript-eslint-parser',
218+
errors: [{
219+
message: `Prop 'a' requires default value to be set.`,
220+
line: 4
221+
}]
160222
}
161223
]
162224
})

‎tests/lib/rules/require-prop-type-constructor.js

+28
Original file line numberDiff line numberDiff line change
@@ -136,6 +136,34 @@ ruleTester.run('require-prop-type-constructor', rule, {
136136
message: 'The "d" property should be a constructor.',
137137
line: 7
138138
}]
139+
},
140+
{
141+
filename: 'SomeComponent.vue',
142+
code: `
143+
export default {
144+
props: {
145+
a: {
146+
type: 'String',
147+
default: 10
148+
} as PropOptions<string>,
149+
}
150+
}
151+
`,
152+
output: `
153+
export default {
154+
props: {
155+
a: {
156+
type: String,
157+
default: 10
158+
} as PropOptions<string>,
159+
}
160+
}
161+
`,
162+
errors: [{
163+
message: 'The "a" property should be a constructor.',
164+
line: 5
165+
}],
166+
parser: 'typescript-eslint-parser'
139167
}
140168
]
141169
})

‎tests/lib/rules/require-prop-types.js

+60
Original file line numberDiff line numberDiff line change
@@ -113,6 +113,34 @@ ruleTester.run('require-prop-types', rule, {
113113
}
114114
`,
115115
parserOptions: { ecmaVersion: 6, sourceType: 'module' }
116+
},
117+
{
118+
filename: 'test.vue',
119+
code: `
120+
export default (Vue as VueConstructor<Vue>).extend({
121+
props: {
122+
foo: {
123+
type: String
124+
} as PropOptions<string>
125+
}
126+
});
127+
`,
128+
parserOptions: { ecmaVersion: 6, sourceType: 'module' },
129+
parser: 'typescript-eslint-parser'
130+
},
131+
{
132+
filename: 'test.vue',
133+
code: `
134+
export default Vue.extend({
135+
props: {
136+
foo: {
137+
type: String
138+
} as PropOptions<string>
139+
}
140+
});
141+
`,
142+
parserOptions: { ecmaVersion: 6, sourceType: 'module' },
143+
parser: 'typescript-eslint-parser'
116144
}
117145
],
118146

@@ -190,6 +218,38 @@ ruleTester.run('require-prop-types', rule, {
190218
message: 'Prop "foo" should define at least its type.',
191219
line: 4
192220
}]
221+
},
222+
{
223+
filename: 'test.vue',
224+
code: `
225+
export default Vue.extend({
226+
props: {
227+
foo: {} as PropOptions<string>
228+
}
229+
});
230+
`,
231+
parserOptions: { ecmaVersion: 6, sourceType: 'module' },
232+
parser: 'typescript-eslint-parser',
233+
errors: [{
234+
message: 'Prop "foo" should define at least its type.',
235+
line: 4
236+
}]
237+
},
238+
{
239+
filename: 'test.vue',
240+
code: `
241+
export default (Vue as VueConstructor<Vue>).extend({
242+
props: {
243+
foo: {} as PropOptions<string>
244+
}
245+
});
246+
`,
247+
parserOptions: { ecmaVersion: 6, sourceType: 'module' },
248+
parser: 'typescript-eslint-parser',
249+
errors: [{
250+
message: 'Prop "foo" should define at least its type.',
251+
line: 4
252+
}]
193253
}
194254
]
195255
})

‎tests/lib/rules/require-valid-default-prop.js

+29
Original file line numberDiff line numberDiff line change
@@ -100,6 +100,21 @@ ruleTester.run('require-valid-default-prop', rule, {
100100
}
101101
})`,
102102
parserOptions
103+
},
104+
{
105+
filename: 'test.vue',
106+
code: `
107+
export default (Vue as VueConstructor<Vue>).extend({
108+
props: {
109+
foo: {
110+
type: [Object, Number],
111+
default: 10
112+
} as PropOptions<object>
113+
}
114+
});
115+
`,
116+
parserOptions: { ecmaVersion: 6, sourceType: 'module' },
117+
parser: 'typescript-eslint-parser'
103118
}
104119
],
105120

@@ -415,6 +430,20 @@ ruleTester.run('require-valid-default-prop', rule, {
415430
}`,
416431
parserOptions,
417432
errors: errorMessage('function or number')
433+
},
434+
{
435+
filename: 'test.vue',
436+
code: `export default (Vue as VueConstructor<Vue>).extend({
437+
props: {
438+
foo: {
439+
type: [Object, Number],
440+
default: {}
441+
} as PropOptions<object>
442+
}
443+
});`,
444+
parserOptions: { ecmaVersion: 6, sourceType: 'module' },
445+
parser: 'typescript-eslint-parser',
446+
errors: errorMessage('function or number')
418447
}
419448
]
420449
})

‎tests/lib/utils/vue-component.js

+26
Original file line numberDiff line numberDiff line change
@@ -96,6 +96,18 @@ function validTests (ext) {
9696
foo: {}
9797
}`,
9898
parserOptions
99+
},
100+
{
101+
filename: `test.${ext}`,
102+
code: `export default (Foo as FooConstructor<Foo>).extend({})`,
103+
parser: 'typescript-eslint-parser',
104+
parserOptions
105+
},
106+
{
107+
filename: `test.${ext}`,
108+
code: `export default Foo.extend({})`,
109+
parser: 'typescript-eslint-parser',
110+
parserOptions
99111
}
100112
]
101113
}
@@ -132,6 +144,20 @@ function invalidTests (ext) {
132144
parserOptions,
133145
errors: [makeError(1)]
134146
},
147+
{
148+
filename: `test.${ext}`,
149+
code: `export default (Vue as VueConstructor<Vue>).extend({})`,
150+
parser: 'typescript-eslint-parser',
151+
parserOptions,
152+
errors: [makeError(1)]
153+
},
154+
{
155+
filename: `test.${ext}`,
156+
code: `export default Vue.extend({})`,
157+
parser: 'typescript-eslint-parser',
158+
parserOptions,
159+
errors: [makeError(1)]
160+
},
135161
{
136162
filename: `test.${ext}`,
137163
code: `

0 commit comments

Comments
 (0)
Please sign in to comment.