Skip to content

Commit

Permalink
feat: Support typescript
Browse files Browse the repository at this point in the history
fix #13
  • Loading branch information
HcySunYang committed Jan 7, 2019
1 parent 195fa2f commit a442da8
Show file tree
Hide file tree
Showing 10 changed files with 522 additions and 204 deletions.
95 changes: 94 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,11 @@ yarn global add vuese
+ [events](#events)
+ [methods](#methods)
+ [vue-class-component](#vue-class-component)
- [@Component](#component)
- [Class Method](#class-method)
+ [vue-property-decorator](#vue-property-decorator)
- [@Prop](#prop)
- [@Emit](#emit)
* [Preview the vue component as a document](#preview-the-vue-component-as-a-document)
- [Contributing](#contributing)
- [Contributors](#contributors)
Expand All @@ -63,7 +68,7 @@ yarn global add vuese
- [x] `cli` & `Core module` for nodejs.

- [x] Support `ts` & [vue-class-component](https://github.com/vuejs/vue-class-component)
- [ ] Support [vue-property-decorator](https://github.com/kaorun343/vue-property-decorator)
- [x] Support [vue-property-decorator](https://github.com/kaorun343/vue-property-decorator)
- [ ] Support for `slots` in `render` function.
- [ ] Identify `v-model`

Expand Down Expand Up @@ -503,6 +508,8 @@ Then we get:

#### vue-class-component

##### @Component

If you use [vue-class-component](https://github.com/vuejs/vue-class-component), all the options in the `@Component` decorator will be parsed, the parsing rules are the same as above, e.g:

```js
Expand Down Expand Up @@ -533,6 +540,92 @@ export default class Child extends Vue {}

It will be parsed correctly 😁.

##### Class Method

Similar to the method in the methods option mentioned above:

```js
@Component
export default class Child extends Vue {
/**
* @vuese
* This is a function exposed as an interface
*
* @arg The first parameter is a Boolean value that represents...
*/
someMethod(a) {

}
}
```

Then we get:

|Method|Description|Parameters|
|---|---|---|
|someMethod|This is a function exposed as an interface|The first parameter is a Boolean value that represents...|

#### vue-property-decorator

##### @Prop

Add leading comments to the `@Prop` decorator as a description of the prop, other aspects are the same as [prop mentioned above](#prop), an example is shown below:

```js
@Component
export default class Child extends Vue {
// Description of prop
@Prop(Number)
a: number

@Prop([Number, String])
b: number | string

@Prop({
type: Number,
// The default value is 1
default: 1,
required: true
})
c: number
}
```

Then we get:

|Name|Description|Type|Required|Default|
|---|---|---|---|---|
|a|Description of prop|`Number`|`false`|-|
|b|-|`Number` / `String`|`false`|-|
|c|-|`Number`|`true`|The default value is 1|

##### @Emit

Add leading comments to the `@Emit` decorator as a description of the prop, other aspects are the same as [events mentioned above](#events), an example is shown below:

```js
@Component
export default class Child extends Vue {

// Fire when the form is cleared
// @arg The argument is a boolean value representing xxx
@Emit()
onClick() {}

@Emit('reset')
resetHandle() {}
}
```

Then we get:

|Event Name|Description|Parameters|
|---|---|---|
|on-click|Fire when the form is cleared| The argument is a boolean value representing xxx|
|reset|-|-|

Note that if no arguments are passed for the `@Emit()` decorator, the function name is converted to a hyphen and used as the name of the event.

### Preview the vue component as a document

`vuese` also allows you to preview a vue component directly as a document, use the `preview` command:
Expand Down
17 changes: 17 additions & 0 deletions __fixtures__/tsEmit.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
<template>
<div></div>
</template>

<script lang="ts">
@Component
export default class Child extends Vue {
// Fire when the form is cleared
// @arg The argument is a boolean value representing xxx
@Emit()
onClick() {}
@Emit('reset')
resetHandle() {}
}
</script>
18 changes: 18 additions & 0 deletions __fixtures__/tsMethod.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
<template>
<div></div>
</template>

<script lang="ts">
@Component
export default class Child extends Vue {
/**
* @vuese
* This is a function exposed as an interface
*
* @arg The first parameter is a Boolean value that represents...
*/
someMethod(a) {
}
}
</script>
23 changes: 23 additions & 0 deletions __fixtures__/tsProp.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
<template>
<div></div>
</template>

<script lang="ts">
@Component
export default class Child extends Vue {
// Description of prop
@Prop(Number)
a: number
@Prop([Number, String])
b: number | string
@Prop({
type: Number,
// The default value is 1
default: 1,
required: true
})
c: number
}
</script>
49 changes: 41 additions & 8 deletions src/helpers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,47 @@ export function isVueOption(
return false
}

export function getEmitDecorator(
decorators: bt.Decorator[] | null
): bt.Decorator | null {
if (!decorators || !decorators.length) return null
for (let i = 0; i < decorators.length; i++) {
const exp = decorators[i].expression
if (
bt.isCallExpression(exp) &&
bt.isIdentifier(exp.callee) &&
exp.callee.name === 'Emit'
) {
return decorators[i]
}
}
return null
}

type PropDecoratorArgument =
| bt.Identifier
| bt.ArrayExpression
| bt.ObjectExpression
| null
export function getArgumentFromPropDecorator(
classPropertyNode: bt.ClassProperty
): PropDecoratorArgument {
const decorators = classPropertyNode.decorators
if (decorators) {
for (let i = 0; i < decorators.length; i++) {
const deco = decorators[i]
if (
bt.isCallExpression(deco.expression) &&
bt.isIdentifier(deco.expression.callee) &&
deco.expression.callee.name === 'Prop'
) {
return deco.expression.arguments[0] as PropDecoratorArgument
}
}
}
return null
}

const josnCache: [] = []
export function writeFileSync(str: any, keep?: boolean) {
const filePath = __dirname + '/a.txt'
Expand Down Expand Up @@ -101,11 +142,3 @@ export function writeFileSync(str: any, keep?: boolean) {
)
fs.writeFileSync(__dirname + '/a.txt', keep ? preContent + content : content)
}

export function getFirstPropContainer(path: NodePath, propName: string) {
const propPath = path.get(`${propName}.0`) as NodePath<bt.Node>

return Array.isArray(propPath.container)
? propPath.container
: [propPath.container]
}
36 changes: 36 additions & 0 deletions src/parser/__test__/__snapshots__/parseJavascript.test.ts.snap
Original file line number Diff line number Diff line change
@@ -1,5 +1,41 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP

exports[`@Emit decorator 1`] = `
Array [
"Fire when the form is cleared",
]
`;

exports[`@Emit decorator 2`] = `
Array [
" The argument is a boolean value representing xxx",
]
`;

exports[`@Prop decorator 1`] = `
Array [
"Description of prop",
]
`;

exports[`@Prop decorator 2`] = `
Array [
"The default value is 1",
]
`;

exports[`Class method 1`] = `
Array [
"This is a function exposed as an interface",
]
`;

exports[`Class method 2`] = `
Array [
"The first parameter is a Boolean value that represents...",
]
`;

exports[`Correct handling of events 1`] = `
Array [
"Triggered when clicked",
Expand Down
64 changes: 64 additions & 0 deletions src/parser/__test__/parseJavascript.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -253,3 +253,67 @@ test('The options in @Component should be parsed correctly', () => {
expect(mockOnProp.mock.calls.length).toBe(1)
expect(arg2 as PropsResult).toMatchSnapshot()
})

test('@Prop decorator', () => {
const sfc: AstResult = getAST('tsProp.vue')
const mockOnProp = jest.fn(() => {})
const options: ParserOptions = {
onProp: mockOnProp
}
parseJavascript(sfc.jsAst as bt.File, options)

const arg1 = mockOnProp.mock.calls[0][0]
const arg2 = mockOnProp.mock.calls[1][0]
const arg3 = mockOnProp.mock.calls[2][0]

expect(mockOnProp.mock.calls.length).toBe(3)
expect((arg1 as PropsResult).name).toBe('a')
expect((arg1 as PropsResult).type).toBe('Number')
expect((arg1 as PropsResult).describe).toMatchSnapshot()

expect((arg2 as PropsResult).name).toBe('b')
expect((arg2 as PropsResult).type).toEqual(['Number', 'String'])

expect((arg3 as PropsResult).name).toBe('c')
expect((arg3 as PropsResult).type).toBe('Number')
expect((arg3 as PropsResult).required).toBe(true)
expect((arg3 as PropsResult).defaultDesc).toMatchSnapshot()
})

test('Class method', () => {
const sfc: AstResult = getAST('tsMethod.vue')
const mockOnMethod = jest.fn(() => {})
const options: ParserOptions = {
onMethod: mockOnMethod
}
parseJavascript(sfc.jsAst as bt.File, options)

const arg = mockOnMethod.mock.calls[0][0]
expect(mockOnMethod.mock.calls.length).toBe(1)
expect((arg as MethodResult).name).toBe('someMethod')
expect(((arg as MethodResult).describe as string[]).length).toBe(1)
expect(((arg as MethodResult).argumentsDesc as string[]).length).toBe(1)
expect((arg as MethodResult).describe).toMatchSnapshot()
expect((arg as MethodResult).argumentsDesc).toMatchSnapshot()
})

test('@Emit decorator', () => {
const sfc: AstResult = getAST('tsEmit.vue')
const mockOnEvent = jest.fn(() => {})
const options: ParserOptions = {
onEvent: mockOnEvent
}
parseJavascript(sfc.jsAst as bt.File, options)

const arg1 = mockOnEvent.mock.calls[0][0]
const arg2 = mockOnEvent.mock.calls[1][0]

expect(mockOnEvent.mock.calls.length).toBe(2)
expect((arg1 as EventResult).name).toBe('on-click')
expect(((arg1 as EventResult).describe as string[]).length).toBe(1)
expect(((arg1 as EventResult).argumentsDesc as string[]).length).toBe(1)
expect((arg1 as EventResult).describe).toMatchSnapshot()
expect((arg1 as EventResult).argumentsDesc).toMatchSnapshot()

expect((arg2 as EventResult).name).toBe('reset')
})
5 changes: 4 additions & 1 deletion src/parser/jscomments.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ export type CommentResult = {
default: string[]
[key: string]: string[]
}
const commentRE = /\n\s*\*?\s*/g
const commentRE = /\s*\*\s*/g
const leadRE = /^@(\w+)\b/

export function getComments(cnode: bt.Node): CommentResult {
Expand All @@ -32,6 +32,9 @@ export function getComments(cnode: bt.Node): CommentResult {
comments = node.value
.replace(commentRE, '\n')
.replace(/^\*/, '')
.split('\n')
.filter(t => t)
.join('\n')
.trim()
let currentKey = 'default'
comments.split('\n').forEach(c => {
Expand Down
Loading

0 comments on commit a442da8

Please sign in to comment.