-
Notifications
You must be signed in to change notification settings - Fork 4.1k
/
Copy pathgetComponentInfo.mjs
135 lines (109 loc) · 4.15 KB
/
getComponentInfo.mjs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
import _ from 'lodash'
import path from 'path'
import { builtinResolvers, defaultHandlers, parse } from 'react-docgen'
import fs from 'fs'
import parseDefaultValue from './parseDefaultValue.mjs'
import parseDocblock from './parseDocblock.mjs'
import parserCustomHandler from './parserCustomHandler.mjs'
import parseType from './parseType.mjs'
const config = (await import('../../../config.js')).default
const getComponentInfo = (filepath) => {
const absPath = path.resolve(process.cwd(), filepath)
const contents = fs.readFileSync(absPath).toString()
const dir = path.dirname(absPath)
const dirname = path.basename(dir)
const filename = path.basename(absPath)
const filenameWithoutExt = path.basename(absPath, path.extname(absPath))
const componentName = path.parse(filename).name
// singular form of the component's ../../ directory
// "element" for "src/elements/Button/Button.js"
const componentType = path.basename(path.dirname(dir)).replace(/s$/, '')
// start with react-docgen info
const resolver = new builtinResolvers.FindAllDefinitionsResolver()
const components = parse(contents, {
resolver,
handlers: [...defaultHandlers, parserCustomHandler],
})
if (!components.length) {
throw new Error(`Could not find a component definition in "${filepath}".`)
}
const info = components.find((component) => component.displayName === componentName)
if (!info) {
throw new Error(
[
`Failed to find a component definition for "${componentName}" in "${filepath}".`,
'Please ensure your module defines matching React component.',
].join(' '),
)
}
// remove keys we don't use
delete info.methods
if (!info.displayName) {
throw new Error(
`Please check that static property "displayName" is defined on a component in "${filepath}".`,
)
}
// add component type
info.type = componentType
// add parent/child info
info.isParent = filenameWithoutExt === dirname
info.isChild = !info.isParent
info.parentDisplayName = info.isParent ? null : dirname
// "Field" for "FormField" since it is accessed as "Form.Field" in the API
info.subcomponentName = info.isParent
? null
: info.displayName.replace(info.parentDisplayName, '')
// "ListItem.js" is a subcomponent is the "List" directory
const subcomponentRegExp = new RegExp(`^${dirname}\\w+\\.js$`)
info.subcomponents = info.isParent
? fs
.readdirSync(dir)
.filter((file) => subcomponentRegExp.test(file))
.map((file) => path.basename(file, path.extname(file)))
: null
// where this component should be exported in the api
info.apiPath = info.isChild
? `${info.parentDisplayName}.${info.subcomponentName}`
: info.displayName
// class name for the component
// example, the "button" in class="ui button"
// name of the component, sub component, or plural parent for sub component groups
info.componentClassName = (info.isChild
? info.subcomponentName.replace(/Group$/, `${info.parentDisplayName}s`)
: info.displayName
).toLowerCase()
// replace the component.description string with a parsed docblock object
info.docblock = parseDocblock(info.description)
delete info.description
// check that examples are present
info.examplesExist = false
if (info.isParent) {
info.examplesExist = fs.existsSync(
config.paths.docsSrc(`examples/${componentType}s/${dirname}/index.js`),
)
}
// file and path info
info.repoPath = absPath
.replace(`${process.cwd()}${path.sep}`, '')
.replace(new RegExp(_.escapeRegExp(path.sep), 'g'), '/')
info.filename = filename
info.filenameWithoutExt = filenameWithoutExt
// replace prop `description` strings with a parsed docblock object and updated `type`
_.each(info.props, (propDef, propName) => {
const { description, tags } = parseDocblock(propDef.description)
const { name, value } = parseType(propName, propDef)
info.props[propName] = {
...propDef,
description,
tags,
value,
defaultValue: parseDefaultValue(propDef),
name: propName,
type: name,
}
})
// sort props
info.props = _.sortBy(info.props, 'name')
return info
}
export default getComponentInfo