1 1
import * as bt from '@babel/types'
2
import { NodePath } from 'ast-types/lib/node-path'
3 1
import { visit } from 'recast'
4 1
import Map from 'ts-map'
5 1
import isExportedAssignment from './isExportedAssignment'
6 1
import resolveExportDeclaration from './resolveExportDeclaration'
7 1
import resolveIdentifier from './resolveIdentifier'
8 1
import resolveRequired, { ImportedVariableSet } from './resolveRequired'
9

10 1
function ignore(): boolean {
11 1
	return false
12
}
13

14
/**
15
 * List of all keys that could contain documentation
16
 */
17 1
const VUE_COMPONENTS_KEYS = ['data', 'props', 'methods', 'computed']
18

19 1
function isObjectExpressionComponentDefinition(node: bt.ObjectExpression): boolean {
20 1
	return (
21
		// export const test = {}
22 1
		node.properties.length === 0 ||
23
		// export const compo = {data(){ return {cpm:"Button"}}
24 1
		node.properties.some(
25 1
			p =>
26 1
				(bt.isObjectMethod(p) || bt.isObjectProperty(p)) &&
27 1
				bt.isIdentifier(p.key) &&
28 1
				VUE_COMPONENTS_KEYS.includes(p.key.name)
29
		)
30
	)
31
}
32

33 1
function isComponentDefinition(path: NodePath): boolean {
34 1
	const { node } = path
35

36 1
	return (
37
		// export default {} (always exported even when empty)
38 1
		bt.isObjectExpression(node) ||
39
		// export const myComp = {} (exported only when there is a componente definition or if empty)
40 1
		(bt.isVariableDeclarator(node) &&
41 1
			node.init &&
42 1
			bt.isObjectExpression(node.init) &&
43 1
			isObjectExpressionComponentDefinition(node.init)) ||
44
		// export default class MyComp extends VueComp
45 1
		bt.isClassDeclaration(node) ||
46
		// export default whatever.extend({})
47 1
		(bt.isCallExpression(node) && bt.isObjectExpression(node.arguments[0])) ||
48
		// export const myComp = whatever.extend({})
49 1
		(bt.isVariableDeclarator(node) &&
50 1
			node.init &&
51 1
			bt.isCallExpression(node.init) &&
52 1
			bt.isObjectExpression(node.init.arguments[0])) ||
53 1
		false
54
	)
55
}
56

57 1
function getReturnStatementObject(realDef: NodePath): NodePath | undefined {
58
	let returnedObjectPath: NodePath | undefined
59 1
	visit(realDef.get('body'), {
60 1
		visitReturnStatement(rPath) {
61 1
			const returnArg = rPath.get('argument')
62 1
			if (bt.isObjectExpression(returnArg.node)) {
63 1
				returnedObjectPath = returnArg
64
			}
65 1
			return false
66
		}
67
	})
68 1
	return returnedObjectPath
69
}
70

71 1
function getReturnedObject(realDef: NodePath): NodePath | undefined {
72 1
	const { node } = realDef
73

74 1
	if (bt.isArrowFunctionExpression(node)) {
75 1
		if (bt.isObjectExpression(realDef.get('body').node)) {
76 1
			return realDef.get('body')
77
		}
78 1
		return getReturnStatementObject(realDef)
79
	}
80

81 1
	if (bt.isFunctionDeclaration(node) || bt.isFunctionExpression(node)) {
82 1
		return getReturnStatementObject(realDef)
83
	}
84 1
	return undefined
85
}
86

87
/**
88
 * Given an AST, this function tries to find the exported component definitions.
89
 *
90
 * If a definition is part of the following statements, it is considered to be
91
 * exported:
92
 *
93
 * modules.exports = Definition;
94
 * exports.foo = Definition;
95
 * export default Definition;
96
 * export var Definition = ...;
97
 */
98 1
export default function resolveExportedComponent(
99 1
	ast: bt.File
100
): [Map<string, NodePath>, ImportedVariableSet] {
101 1
	const components = new Map<string, NodePath>()
102 1
	const ievPureExports: ImportedVariableSet = {}
103 1
	const nonComponentsIdentifiers: string[] = []
104

105 1
	function setComponent(exportName: string, definition: NodePath) {
106 1
		if (definition && !components.get(exportName)) {
107 1
			components.set(exportName, normalizeComponentPath(definition))
108
		}
109
	}
110

111
	// function run for every non "assignment" export declaration
112
	// in extenso export default or export myvar
113 1
	function exportDeclaration(path: NodePath) {
114 1
		const definitions = resolveExportDeclaration(path)
115

116 1
		const sourcePath: string = path.get('source').value?.value
117

118 1
		definitions.forEach((definition: NodePath, name: string) => {
119 1
			if (sourcePath) {
120 1
				ievPureExports[name] = {
121
					exportName: definition.value.name,
122
					filePath: [sourcePath]
123
				}
124
			} else {
125 1
				const realDef = resolveIdentifier(ast, definition)
126 1
				if (realDef) {
127 1
					if (isComponentDefinition(realDef)) {
128 1
						setComponent(name, realDef)
129
					} else {
130 1
						const returnedObject = getReturnedObject(realDef)
131 1
						if (returnedObject && isObjectExpressionComponentDefinition(returnedObject.node)) {
132 1
							setComponent(name, returnedObject)
133
						}
134
					}
135
				} else {
136 1
					nonComponentsIdentifiers.push(definition.value.name)
137
				}
138
			}
139
		})
140 1
		return false
141
	}
142

143 1
	visit(ast.program, {
144
		// for perf resons,
145
		// look only at the root,
146
		// ignore all traversing except for if
147
		visitFunctionDeclaration: ignore,
148
		visitFunctionExpression: ignore,
149
		visitClassDeclaration: ignore,
150
		visitClassExpression: ignore,
151
		visitWithStatement: ignore,
152
		visitSwitchStatement: ignore,
153
		visitWhileStatement: ignore,
154
		visitDoWhileStatement: ignore,
155
		visitForStatement: ignore,
156
		visitForInStatement: ignore,
157

158
		visitDeclareExportDeclaration: exportDeclaration,
159
		visitExportNamedDeclaration: exportDeclaration,
160
		visitExportDefaultDeclaration: exportDeclaration,
161

162 1
		visitAssignmentExpression(path: NodePath) {
163
			// function run on every assignments (with an =)
164

165
			// Ignore anything that is not `exports.X = ...;` or
166
			// `module.exports = ...;`
167 1
			if (!isExportedAssignment(path)) {
168 0
				return false
169
			}
170

171
			// Resolve the value of the right hand side. It should resolve to a call
172
			// expression, something like Vue.extend({})
173 1
			const pathRight = path.get('right')
174 1
			const pathLeft = path.get('left')
175 1
			const realComp = resolveIdentifier(ast, pathRight)
176

177
			const name =
178 1
				bt.isMemberExpression(pathLeft.node) &&
179 1
				bt.isIdentifier(pathLeft.node.property) &&
180 1
				pathLeft.node.property.name !== 'exports'
181 1
					? pathLeft.node.property.name
182 1
					: 'default'
183

184 1
			if (realComp) {
185 1
				if (isComponentDefinition(realComp)) {
186 1
					setComponent(name, realComp)
187
				} else {
188 1
					const returnedObject = getReturnedObject(realComp)
189 1
					if (returnedObject && isObjectExpressionComponentDefinition(returnedObject.node)) {
190 1
						setComponent(name, returnedObject)
191
					}
192
				}
193
			} else {
194 0
				nonComponentsIdentifiers.push(name)
195
			}
196

197 1
			return false
198
		}
199
	})
200

201 1
	const requiredValues = Object.assign(
202
		ievPureExports,
203
		resolveRequired(ast, nonComponentsIdentifiers)
204
	)
205

206 1
	return [components, requiredValues]
207
}
208

209 1
function normalizeComponentPath(path: NodePath): NodePath {
210 1
	if (bt.isObjectExpression(path.node)) {
211 1
		return path
212 1
	} else if (bt.isCallExpression(path.node)) {
213 1
		return path.get('arguments', 0)
214 1
	} else if (bt.isVariableDeclarator(path.node)) {
215 1
		return path.get('init')
216
	}
217 1
	return path
218
}

Read our documentation on viewing source code .

Loading