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

Commit

Permalink
fix: resolved immediately exported variables as their original file (#94
Browse files Browse the repository at this point in the history
)

* 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
  • Loading branch information
elevatebart committed Feb 4, 2019
1 parent 9b918bf commit 00f06f2
Show file tree
Hide file tree
Showing 13 changed files with 318 additions and 18 deletions.
13 changes: 7 additions & 6 deletions src/script-handlers/extendsHandler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,8 @@ import { NodePath } from 'ast-types'
import * as path from 'path'
import { Documentation } from '../Documentation'
import { parseFile, ParseOptions } from '../parse'
import resolveAliases from '../utils/resolveAliases'
import resolvePathFrom from '../utils/resolvePathFrom'
import resolveImmediatelyExportedRequire from '../utils/adaptExportsToIEV'
import makePathResolver from '../utils/makePathResolver'
import resolveRequired from '../utils/resolveRequired'

/**
Expand All @@ -31,12 +31,13 @@ export default function extendsHandler(

const originalDirName = path.dirname(opt.filePath)

const pathResolver = makePathResolver(originalDirName, opt.aliases)

resolveImmediatelyExportedRequire(pathResolver, extendsFilePath)

// only look for documentation in the current project not in node_modules
if (/^\./.test(extendsFilePath[extendsVariableName].filePath)) {
const fullFilePath = resolvePathFrom(
resolveAliases(extendsFilePath[extendsVariableName].filePath, opt.aliases || {}),
originalDirName,
)
const fullFilePath = pathResolver(extendsFilePath[extendsVariableName].filePath)

parseFile(documentation, {
...opt,
Expand Down
13 changes: 7 additions & 6 deletions src/script-handlers/mixinsHandler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,8 @@ import * as path from 'path'
import Map from 'ts-map'
import { Documentation } from '../Documentation'
import { parseFile, ParseOptions } from '../parse'
import resolveAliases from '../utils/resolveAliases'
import resolvePathFrom from '../utils/resolvePathFrom'
import resolveImmediatelyExportedRequire from '../utils/adaptExportsToIEV'
import makePathResolver from '../utils/makePathResolver'
import resolveRequired from '../utils/resolveRequired'

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

const pathResolver = makePathResolver(originalDirName, opt.aliases)

// filter only mixins
const mixinVariableNames = getMixinsVariableNames(componentDefinition)

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

resolveImmediatelyExportedRequire(pathResolver, mixinVarToFilePath)

// get each doc for each mixin using parse
const files = new Map<string, string[]>()
for (const varName of Object.keys(mixinVarToFilePath)) {
const { filePath, exportName } = mixinVarToFilePath[varName]
const fullFilePath = resolvePathFrom(
resolveAliases(filePath, opt.aliases || {}),
originalDirName,
)
const fullFilePath = pathResolver(filePath)
const vars = files.get(fullFilePath) || []
vars.push(exportName)
files.set(fullFilePath, vars)
Expand Down
19 changes: 19 additions & 0 deletions src/utils/__tests__/adaptExportsToIEV.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import adaptRequireWithIEV from '../adaptExportsToIEV'
import { ImportedVariableSet } from '../resolveRequired'

jest.mock('../resolveImmediatelyExported')

describe('adaptRequireWithIEV', () => {
let set: ImportedVariableSet
let mockResolver: jest.Mock
beforeEach(() => {
set = { test: { filePath: 'my/path', exportName: 'exportIt' } }
mockResolver = jest.fn()
})

it('should call the resolver', () => {
adaptRequireWithIEV(mockResolver, set)

expect(mockResolver).toHaveBeenCalledWith('my/path')
})
})
53 changes: 53 additions & 0 deletions src/utils/__tests__/resolveImmediatelyExported.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
import babylon from '../../babel-parser'
import resolveImmediatelyExported from '../resolveImmediatelyExported'

describe('resolveImmediatelyExported', () => {
it('should immediately exported varibles', () => {
const ast = babylon().parse('export { test } from "test/path";')
const varNames = resolveImmediatelyExported(ast, ['test'])
expect(varNames).toMatchObject({
test: { filePath: 'test/path', exportName: 'test' },
})
})

it('should immediately exported varibles with aliases', () => {
const ast = babylon().parse('export { test as changedName } from "test/path";')
const varNames = resolveImmediatelyExported(ast, ['changedName'])
expect(varNames).toMatchObject({
changedName: { filePath: 'test/path', exportName: 'test' },
})
})

it('should resolve immediately exported varibles in two steps', () => {
const ast = babylon().parse(
[
'import { test as middleName } from "test/path";',
'export { middleName as changedName };',
].join('\n'),
)
const varNames = resolveImmediatelyExported(ast, ['changedName'])
expect(varNames).toMatchObject({
changedName: { filePath: 'test/path', exportName: 'test' },
})
})

it('should return immediately exported varibles in two steps with default import', () => {
const ast = babylon().parse(
['import test from "test/path";', 'export { test as changedName };'].join('\n'),
)
const varNames = resolveImmediatelyExported(ast, ['changedName'])
expect(varNames).toMatchObject({
changedName: { filePath: 'test/path', exportName: 'default' },
})
})

it('should return immediately exported varibles in two steps with default export', () => {
const ast = babylon().parse(
['import { test } from "test/path";', 'export default test;'].join('\n'),
)
const varNames = resolveImmediatelyExported(ast, ['default'])
expect(varNames).toMatchObject({
default: { filePath: 'test/path', exportName: 'test' },
})
})
})
58 changes: 58 additions & 0 deletions src/utils/adaptExportsToIEV.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
import * as fs from 'fs'
import * as path from 'path'
import Map from 'ts-map'
import buildParser from '../babel-parser'
import cacher from './cacher'
import resolveImmediatelyExported from './resolveImmediatelyExported'
import { ImportedVariableSet } from './resolveRequired'

// tslint:disable-next-line:no-var-requires
import recast = require('recast')

export default function adaptExportsToIEV(
pathResolver: (path: string, originalDirNameOverride?: string) => string,
varToFilePath: ImportedVariableSet,
) {
// key: filepath, content: {key: localName, content: exportedName}
const filePathToVars = new Map<string, Map<string, string>>()
Object.keys(varToFilePath).forEach(k => {
const exportedVariable = varToFilePath[k]
const exportToLocalMap =
filePathToVars.get(exportedVariable.filePath) || new Map<string, string>()
exportToLocalMap.set(k, exportedVariable.exportName)
filePathToVars.set(exportedVariable.filePath, exportToLocalMap)
})

filePathToVars.forEach((exportToLocal, filePath) => {
if (filePath && exportToLocal) {
const exportedVariableNames: string[] = []
exportToLocal.forEach(exportedName => {
if (exportedName) {
exportedVariableNames.push(exportedName)
}
})
try {
const fullFilePath = pathResolver(filePath)
const source = fs.readFileSync(fullFilePath, {
encoding: 'utf-8',
})
const astRemote = cacher(() => recast.parse(source, { parser: buildParser() }), source)
const returnedVariables = resolveImmediatelyExported(astRemote, exportedVariableNames)
exportToLocal.forEach((exported, local) => {
if (exported && local) {
const aliasedVariable = returnedVariables[exported]
if (aliasedVariable) {
aliasedVariable.filePath = pathResolver(
aliasedVariable.filePath,
path.dirname(fullFilePath),
)
varToFilePath[local] = aliasedVariable
}
}
})
} catch (e) {
// ignore load errors
}
}
})
}
10 changes: 10 additions & 0 deletions src/utils/makePathResolver.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import resolveAliases from '../utils/resolveAliases'
import resolvePathFrom from '../utils/resolvePathFrom'

export default function makePathResolver(
refDirName: string,
aliases?: { [alias: string]: string },
): (filePath: string, originalDirNameOverride?: string) => string {
return (filePath: string, originalDirNameOverride?: string): string =>
resolvePathFrom(resolveAliases(filePath, aliases || {}), originalDirNameOverride || refDirName)
}
73 changes: 73 additions & 0 deletions src/utils/resolveImmediatelyExported.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
import * as bt from '@babel/types'
import { NodePath } from 'ast-types'
import { ImportedVariableSet } from './resolveRequired'

// tslint:disable-next-line:no-var-requires
import recast = require('recast')

export default function(ast: bt.File, variableFilter: string[]): ImportedVariableSet {
const variables: ImportedVariableSet = {}

const importedVariablePaths: ImportedVariableSet = {}

// get imported variable names and filepath
recast.visit(ast.program, {
visitImportDeclaration(astPath: NodePath<bt.ImportDeclaration>) {
if (!astPath.node.source) {
return false
}
const filePath = astPath.node.source.value

const specifiers = astPath.get('specifiers')
specifiers.each((s: NodePath<bt.ImportSpecifier | bt.ImportDefaultSpecifier>) => {
const varName = s.node.local.name
const exportName = bt.isImportSpecifier(s.node) ? s.node.imported.name : 'default'
importedVariablePaths[varName] = { filePath, exportName }
})
return false
},
})

recast.visit(ast.program, {
visitExportNamedDeclaration(astPath: NodePath<bt.ExportNamedDeclaration>) {
const specifiers = astPath.get('specifiers')
if (astPath.node.source) {
const filePath = astPath.node.source.value

specifiers.each((s: NodePath<bt.ExportSpecifier>) => {
const varName = s.node.exported.name
const exportName = s.node.local.name
if (variableFilter.indexOf(varName) > -1) {
variables[varName] = { filePath, exportName }
}
})
} else {
specifiers.each((s: NodePath<bt.ExportSpecifier>) => {
const varName = s.node.exported.name
const middleName = s.node.local.name
const importedVar = importedVariablePaths[middleName]
if (importedVar && variableFilter.indexOf(varName) > -1) {
variables[varName] = importedVar
}
})
}

return false
},
visitExportDefaultDeclaration(astPath: NodePath<bt.ExportDefaultDeclaration>) {
if (variableFilter.indexOf('default') > -1) {
const middleNameDeclaration = astPath.node.declaration
if (bt.isIdentifier(middleNameDeclaration)) {
const middleName = middleNameDeclaration.name
const importedVar = importedVariablePaths[middleName]
if (importedVar) {
variables.default = importedVar
}
}
}
return false
},
})

return variables
}
23 changes: 21 additions & 2 deletions src/utils/resolvePathFrom.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,24 @@
const SUFFIXES = ['', '.js', '.ts', '.vue', '/index.js', '/index.ts']

export default function resolvePathFrom(path: string, from: string): string {
return require.resolve(path, {
paths: [from],
let finalPath = ''
SUFFIXES.forEach(s => {
if (!finalPath.length) {
try {
finalPath = require.resolve(`${path}${s}`, {
paths: [from],
})
} catch (e) {
// eat the error
}
}
})

if (!finalPath.length) {
throw new Error(
`Neither '${path}.vue' nor '${path}.js', not even '${path}/index.js' or '${path}/index.ts' could be found in '${from}'`,
)
}

return finalPath
}
13 changes: 9 additions & 4 deletions src/utils/resolveRequired.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,15 @@ import { NodePath } from 'ast-types'
// tslint:disable-next-line:no-var-requires
import recast = require('recast')

interface ImportedVariableToken {
interface ImportedVariable {
filePath: string
exportName: string
}

export interface ImportedVariableSet {
[key: string]: ImportedVariable
}

/**
*
* @param ast
Expand All @@ -17,8 +21,8 @@ interface ImportedVariableToken {
export default function resolveRequired(
ast: bt.File,
varNameFilter?: string[],
): { [key: string]: ImportedVariableToken } {
const varToFilePath: { [key: string]: ImportedVariableToken } = {}
): ImportedVariableSet {
const varToFilePath: ImportedVariableSet = {}

recast.visit(ast.program, {
visitImportDeclaration(astPath: NodePath) {
Expand All @@ -37,8 +41,9 @@ export default function resolveRequired(
if (!varNameFilter || varNameFilter.indexOf(localVariableName) > -1) {
const nodeSource = (astPath.get('source') as NodePath<bt.Literal>).node
if (bt.isStringLiteral(nodeSource)) {
const filePath = nodeSource.value
varToFilePath[localVariableName] = {
filePath: nodeSource.value,
filePath,
exportName,
}
}
Expand Down
15 changes: 15 additions & 0 deletions tests/components/mixin-resolved/button.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
<template>
<button
class="buttonComponent"
@click.prevent="onClick"
:style="`background:${color};width:${size}px;`"
/>
</template>

<script>
import { anotherMixin, myMixin } from '@mixins/index'
export default {
mixins: [anotherMixin, myMixin],
}
</script>
31 changes: 31 additions & 0 deletions tests/components/mixin-resolved/resolved-mixin.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
import * as path from 'path'

import { ComponentDoc, PropDescriptor } from '../../../src/Documentation'
import { parse } from '../../../src/main'
const button = path.join(__dirname, './button.vue')
let docButton: ComponentDoc

describe('tests button', () => {
beforeAll(done => {
docButton = parse(button, {
'@mixins': path.resolve(__dirname, '../../mixins'),
})
done()
})

describe('props', () => {
let props: { [propName: string]: PropDescriptor }

beforeAll(() => {
props = docButton.props ? docButton.props : {}
})

it('should return the "color" prop description from passthrough exported mixin', () => {
expect(props.color.description).toEqual('Another Mixins Error')
})

it('should return the "propsAnother" prop description from a vue file mixin', () => {
expect(props.propsAnother.description).toEqual('Example prop in vue file')
})
})
})

0 comments on commit 00f06f2

Please sign in to comment.