Skip to content
This repository was archived by the owner on Mar 8, 2019. It is now read-only.

Commit 00f06f2

Browse files
authored
fix: resolved immediately exported variables as their original file (#94)
* add e2e test for #92 * add tests and create the immediately export lib * need better name for New file + tests * add resolution of vue files for dependencies * fix naming a little * fix unit tests * first unit test * fix tests * fix compile * fix case issue in e2e * better arch for resolvePath * better tests for file resolution closes #92 and vue-styleguidist/vue-styleguidist#158
1 parent 9b918bf commit 00f06f2

13 files changed

+318
-18
lines changed

src/script-handlers/extendsHandler.ts

+7-6
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,8 @@ import { NodePath } from 'ast-types'
33
import * as path from 'path'
44
import { Documentation } from '../Documentation'
55
import { parseFile, ParseOptions } from '../parse'
6-
import resolveAliases from '../utils/resolveAliases'
7-
import resolvePathFrom from '../utils/resolvePathFrom'
6+
import resolveImmediatelyExportedRequire from '../utils/adaptExportsToIEV'
7+
import makePathResolver from '../utils/makePathResolver'
88
import resolveRequired from '../utils/resolveRequired'
99

1010
/**
@@ -31,12 +31,13 @@ export default function extendsHandler(
3131

3232
const originalDirName = path.dirname(opt.filePath)
3333

34+
const pathResolver = makePathResolver(originalDirName, opt.aliases)
35+
36+
resolveImmediatelyExportedRequire(pathResolver, extendsFilePath)
37+
3438
// only look for documentation in the current project not in node_modules
3539
if (/^\./.test(extendsFilePath[extendsVariableName].filePath)) {
36-
const fullFilePath = resolvePathFrom(
37-
resolveAliases(extendsFilePath[extendsVariableName].filePath, opt.aliases || {}),
38-
originalDirName,
39-
)
40+
const fullFilePath = pathResolver(extendsFilePath[extendsVariableName].filePath)
4041

4142
parseFile(documentation, {
4243
...opt,

src/script-handlers/mixinsHandler.ts

+7-6
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,8 @@ import * as path from 'path'
44
import Map from 'ts-map'
55
import { Documentation } from '../Documentation'
66
import { parseFile, ParseOptions } from '../parse'
7-
import resolveAliases from '../utils/resolveAliases'
8-
import resolvePathFrom from '../utils/resolvePathFrom'
7+
import resolveImmediatelyExportedRequire from '../utils/adaptExportsToIEV'
8+
import makePathResolver from '../utils/makePathResolver'
99
import resolveRequired from '../utils/resolveRequired'
1010

1111
/**
@@ -21,6 +21,8 @@ export default function mixinsHandler(
2121
) {
2222
const originalDirName = path.dirname(opt.filePath)
2323

24+
const pathResolver = makePathResolver(originalDirName, opt.aliases)
25+
2426
// filter only mixins
2527
const mixinVariableNames = getMixinsVariableNames(componentDefinition)
2628

@@ -31,14 +33,13 @@ export default function mixinsHandler(
3133
// get all require / import statements
3234
const mixinVarToFilePath = resolveRequired(astPath, mixinVariableNames)
3335

36+
resolveImmediatelyExportedRequire(pathResolver, mixinVarToFilePath)
37+
3438
// get each doc for each mixin using parse
3539
const files = new Map<string, string[]>()
3640
for (const varName of Object.keys(mixinVarToFilePath)) {
3741
const { filePath, exportName } = mixinVarToFilePath[varName]
38-
const fullFilePath = resolvePathFrom(
39-
resolveAliases(filePath, opt.aliases || {}),
40-
originalDirName,
41-
)
42+
const fullFilePath = pathResolver(filePath)
4243
const vars = files.get(fullFilePath) || []
4344
vars.push(exportName)
4445
files.set(fullFilePath, vars)
+19
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
import adaptRequireWithIEV from '../adaptExportsToIEV'
2+
import { ImportedVariableSet } from '../resolveRequired'
3+
4+
jest.mock('../resolveImmediatelyExported')
5+
6+
describe('adaptRequireWithIEV', () => {
7+
let set: ImportedVariableSet
8+
let mockResolver: jest.Mock
9+
beforeEach(() => {
10+
set = { test: { filePath: 'my/path', exportName: 'exportIt' } }
11+
mockResolver = jest.fn()
12+
})
13+
14+
it('should call the resolver', () => {
15+
adaptRequireWithIEV(mockResolver, set)
16+
17+
expect(mockResolver).toHaveBeenCalledWith('my/path')
18+
})
19+
})
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
import babylon from '../../babel-parser'
2+
import resolveImmediatelyExported from '../resolveImmediatelyExported'
3+
4+
describe('resolveImmediatelyExported', () => {
5+
it('should immediately exported varibles', () => {
6+
const ast = babylon().parse('export { test } from "test/path";')
7+
const varNames = resolveImmediatelyExported(ast, ['test'])
8+
expect(varNames).toMatchObject({
9+
test: { filePath: 'test/path', exportName: 'test' },
10+
})
11+
})
12+
13+
it('should immediately exported varibles with aliases', () => {
14+
const ast = babylon().parse('export { test as changedName } from "test/path";')
15+
const varNames = resolveImmediatelyExported(ast, ['changedName'])
16+
expect(varNames).toMatchObject({
17+
changedName: { filePath: 'test/path', exportName: 'test' },
18+
})
19+
})
20+
21+
it('should resolve immediately exported varibles in two steps', () => {
22+
const ast = babylon().parse(
23+
[
24+
'import { test as middleName } from "test/path";',
25+
'export { middleName as changedName };',
26+
].join('\n'),
27+
)
28+
const varNames = resolveImmediatelyExported(ast, ['changedName'])
29+
expect(varNames).toMatchObject({
30+
changedName: { filePath: 'test/path', exportName: 'test' },
31+
})
32+
})
33+
34+
it('should return immediately exported varibles in two steps with default import', () => {
35+
const ast = babylon().parse(
36+
['import test from "test/path";', 'export { test as changedName };'].join('\n'),
37+
)
38+
const varNames = resolveImmediatelyExported(ast, ['changedName'])
39+
expect(varNames).toMatchObject({
40+
changedName: { filePath: 'test/path', exportName: 'default' },
41+
})
42+
})
43+
44+
it('should return immediately exported varibles in two steps with default export', () => {
45+
const ast = babylon().parse(
46+
['import { test } from "test/path";', 'export default test;'].join('\n'),
47+
)
48+
const varNames = resolveImmediatelyExported(ast, ['default'])
49+
expect(varNames).toMatchObject({
50+
default: { filePath: 'test/path', exportName: 'test' },
51+
})
52+
})
53+
})

src/utils/adaptExportsToIEV.ts

+58
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
import * as fs from 'fs'
2+
import * as path from 'path'
3+
import Map from 'ts-map'
4+
import buildParser from '../babel-parser'
5+
import cacher from './cacher'
6+
import resolveImmediatelyExported from './resolveImmediatelyExported'
7+
import { ImportedVariableSet } from './resolveRequired'
8+
9+
// tslint:disable-next-line:no-var-requires
10+
import recast = require('recast')
11+
12+
export default function adaptExportsToIEV(
13+
pathResolver: (path: string, originalDirNameOverride?: string) => string,
14+
varToFilePath: ImportedVariableSet,
15+
) {
16+
// key: filepath, content: {key: localName, content: exportedName}
17+
const filePathToVars = new Map<string, Map<string, string>>()
18+
Object.keys(varToFilePath).forEach(k => {
19+
const exportedVariable = varToFilePath[k]
20+
const exportToLocalMap =
21+
filePathToVars.get(exportedVariable.filePath) || new Map<string, string>()
22+
exportToLocalMap.set(k, exportedVariable.exportName)
23+
filePathToVars.set(exportedVariable.filePath, exportToLocalMap)
24+
})
25+
26+
filePathToVars.forEach((exportToLocal, filePath) => {
27+
if (filePath && exportToLocal) {
28+
const exportedVariableNames: string[] = []
29+
exportToLocal.forEach(exportedName => {
30+
if (exportedName) {
31+
exportedVariableNames.push(exportedName)
32+
}
33+
})
34+
try {
35+
const fullFilePath = pathResolver(filePath)
36+
const source = fs.readFileSync(fullFilePath, {
37+
encoding: 'utf-8',
38+
})
39+
const astRemote = cacher(() => recast.parse(source, { parser: buildParser() }), source)
40+
const returnedVariables = resolveImmediatelyExported(astRemote, exportedVariableNames)
41+
exportToLocal.forEach((exported, local) => {
42+
if (exported && local) {
43+
const aliasedVariable = returnedVariables[exported]
44+
if (aliasedVariable) {
45+
aliasedVariable.filePath = pathResolver(
46+
aliasedVariable.filePath,
47+
path.dirname(fullFilePath),
48+
)
49+
varToFilePath[local] = aliasedVariable
50+
}
51+
}
52+
})
53+
} catch (e) {
54+
// ignore load errors
55+
}
56+
}
57+
})
58+
}

src/utils/makePathResolver.ts

+10
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
import resolveAliases from '../utils/resolveAliases'
2+
import resolvePathFrom from '../utils/resolvePathFrom'
3+
4+
export default function makePathResolver(
5+
refDirName: string,
6+
aliases?: { [alias: string]: string },
7+
): (filePath: string, originalDirNameOverride?: string) => string {
8+
return (filePath: string, originalDirNameOverride?: string): string =>
9+
resolvePathFrom(resolveAliases(filePath, aliases || {}), originalDirNameOverride || refDirName)
10+
}
+73
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
import * as bt from '@babel/types'
2+
import { NodePath } from 'ast-types'
3+
import { ImportedVariableSet } from './resolveRequired'
4+
5+
// tslint:disable-next-line:no-var-requires
6+
import recast = require('recast')
7+
8+
export default function(ast: bt.File, variableFilter: string[]): ImportedVariableSet {
9+
const variables: ImportedVariableSet = {}
10+
11+
const importedVariablePaths: ImportedVariableSet = {}
12+
13+
// get imported variable names and filepath
14+
recast.visit(ast.program, {
15+
visitImportDeclaration(astPath: NodePath<bt.ImportDeclaration>) {
16+
if (!astPath.node.source) {
17+
return false
18+
}
19+
const filePath = astPath.node.source.value
20+
21+
const specifiers = astPath.get('specifiers')
22+
specifiers.each((s: NodePath<bt.ImportSpecifier | bt.ImportDefaultSpecifier>) => {
23+
const varName = s.node.local.name
24+
const exportName = bt.isImportSpecifier(s.node) ? s.node.imported.name : 'default'
25+
importedVariablePaths[varName] = { filePath, exportName }
26+
})
27+
return false
28+
},
29+
})
30+
31+
recast.visit(ast.program, {
32+
visitExportNamedDeclaration(astPath: NodePath<bt.ExportNamedDeclaration>) {
33+
const specifiers = astPath.get('specifiers')
34+
if (astPath.node.source) {
35+
const filePath = astPath.node.source.value
36+
37+
specifiers.each((s: NodePath<bt.ExportSpecifier>) => {
38+
const varName = s.node.exported.name
39+
const exportName = s.node.local.name
40+
if (variableFilter.indexOf(varName) > -1) {
41+
variables[varName] = { filePath, exportName }
42+
}
43+
})
44+
} else {
45+
specifiers.each((s: NodePath<bt.ExportSpecifier>) => {
46+
const varName = s.node.exported.name
47+
const middleName = s.node.local.name
48+
const importedVar = importedVariablePaths[middleName]
49+
if (importedVar && variableFilter.indexOf(varName) > -1) {
50+
variables[varName] = importedVar
51+
}
52+
})
53+
}
54+
55+
return false
56+
},
57+
visitExportDefaultDeclaration(astPath: NodePath<bt.ExportDefaultDeclaration>) {
58+
if (variableFilter.indexOf('default') > -1) {
59+
const middleNameDeclaration = astPath.node.declaration
60+
if (bt.isIdentifier(middleNameDeclaration)) {
61+
const middleName = middleNameDeclaration.name
62+
const importedVar = importedVariablePaths[middleName]
63+
if (importedVar) {
64+
variables.default = importedVar
65+
}
66+
}
67+
}
68+
return false
69+
},
70+
})
71+
72+
return variables
73+
}

src/utils/resolvePathFrom.ts

+21-2
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,24 @@
1+
const SUFFIXES = ['', '.js', '.ts', '.vue', '/index.js', '/index.ts']
2+
13
export default function resolvePathFrom(path: string, from: string): string {
2-
return require.resolve(path, {
3-
paths: [from],
4+
let finalPath = ''
5+
SUFFIXES.forEach(s => {
6+
if (!finalPath.length) {
7+
try {
8+
finalPath = require.resolve(`${path}${s}`, {
9+
paths: [from],
10+
})
11+
} catch (e) {
12+
// eat the error
13+
}
14+
}
415
})
16+
17+
if (!finalPath.length) {
18+
throw new Error(
19+
`Neither '${path}.vue' nor '${path}.js', not even '${path}/index.js' or '${path}/index.ts' could be found in '${from}'`,
20+
)
21+
}
22+
23+
return finalPath
524
}

src/utils/resolveRequired.ts

+9-4
Original file line numberDiff line numberDiff line change
@@ -4,11 +4,15 @@ import { NodePath } from 'ast-types'
44
// tslint:disable-next-line:no-var-requires
55
import recast = require('recast')
66

7-
interface ImportedVariableToken {
7+
interface ImportedVariable {
88
filePath: string
99
exportName: string
1010
}
1111

12+
export interface ImportedVariableSet {
13+
[key: string]: ImportedVariable
14+
}
15+
1216
/**
1317
*
1418
* @param ast
@@ -17,8 +21,8 @@ interface ImportedVariableToken {
1721
export default function resolveRequired(
1822
ast: bt.File,
1923
varNameFilter?: string[],
20-
): { [key: string]: ImportedVariableToken } {
21-
const varToFilePath: { [key: string]: ImportedVariableToken } = {}
24+
): ImportedVariableSet {
25+
const varToFilePath: ImportedVariableSet = {}
2226

2327
recast.visit(ast.program, {
2428
visitImportDeclaration(astPath: NodePath) {
@@ -37,8 +41,9 @@ export default function resolveRequired(
3741
if (!varNameFilter || varNameFilter.indexOf(localVariableName) > -1) {
3842
const nodeSource = (astPath.get('source') as NodePath<bt.Literal>).node
3943
if (bt.isStringLiteral(nodeSource)) {
44+
const filePath = nodeSource.value
4045
varToFilePath[localVariableName] = {
41-
filePath: nodeSource.value,
46+
filePath,
4247
exportName,
4348
}
4449
}
+15
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
<template>
2+
<button
3+
class="buttonComponent"
4+
@click.prevent="onClick"
5+
:style="`background:${color};width:${size}px;`"
6+
/>
7+
</template>
8+
9+
<script>
10+
import { anotherMixin, myMixin } from '@mixins/index'
11+
12+
export default {
13+
mixins: [anotherMixin, myMixin],
14+
}
15+
</script>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
import * as path from 'path'
2+
3+
import { ComponentDoc, PropDescriptor } from '../../../src/Documentation'
4+
import { parse } from '../../../src/main'
5+
const button = path.join(__dirname, './button.vue')
6+
let docButton: ComponentDoc
7+
8+
describe('tests button', () => {
9+
beforeAll(done => {
10+
docButton = parse(button, {
11+
'@mixins': path.resolve(__dirname, '../../mixins'),
12+
})
13+
done()
14+
})
15+
16+
describe('props', () => {
17+
let props: { [propName: string]: PropDescriptor }
18+
19+
beforeAll(() => {
20+
props = docButton.props ? docButton.props : {}
21+
})
22+
23+
it('should return the "color" prop description from passthrough exported mixin', () => {
24+
expect(props.color.description).toEqual('Another Mixins Error')
25+
})
26+
27+
it('should return the "propsAnother" prop description from a vue file mixin', () => {
28+
expect(props.propsAnother.description).toEqual('Example prop in vue file')
29+
})
30+
})
31+
})

0 commit comments

Comments
 (0)