Skip to content

Commit

Permalink
Merge pull request #751 from vue-styleguidist/feat-add-tags-to-slots
Browse files Browse the repository at this point in the history
Feature: add tags to slots
  • Loading branch information
elevatebart committed Feb 12, 2020
2 parents 6484a10 + dcbddf8 commit db1595e
Show file tree
Hide file tree
Showing 5 changed files with 113 additions and 37 deletions.
1 change: 1 addition & 0 deletions packages/vue-docgen-api/src/Documentation.ts
Expand Up @@ -85,6 +85,7 @@ export interface SlotDescriptor extends Descriptor {
description?: string
bindings?: ParamTag[]
scoped?: boolean
tags?: { [key: string]: BlockTag[] }
}

export interface ComponentDoc {
Expand Down
Expand Up @@ -277,6 +277,39 @@ export default {
expect(mockSlotDescriptor.description).toEqual('the content for the pending state')
done()
})
describe('tags', () => {
it('should extract tags from the description block', async done => {
const src = `
export default {
render(createElement) {
return createElement('div', [
/**
* @slot
* @ignore
*/
this.$scopedSlots.default
])
}
}
`
const def = parse(src)
if (def) {
await slotHandler(documentation, def)
}
expect(mockSlotDescriptor.tags).not.toBeUndefined()
expect(mockSlotDescriptor.tags).toMatchInlineSnapshot(`
Object {
"ignore": Array [
Object {
"description": true,
"title": "ignore",
},
],
}
`)
done()
})
})

describe('bindings', () => {
it('should describe slots bindings in render functions', async done => {
Expand Down
68 changes: 38 additions & 30 deletions packages/vue-docgen-api/src/script-handlers/slotHandler.ts
@@ -1,9 +1,10 @@
import * as bt from '@babel/types'
import { NodePath } from 'ast-types'
import recast from 'recast'
import Documentation, { ParamTag, ParamType, Tag } from '../Documentation'
import Documentation, { ParamTag, ParamType, Tag, SlotDescriptor } from '../Documentation'
import getDoclets from '../utils/getDoclets'
import { parseDocblock } from '../utils/getDocblock'
import transformTagsIntoObject from '../utils/transformTagsIntoObject'

export interface TypedParamTag extends ParamTag {
type: ParamType
Expand Down Expand Up @@ -38,10 +39,7 @@ export default async function slotHandler(documentation: Documentation, path: No
pathCall.node.callee.object.property.name === '$scopedSlots')
) {
const doc = documentation.getSlotDescriptor(pathCall.node.callee.property.name)
const comment = getSlotComment(pathCall)
if (comment && (!doc.description || !doc.description.length)) {
doc.description = comment.description
}
const comment = getSlotComment(pathCall, doc)
const bindings = pathCall.node.arguments[0]
if (bt.isObjectExpression(bindings) && bindings.properties.length) {
doc.bindings = getBindings(bindings, comment ? comment.bindings : undefined)
Expand All @@ -60,11 +58,8 @@ export default async function slotHandler(documentation: Documentation, path: No
pathMember.node.object.property.name === '$scopedSlots') &&
bt.isIdentifier(pathMember.node.property)
) {
const comment = getSlotComment(pathMember)
const doc = documentation.getSlotDescriptor(pathMember.node.property.name)
if (comment && comment.description) {
doc.description = comment.description
}
getSlotComment(pathMember, doc)
return false
}
this.traverse(pathMember)
Expand All @@ -81,10 +76,7 @@ export default async function slotHandler(documentation: Documentation, path: No
const parentNode = pathJSX.parentPath.node
let comment: SlotComment | undefined
if (bt.isJSXElement(parentNode)) {
comment = getJSXDescription(nodeJSX, parentNode.children)
if (comment) {
doc.description = comment.description
}
comment = getJSXDescription(nodeJSX, parentNode.children, doc)
}
const bindings = nodeJSX.openingElement.attributes
if (bindings && bindings.length) {
Expand Down Expand Up @@ -114,12 +106,13 @@ function getName(nodeJSX: bt.JSXElement): string {
return nameNode && bt.isStringLiteral(nameNode) ? nameNode.value : 'default'
}

type SlotComment = {
description?: string
bindings?: ParamTag[]
}
type SlotComment = Pick<SlotDescriptor, 'bindings'>

function getJSXDescription(nodeJSX: bt.JSXElement, siblings: bt.Node[]): SlotComment | undefined {
function getJSXDescription(
nodeJSX: bt.JSXElement,
siblings: bt.Node[],
descriptor: SlotDescriptor
): SlotComment | undefined {
if (!siblings) {
return undefined
}
Expand All @@ -139,12 +132,12 @@ function getJSXDescription(nodeJSX: bt.JSXElement, siblings: bt.Node[]): SlotCom
const cmts = commentExpression.expression.innerComments
const lastComment = cmts[cmts.length - 1]

return parseCommentNode(lastComment)
return parseCommentNode(lastComment, descriptor)
}

function getSlotComment(path: NodePath): SlotComment | undefined {
const desc = getExpressionDescription(path)
if (desc && desc.description && desc.description.length) {
function getSlotComment(path: NodePath, descriptor: SlotDescriptor): SlotComment | undefined {
const desc = getExpressionDescription(path, descriptor)
if (desc) {
return desc
}
// in case we don't find a description on the expression,
Expand All @@ -157,33 +150,48 @@ function getSlotComment(path: NodePath): SlotComment | undefined {
}

// 2: extract the description if it exists
return path ? getExpressionDescription(path) : undefined
return path ? getExpressionDescription(path, descriptor) : undefined
}

function getExpressionDescription(path: NodePath): SlotComment | undefined {
function getExpressionDescription(
path: NodePath,
descriptor: SlotDescriptor
): SlotComment | undefined {
const node = path.node
if (!node.leadingComments || node.leadingComments.length === 0) {
return undefined
}

return parseCommentNode(node.leadingComments[node.leadingComments.length - 1])
return parseCommentNode(node.leadingComments[node.leadingComments.length - 1], descriptor)
}

function parseCommentNode(node: bt.Comment): SlotComment | undefined {
function parseCommentNode(node: bt.Comment, descriptor: SlotDescriptor): SlotComment | undefined {
if (node.type !== 'CommentBlock') {
return undefined
}
const docBlock = parseDocblock(node.value).trim()
return parseSlotDocBlock(node.value, descriptor)
}

export function parseSlotDocBlock(str: string, descriptor: SlotDescriptor) {
const docBlock = parseDocblock(str).trim()
const jsDoc = getDoclets(docBlock)
if (!jsDoc.tags) {
return undefined
}
const slotTags = jsDoc.tags.filter(t => t.title === 'slot')
if (slotTags.length) {
const tagContent = (slotTags[0] as Tag).content
return typeof tagContent === 'string'
? { description: tagContent, bindings: jsDoc.tags.filter(t => t.title === 'binding') }
: undefined
const description = typeof tagContent === 'string' ? tagContent : undefined
if (description && (!descriptor.description || !descriptor.description.length)) {
descriptor.description = description
}
const tags = jsDoc.tags.filter(t => t.title !== 'slot' && t.title !== 'binding')
if (tags.length) {
descriptor.tags = transformTagsIntoObject(tags)
}
return {
bindings: jsDoc.tags.filter(t => t.title === 'binding')
}
}
return undefined
}
Expand Down
Expand Up @@ -214,5 +214,37 @@ describe('slotHandler', () => {
done.fail()
}
})

it('should extract tags from a slot', done => {
const ast = compile(
[
'<div>', //
' <!--',
' @slot',
' @ignore',
' -->', //
' <slot />',
'</div>'
].join('\n'),
{ comments: true }
).ast
if (ast) {
traverse(ast, doc, [slotHandler], { functional: false, rootLeadingComment: [] })
const slots = doc.toObject().slots || []
expect(slots[0].tags).toMatchInlineSnapshot(`
Object {
"ignore": Array [
Object {
"description": true,
"title": "ignore",
},
],
}
`)
done()
} else {
done.fail()
}
})
})
})
16 changes: 9 additions & 7 deletions packages/vue-docgen-api/src/template-handlers/slotHandler.ts
@@ -1,11 +1,11 @@
import * as bt from '@babel/types'
import { ASTElement } from 'vue-template-compiler'
import recast, { NodePath } from 'recast'
import Documentation, { Tag, ParamTag } from '../Documentation'
import Documentation, { ParamTag } from '../Documentation'
import buildParser from '../babel-parser'
import { TemplateParserOptions } from '../parse-template'
import extractLeadingComment from '../utils/extractLeadingComment'
import getDoclets from '../utils/getDoclets'
import { parseSlotDocBlock } from '../script-handlers/slotHandler'

const parser = buildParser({ plugins: ['typescript'] })

Expand Down Expand Up @@ -61,13 +61,15 @@ export default function slotHandler(
options.rootLeadingComment
)
let bindingDescriptors: ParamTag[] = []

comments.forEach(comment => {
// if a comment contains @slot,
// use it to determine bindings and tags
// if multiple @slot, use the last one
if (comment.length) {
const doclets = getDoclets(comment)
if (doclets.tags) {
const slotTag = doclets.tags.filter(t => t.title === 'slot')[0] as Tag
slotDescriptor.description = slotTag && (slotTag.content as string).trim()
bindingDescriptors = doclets.tags.filter(t => t.title === 'binding') as ParamTag[]
const doclets = parseSlotDocBlock(comment, slotDescriptor)
if (doclets && doclets.bindings) {
bindingDescriptors = doclets.bindings
}
}
})
Expand Down

0 comments on commit db1595e

Please sign in to comment.