Skip to content

Commit d99ac16

Browse files
committed
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
1 parent 3ccf3ef commit d99ac16

7 files changed

+195
-26
lines changed

.vscode/launch.json

+26
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
{
2+
// Use IntelliSense to learn about possible attributes.
3+
// Hover to view descriptions of existing attributes.
4+
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
5+
"version": "0.2.0",
6+
"configurations": [
7+
{
8+
"type": "node",
9+
"request": "launch",
10+
"name": "Mocha Tests",
11+
"program": "${workspaceFolder}/node_modules/mocha/bin/_mocha",
12+
"args": [
13+
"-u",
14+
"tdd",
15+
"--timeout",
16+
"999999",
17+
"--colors",
18+
"${workspaceFolder}/tests/lib/**/*.js",
19+
"--reporter",
20+
"dot",
21+
"--watch"
22+
],
23+
"internalConsoleOptions": "openOnSessionStart"
24+
}
25+
]
26+
}

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-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/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
@@ -56,6 +56,8 @@
5656
"eslint-plugin-vue-libs": "^3.0.0",
5757
"lodash": "^4.17.4",
5858
"mocha": "^5.2.0",
59-
"nyc": "^12.0.2"
59+
"nyc": "^12.0.2",
60+
"typescript": "^3.1.3",
61+
"typescript-eslint-parser": "^20.0.0"
6062
}
6163
}

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-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
})

0 commit comments

Comments
 (0)