diff --git a/src/parse-script.ts b/src/parse-script.ts index cf0903a..03ba55a 100644 --- a/src/parse-script.ts +++ b/src/parse-script.ts @@ -1,6 +1,7 @@ import { ParserPlugin } from '@babel/parser' import * as bt from '@babel/types' import { NodePath } from 'ast-types' +import Map from 'ts-map' import buildParser from './babel-parser' import { Documentation } from './Documentation' import cacher from './utils/cacher' @@ -11,13 +12,19 @@ import recast = require('recast') const ERROR_MISSING_DEFINITION = 'No suitable component definition found' +interface ParseScriptOptions { + lang: 'ts' | 'js' + filePath: string + nameFilter?: string[] +} + export default function parseScript( source: string, documentation: Documentation, handlers: Array< (doc: Documentation, componentDefinition: NodePath, ast: bt.File, filePath: string) => void >, - options: { lang: 'ts' | 'js'; filePath: string }, + options: ParseScriptOptions, ) { const plugins: ParserPlugin[] = options.lang === 'ts' ? ['typescript'] : ['flow'] @@ -26,28 +33,27 @@ export default function parseScript( throw new Error(ERROR_MISSING_DEFINITION) } - // FIXME: should be a Map - // then the documentation can become a map itself and we can look at component/mixins - // with multiple items inside const componentDefinitions = resolveExportedComponent(ast) - if (componentDefinitions.length === 0) { + if (componentDefinitions.size === 0) { throw new Error(ERROR_MISSING_DEFINITION) } - executeHandlers(handlers, componentDefinitions, documentation, ast, options.filePath) + executeHandlers(handlers, componentDefinitions, documentation, ast, options) } function executeHandlers( localHandlers: Array< (doc: Documentation, componentDefinition: NodePath, ast: bt.File, filePath: string) => void >, - componentDefinitions: NodePath[], + componentDefinitions: Map, documentation: Documentation, ast: bt.File, - filePath: string, + opt: ParseScriptOptions, ) { - return componentDefinitions.forEach(compDef => { - localHandlers.forEach(handler => handler(documentation, compDef, ast, filePath)) + return componentDefinitions.forEach((compDef, name) => { + if (compDef && name && (!opt.nameFilter || opt.nameFilter.indexOf(name) > -1)) { + localHandlers.forEach(handler => handler(documentation, compDef, ast, opt.filePath)) + } }) } diff --git a/src/parse.ts b/src/parse.ts index fd49a1a..d2d8816 100755 --- a/src/parse.ts +++ b/src/parse.ts @@ -16,11 +16,11 @@ const ERROR_EMPTY_DOCUMENT = 'The passed source is empty' * @param {string} filePath path of the current file against whom to resolve the mixins * @returns {object} documentation object */ -export function parseFile(filePath: string, documentation: Documentation) { +export function parseFile(filePath: string, documentation: Documentation, nameFilter?: string[]) { const source = fs.readFileSync(filePath, { encoding: 'utf-8', }) - return parseSource(source, filePath, documentation) + return parseSource(source, filePath, documentation, nameFilter) } /** @@ -29,7 +29,12 @@ export function parseFile(filePath: string, documentation: Documentation) { * @param {string} filePath path of the current file against whom to resolve the mixins * @returns {object} documentation object */ -export function parseSource(source: string, filePath: string, documentation: Documentation) { +export function parseSource( + source: string, + filePath: string, + documentation: Documentation, + nameFilter?: string[], +) { const singleFileComponent = /\.vue$/i.test(path.extname(filePath)) let parts: SFCDescriptor | null = null @@ -48,7 +53,7 @@ export function parseSource(source: string, filePath: string, documentation: Doc /\.tsx?$/i.test(path.extname(filePath)) ? 'ts' : 'js' - parseScript(scriptSource, documentation, handlers, { lang, filePath }) + parseScript(scriptSource, documentation, handlers, { lang, filePath, nameFilter }) } // get slots from template diff --git a/src/script-handlers/__tests__/classDisplayNameHandler.ts b/src/script-handlers/__tests__/classDisplayNameHandler.ts index 43e0786..ea6cc4c 100644 --- a/src/script-handlers/__tests__/classDisplayNameHandler.ts +++ b/src/script-handlers/__tests__/classDisplayNameHandler.ts @@ -23,8 +23,10 @@ describe('classDisplayNameHandler', () => { export default class Decorum extends Vue{ } ` - const def = parse(src) - classDisplayNameHandler(documentation, def[0]) + const def = parse(src).get('default') + if (def) { + classDisplayNameHandler(documentation, def) + } expect(documentation.set).toHaveBeenCalledWith('displayName', 'Decorum') }) @@ -34,8 +36,10 @@ describe('classDisplayNameHandler', () => { export default class Test extends Vue{ } ` - const def = parse(src) - classDisplayNameHandler(documentation, def[0]) + const def = parse(src).get('default') + if (def) { + classDisplayNameHandler(documentation, def) + } expect(documentation.set).toHaveBeenCalledWith('displayName', 'decorum') }) }) diff --git a/src/script-handlers/__tests__/classMethodHandler.ts b/src/script-handlers/__tests__/classMethodHandler.ts index b13ee4b..6183a93 100644 --- a/src/script-handlers/__tests__/classMethodHandler.ts +++ b/src/script-handlers/__tests__/classMethodHandler.ts @@ -1,3 +1,5 @@ +import { NodePath } from 'ast-types' +import Map from 'ts-map' import babylon from '../../babel-parser' import { Documentation, MethodDescriptor } from '../../Documentation' import resolveExportedComponent from '../../utils/resolveExportedComponent' @@ -5,7 +7,7 @@ import classMethodHandler from '../classMethodHandler' jest.mock('../../Documentation') -function parseTS(src: string) { +function parseTS(src: string): Map { const ast = babylon({ plugins: ['typescript'] }).parse(src) return resolveExportedComponent(ast) } @@ -26,8 +28,10 @@ describe('classPropHandler', () => { }) function tester(src: string, matchedObj: any) { - const def = parseTS(src) - classMethodHandler(documentation, def[0]) + const def = parseTS(src).get('default') + if (def) { + classMethodHandler(documentation, def) + } expect(mockMethodDescriptor).toMatchObject(matchedObj) } diff --git a/src/script-handlers/__tests__/classPropHandler.ts b/src/script-handlers/__tests__/classPropHandler.ts index 0b476b6..2497f7b 100644 --- a/src/script-handlers/__tests__/classPropHandler.ts +++ b/src/script-handlers/__tests__/classPropHandler.ts @@ -1,3 +1,5 @@ +import { NodePath } from 'ast-types' +import Map from 'ts-map' import babylon from '../../babel-parser' import { Documentation, PropDescriptor } from '../../Documentation' import resolveExportedComponent from '../../utils/resolveExportedComponent' @@ -5,7 +7,7 @@ import classPropHandler from '../classPropHandler' jest.mock('../../Documentation') -function parse(src: string) { +function parse(src: string): Map { const ast = babylon({ plugins: ['typescript'] }).parse(src) return resolveExportedComponent(ast) } @@ -27,8 +29,8 @@ describe('propHandler', () => { }) function tester(src: string, matchedObj: any) { - const def = parse(src) - classPropHandler(documentation, def[0] as any) + const def = parse(src).get('default') + classPropHandler(documentation, def as any) expect(mockPropDescriptor).toMatchObject(matchedObj) } diff --git a/src/script-handlers/__tests__/componentHandler.ts b/src/script-handlers/__tests__/componentHandler.ts index da8d818..a3c4c87 100644 --- a/src/script-handlers/__tests__/componentHandler.ts +++ b/src/script-handlers/__tests__/componentHandler.ts @@ -1,4 +1,6 @@ import { ParserPlugin } from '@babel/parser' +import { NodePath } from 'ast-types' +import Map from 'ts-map' import babylon from '../../babel-parser' import { Documentation } from '../../Documentation' import resolveExportedComponent from '../../utils/resolveExportedComponent' @@ -6,7 +8,7 @@ import componentHandler from '../componentHandler' jest.mock('../../Documentation') -function parse(src: string, plugins: ParserPlugin[] = []) { +function parse(src: string, plugins: ParserPlugin[] = []): Map { const ast = babylon({ plugins }).parse(src) return resolveExportedComponent(ast) } @@ -27,8 +29,10 @@ describe('componentHandler', () => { name: 'name-123', } ` - const def = parse(src) - componentHandler(documentation, def[0]) + const def = parse(src).get('default') + if (def) { + componentHandler(documentation, def) + } expect(documentation.set).toHaveBeenCalledWith('description', 'An empty component') }) @@ -43,8 +47,10 @@ describe('componentHandler', () => { name: 'name-123', } ` - const def = parse(src) - componentHandler(documentation, def[0]) + const def = parse(src).get('default') + if (def) { + componentHandler(documentation, def) + } expect(documentation.set).toHaveBeenCalledWith('tags', { author: [{ description: '[Rafael]', title: 'author' }], version: [{ description: '12.5.7', title: 'version' }], @@ -62,8 +68,10 @@ describe('componentHandler', () => { } ` - const def = parse(src, ['typescript']) - componentHandler(documentation, def[0]) + const def = parse(src, ['typescript']).get('default') + if (def) { + componentHandler(documentation, def) + } expect(documentation.set).toHaveBeenCalledWith('tags', { version: [{ description: '12.5.7', title: 'version' }], }) diff --git a/src/script-handlers/__tests__/displayNameHandler.ts b/src/script-handlers/__tests__/displayNameHandler.ts index f0ca5d3..d68fb98 100644 --- a/src/script-handlers/__tests__/displayNameHandler.ts +++ b/src/script-handlers/__tests__/displayNameHandler.ts @@ -1,3 +1,4 @@ +import { NodePath } from 'ast-types' import babylon from '../../babel-parser' import { Documentation } from '../../Documentation' import resolveExportedComponent from '../../utils/resolveExportedComponent' @@ -5,9 +6,9 @@ import displayNameHandler from '../displayNameHandler' jest.mock('../../Documentation') -function parse(src: string) { +function parse(src: string): NodePath | undefined { const ast = babylon().parse(src) - return resolveExportedComponent(ast) + return resolveExportedComponent(ast).get('default') } describe('displayNameHandler', () => { @@ -27,7 +28,9 @@ describe('displayNameHandler', () => { } ` const def = parse(src) - displayNameHandler(documentation, def[0]) + if (def) { + displayNameHandler(documentation, def) + } expect(documentation.set).toHaveBeenCalledWith('displayName', 'name-123') }) @@ -42,7 +45,9 @@ describe('displayNameHandler', () => { } ` const def = parse(src) - displayNameHandler(documentation, def[0]) + if (def) { + displayNameHandler(documentation, def) + } expect(documentation.set).toHaveBeenCalledWith('displayName', 'name-123') }) }) diff --git a/src/script-handlers/__tests__/eventHandler.ts b/src/script-handlers/__tests__/eventHandler.ts index edfa8d3..91f0505 100644 --- a/src/script-handlers/__tests__/eventHandler.ts +++ b/src/script-handlers/__tests__/eventHandler.ts @@ -6,9 +6,9 @@ import eventHandler from '../eventHandler' jest.mock('../../Documentation') -function parse(src: string): NodePath[] { +function parse(src: string): NodePath | undefined { const ast = babylon().parse(src) - return resolveExportedComponent(ast) + return resolveExportedComponent(ast).get('default') } describe('eventHandler', () => { @@ -38,7 +38,9 @@ describe('eventHandler', () => { } ` const def = parse(src) - eventHandler(documentation, def[0]) + if (def) { + eventHandler(documentation, def) + } const eventComp: EventDescriptor = { description: 'Describe the event', properties: [ @@ -71,7 +73,9 @@ describe('eventHandler', () => { } ` const def = parse(src) - eventHandler(documentation, def[0]) + if (def) { + eventHandler(documentation, def) + } const eventComp: EventDescriptor = { description: '', type: { diff --git a/src/script-handlers/__tests__/extendsHandler.ts b/src/script-handlers/__tests__/extendsHandler.ts index 8575230..0c7d0e9 100644 --- a/src/script-handlers/__tests__/extendsHandler.ts +++ b/src/script-handlers/__tests__/extendsHandler.ts @@ -19,7 +19,9 @@ describe('extendsHandler', () => { resolveRequiredMock = resolveRequired as jest.Mock< (ast: bt.File, varNameFilter?: string[]) => { [key: string]: string } > - resolveRequiredMock.mockReturnValue({ testComponent: './componentPath' }) + resolveRequiredMock.mockReturnValue({ + testComponent: { filePath: './componentPath', exportName: 'default' }, + }) mockResolvePathFrom = resolvePathFrom as jest.Mock<(path: string, from: string) => string> mockResolvePathFrom.mockReturnValue('./component/full/path') @@ -30,8 +32,10 @@ describe('extendsHandler', () => { function parseItExtends(src: string) { const ast = babylon().parse(src) - const path = resolveExportedComponent(ast) - extendsHandler(doc, path[0], ast, '') + const path = resolveExportedComponent(ast).get('default') + if (path) { + extendsHandler(doc, path, ast, '') + } } it('should resolve extended modules variables in import default', () => { @@ -42,7 +46,7 @@ describe('extendsHandler', () => { '}', ].join('\n') parseItExtends(src) - expect(parseFile).toHaveBeenCalledWith('./component/full/path', doc) + expect(parseFile).toHaveBeenCalledWith('./component/full/path', doc, ['default']) }) it('should resolve extended modules variables in require', () => { @@ -53,27 +57,28 @@ describe('extendsHandler', () => { '}', ].join('\n') parseItExtends(src) - expect(parseFile).toHaveBeenCalledWith('./component/full/path', doc) + expect(parseFile).toHaveBeenCalledWith('./component/full/path', doc, ['default']) }) it('should resolve extended modules variables in import', () => { const src = [ - 'import { testComponent, other } from "./testComponent"', + 'import { test as testComponent, other } from "./testComponent"', 'export default {', ' extends:testComponent', '}', ].join('\n') parseItExtends(src) - expect(parseFile).toHaveBeenCalledWith('./component/full/path', doc) + expect(parseFile).toHaveBeenCalledWith('./component/full/path', doc, ['default']) }) it('should resolve extended modules variables in class style components', () => { const src = [ - 'import { testComponent} from "./testComponent"', + 'import { testComponent } from "./testComponent";', + '@Component', 'export default class Bart extends testComponent {', '}', ].join('\n') parseItExtends(src) - expect(parseFile).toHaveBeenCalledWith('./component/full/path', doc) + expect(parseFile).toHaveBeenCalledWith('./component/full/path', doc, ['default']) }) }) diff --git a/src/script-handlers/__tests__/methodHandler.ts b/src/script-handlers/__tests__/methodHandler.ts index d469846..1d91651 100644 --- a/src/script-handlers/__tests__/methodHandler.ts +++ b/src/script-handlers/__tests__/methodHandler.ts @@ -1,3 +1,4 @@ +import { NodePath } from 'ast-types' import babylon from '../../babel-parser' import { Documentation, MethodDescriptor } from '../../Documentation' import resolveExportedComponent from '../../utils/resolveExportedComponent' @@ -5,14 +6,14 @@ import propHandler from '../methodHandler' jest.mock('../../Documentation') -function parse(src: string) { +function parse(src: string): NodePath | undefined { const ast = babylon({ plugins: ['flow'] }).parse(src) - return resolveExportedComponent(ast) + return resolveExportedComponent(ast).get('default') } -function parseTS(src: string) { +function parseTS(src: string): NodePath | undefined { const ast = babylon({ plugins: ['typescript'] }).parse(src) - return resolveExportedComponent(ast) + return resolveExportedComponent(ast).get('default') } describe('methodHandler', () => { @@ -32,7 +33,9 @@ describe('methodHandler', () => { function tester(src: string, matchedObj: any) { const def = parse(src) - propHandler(documentation, def[0]) + if (def) { + propHandler(documentation, def) + } expect(mockMethodDescriptor).toMatchObject(matchedObj) } @@ -235,7 +238,9 @@ describe('methodHandler', () => { ].join('\n') const def = parse(src) - propHandler(documentation, def[0]) + if (def) { + propHandler(documentation, def) + } expect(mockMethodDescriptor).toMatchObject({ name: 'publicMethod', params: [ @@ -261,7 +266,9 @@ describe('methodHandler', () => { ].join('\n') const def = parse(src) - propHandler(documentation, def[0]) + if (def) { + propHandler(documentation, def) + } expect(mockMethodDescriptor).toMatchObject({ name: 'publicMethod', returns: { type: { name: 'string' } }, @@ -284,8 +291,10 @@ describe('methodHandler', () => { } ` - const def = parseTS(src) - propHandler(documentation, def[0]) + const def = parse(src) + if (def) { + propHandler(documentation, def) + } expect(mockMethodDescriptor).toMatchObject({ name: 'publicMethod', params: [ @@ -310,7 +319,9 @@ describe('methodHandler', () => { ` const def = parseTS(src) - propHandler(documentation, def[0]) + if (def) { + propHandler(documentation, def) + } expect(mockMethodDescriptor).toMatchObject({ name: 'twoMethod', returns: { type: { name: 'number' } }, diff --git a/src/script-handlers/__tests__/mixinsHandler.ts b/src/script-handlers/__tests__/mixinsHandler.ts index d03db28..4cb900b 100644 --- a/src/script-handlers/__tests__/mixinsHandler.ts +++ b/src/script-handlers/__tests__/mixinsHandler.ts @@ -19,7 +19,9 @@ describe('mixinsHandler', () => { resolveRequiredMock = resolveRequired as jest.Mock< (ast: bt.File, varNameFilter?: string[]) => { [key: string]: string } > - resolveRequiredMock.mockReturnValue({ testComponent: 'componentPath' }) + resolveRequiredMock.mockReturnValue({ + testComponent: { filePath: 'componentPath', exportName: 'default' }, + }) mockResolvePathFrom = resolvePathFrom as jest.Mock<(path: string, from: string) => string> mockResolvePathFrom.mockReturnValue('component/full/path') @@ -49,8 +51,10 @@ describe('mixinsHandler', () => { ].join('\n'), ])('should resolve extended modules variables', src => { const ast = babelParser().parse(src) - const path = resolveExportedComponent(ast) - mixinsHandler(doc, path[0], ast, '') - expect(parseFile).toHaveBeenCalledWith('component/full/path', doc) + const path = resolveExportedComponent(ast).get('default') + if (path) { + mixinsHandler(doc, path, ast, '') + } + expect(parseFile).toHaveBeenCalledWith('component/full/path', doc, ['default']) }) }) diff --git a/src/script-handlers/__tests__/propHandler.ts b/src/script-handlers/__tests__/propHandler.ts index 61c0f43..7f33f65 100644 --- a/src/script-handlers/__tests__/propHandler.ts +++ b/src/script-handlers/__tests__/propHandler.ts @@ -1,3 +1,4 @@ +import { NodePath } from 'ast-types' import babylon from '../../babel-parser' import { Documentation, PropDescriptor } from '../../Documentation' import resolveExportedComponent from '../../utils/resolveExportedComponent' @@ -5,9 +6,9 @@ import propHandler from '../propHandler' jest.mock('../../Documentation') -function parse(src: string) { +function parse(src: string): NodePath | undefined { const ast = babylon().parse(src) - return resolveExportedComponent(ast) + return resolveExportedComponent(ast).get('default') } describe('propHandler', () => { @@ -27,7 +28,9 @@ describe('propHandler', () => { function tester(src: string, matchedObj: any) { const def = parse(src) - propHandler(documentation, def[0]) + if (def) { + propHandler(documentation, def) + } expect(mockPropDescriptor).toMatchObject(matchedObj) } @@ -38,7 +41,9 @@ describe('propHandler', () => { props: ['testArray'] }` const def = parse(src) - propHandler(documentation, def[0]) + if (def) { + propHandler(documentation, def) + } expect(mockPropDescriptor.required).toEqual('') expect(documentation.getPropDescriptor).toHaveBeenCalledWith('testArray') }) @@ -114,7 +119,9 @@ describe('propHandler', () => { } ` const def = parse(src) - propHandler(documentation, def[0]) + if (def) { + propHandler(documentation, def) + } expect(mockPropDescriptor.required).toBeUndefined() }) diff --git a/src/script-handlers/__tests__/slotHandler.ts b/src/script-handlers/__tests__/slotHandler.ts index 47e8c05..c1e2227 100644 --- a/src/script-handlers/__tests__/slotHandler.ts +++ b/src/script-handlers/__tests__/slotHandler.ts @@ -6,9 +6,9 @@ import slotHandler from '../slotHandler' jest.mock('../../Documentation') -function parse(src: string): NodePath[] { +function parse(src: string): NodePath | undefined { const ast = babylon().parse(src) - return resolveExportedComponent(ast) + return resolveExportedComponent(ast).get('default') } describe('render function slotHandler', () => { @@ -31,7 +31,9 @@ describe('render function slotHandler', () => { } ` const def = parse(src) - slotHandler(documentation, def[0]) + if (def) { + slotHandler(documentation, def) + } expect(documentation.getSlotDescriptor).toHaveBeenCalledWith('mySlot') }) @@ -48,7 +50,9 @@ describe('render function slotHandler', () => { } ` const def = parse(src) - slotHandler(documentation, def[0]) + if (def) { + slotHandler(documentation, def) + } expect(documentation.getSlotDescriptor).toHaveBeenCalledWith('myScopedSlot') }) }) diff --git a/src/script-handlers/extendsHandler.ts b/src/script-handlers/extendsHandler.ts index 4d8e4fe..713a2b0 100644 --- a/src/script-handlers/extendsHandler.ts +++ b/src/script-handlers/extendsHandler.ts @@ -30,10 +30,13 @@ export default function extendsHandler( const originalDirName = path.dirname(originalFilePath) - // only look for documentation in the current project - if (/^\./.test(extendsFilePath[extendsVariableName])) { - const fullFilePath = resolvePathFrom(extendsFilePath[extendsVariableName], originalDirName) - parseFile(fullFilePath, documentation) + // only look for documentation in the current project not in node_modules + if (/^\./.test(extendsFilePath[extendsVariableName].filePath)) { + const fullFilePath = resolvePathFrom( + extendsFilePath[extendsVariableName].filePath, + originalDirName, + ) + parseFile(fullFilePath, documentation, [extendsFilePath[extendsVariableName].exportName]) } } diff --git a/src/script-handlers/mixinsHandler.ts b/src/script-handlers/mixinsHandler.ts index fe80f4e..743c29d 100644 --- a/src/script-handlers/mixinsHandler.ts +++ b/src/script-handlers/mixinsHandler.ts @@ -30,9 +30,10 @@ export default function mixinsHandler( // get each doc for each mixin using parse for (const varName of Object.keys(mixinVarToFilePath)) { - const filePath = mixinVarToFilePath[varName] + // TODO: consolidate variables accessing the same file + const { filePath, exportName } = mixinVarToFilePath[varName] const fullFilePath = resolvePathFrom(filePath, originalDirName) - parseFile(fullFilePath, documentation) + parseFile(fullFilePath, documentation, [exportName]) } } diff --git a/src/utils/__tests__/resolveExportedComponent.ts b/src/utils/__tests__/resolveExportedComponent.ts index e70b3be..83be268 100644 --- a/src/utils/__tests__/resolveExportedComponent.ts +++ b/src/utils/__tests__/resolveExportedComponent.ts @@ -4,50 +4,55 @@ import resolveExportedComponent from '../resolveExportedComponent' describe('resolveExportedComponent', () => { it('should return an export default', () => { const ast = babylon().parse('export default {}') - expect(resolveExportedComponent(ast).length).toBe(1) + expect(resolveExportedComponent(ast).size).toBe(1) }) - it('should return an es6 export', () => { + it('should return an es6 export with its name', () => { const ast = babylon().parse('export const test = {}') - expect(resolveExportedComponent(ast).length).toBe(1) + expect(resolveExportedComponent(ast).get('test')).not.toBeUndefined() + }) + + it('should return an es6 export with its name even with 2 statements', () => { + const ast = babylon().parse(['const testTwoLines = {};', 'export { testTwoLines };'].join('\n')) + expect(resolveExportedComponent(ast).get('testTwoLines')).not.toBeUndefined() }) it('should return an es5 export', () => { const ast = babylon().parse('module.exports = {};') - expect(resolveExportedComponent(ast).length).toBe(1) + expect(resolveExportedComponent(ast).get('default')).not.toBeUndefined() }) it('should return an es5 export direct', () => { const ast = babylon().parse('exports = {};') - expect(resolveExportedComponent(ast).length).toBe(1) + expect(resolveExportedComponent(ast).size).toBe(1) }) it('should return an es5 exports.variable', () => { const ast = babylon().parse('exports.xxx = {};') - expect(resolveExportedComponent(ast).length).toBe(1) + expect(resolveExportedComponent(ast).get('xxx')).not.toBeUndefined() }) it('should return indirectly exported components', () => { const ast = babylon().parse(['const test = {}', 'export default test'].join('\n')) - expect(resolveExportedComponent(ast).length).toBe(1) + expect(resolveExportedComponent(ast).size).toBe(1) }) it('should return indirectly exported class style components', () => { const ast = babylon().parse( ['@Component()', 'class testClass extends Vue{}', 'export default testClass'].join('\n'), ) - expect(resolveExportedComponent(ast).length).toBe(1) + expect(resolveExportedComponent(ast).get('default')).not.toBeUndefined() }) it('should return indirectly exported components es5', () => { const ast = babylon().parse(['const test = {}', 'module.exports = test'].join('\n')) - expect(resolveExportedComponent(ast).length).toBe(1) + expect(resolveExportedComponent(ast).size).toBe(1) }) it('should return exported class style components', () => { const ast = babylon().parse( ['@Component()', 'export default class Bart extends testComponent {}'].join('\n'), ) - expect(resolveExportedComponent(ast).length).toBe(1) + expect(resolveExportedComponent(ast).size).toBe(1) }) }) diff --git a/src/utils/__tests__/resolveRequired.ts b/src/utils/__tests__/resolveRequired.ts index 8a521a4..ed56380 100644 --- a/src/utils/__tests__/resolveRequired.ts +++ b/src/utils/__tests__/resolveRequired.ts @@ -5,13 +5,26 @@ describe('resolveRequired', () => { it('should resolve imported variables', () => { const ast = babylon().parse('import {test, bonjour} from "test/path";') const varNames = resolveRequired(ast) - expect(varNames).toMatchObject({ test: 'test/path', bonjour: 'test/path' }) + expect(varNames).toMatchObject({ + test: { filePath: 'test/path', exportName: 'test' }, + bonjour: { filePath: 'test/path', exportName: 'bonjour' }, + }) }) it('should resolve imported default', () => { const ast = babylon().parse('import bonjour from "test/path";') const varNames = resolveRequired(ast) - expect(varNames).toMatchObject({ bonjour: 'test/path' }) + expect(varNames).toMatchObject({ + bonjour: { filePath: 'test/path', exportName: 'default' }, + }) + }) + + it('should resolve imported variable as another name', () => { + const ast = babylon().parse('import {bonjour as hello} from "test/path";') + const varNames = resolveRequired(ast) + expect(varNames).toMatchObject({ + hello: { filePath: 'test/path', exportName: 'bonjour' }, + }) }) it('should resolve required variables', () => { @@ -23,8 +36,8 @@ describe('resolveRequired', () => { ].join('\n'), ) expect(resolveRequired(ast)).toMatchObject({ - hello: 'test/pathEN', - bonjour: 'test/pathFR', + hello: { filePath: 'test/pathEN', exportName: 'default' }, + bonjour: { filePath: 'test/pathFR', exportName: 'default' }, }) }) @@ -37,13 +50,13 @@ describe('resolveRequired', () => { ].join('\n'), ) expect(resolveRequired(ast)).toMatchObject({ - ciao: 'test/pathOther', - astaruego: 'test/pathOther', - sayonara: 'test/pathJP', + ciao: { filePath: 'test/pathOther', exportName: 'default' }, + astaruego: { filePath: 'test/pathOther', exportName: 'default' }, + sayonara: { filePath: 'test/pathJP', exportName: 'default' }, }) }) - it('should not require non required variables', () => { + it('should not return non required variables', () => { const ast = babylon().parse('const sayonara = "Japanese Hello";') expect(resolveRequired(ast).sayonara).toBeUndefined() }) diff --git a/src/utils/resolveExportDeclaration.ts b/src/utils/resolveExportDeclaration.ts index 0da12da..c3abb4e 100644 --- a/src/utils/resolveExportDeclaration.ts +++ b/src/utils/resolveExportDeclaration.ts @@ -1,26 +1,39 @@ import * as bt from '@babel/types' import { NodePath } from 'ast-types' +import Map from 'ts-map' -export default function resolveExportDeclaration(path: NodePath): NodePath[] { - const definitions: NodePath[] = [] +export default function resolveExportDeclaration(path: NodePath): Map { + const definitions = new Map() if (bt.isExportDefaultDeclaration(path.node)) { const defaultPath = path as NodePath - definitions.push(defaultPath.get('declaration')) + definitions.set('default', defaultPath.get('declaration')) } else if (bt.isExportNamedDeclaration(path.node)) { const declaration = path.get('declaration') + // export const example = {} if (declaration && bt.isVariableDeclaration(declaration.node)) { - declaration.get('declarations').each((declarator: NodePath) => definitions.push(declarator)) + declaration.get('declarations').each((declarator: NodePath) => { + const nodeId = declarator.node.id + if (bt.isIdentifier(nodeId)) { + definitions.set(nodeId.name, declarator) + } + }) } else { - definitions.push(path.get('declaration') as NodePath) + // const example = {} + // export { example } + getDefinitionsFromPathSpecifiers(path, definitions) } } else if (bt.isExportDeclaration(path.node)) { - const declarePath = path - const specifiersPath = declarePath.get('specifiers') - specifiersPath.each((specifier: NodePath) => { - definitions.push( - bt.isExportSpecifier(specifier.node) ? specifier.get('local') : specifier.get('exported'), - ) - }) + getDefinitionsFromPathSpecifiers(path, definitions) } return definitions } + +function getDefinitionsFromPathSpecifiers(path: NodePath, defs: Map) { + const specifiersPath = path.get('specifiers') + specifiersPath.each((specifier: NodePath) => { + defs.set( + specifier.node.exported.name, + bt.isExportSpecifier(specifier.node) ? specifier.get('local') : specifier.get('exported'), + ) + }) +} diff --git a/src/utils/resolveExportedComponent.ts b/src/utils/resolveExportedComponent.ts index b8d2472..9797258 100755 --- a/src/utils/resolveExportedComponent.ts +++ b/src/utils/resolveExportedComponent.ts @@ -1,5 +1,6 @@ import * as bt from '@babel/types' import { NodePath } from 'ast-types' +import Map from 'ts-map' import isExportedAssignment from './isExportedAssignment' import resolveExportDeclaration from './resolveExportDeclaration' @@ -45,28 +46,25 @@ function isComponentDefinition(path: NodePath): boolean { * export default Definition; * export var Definition = ...; */ -export default function resolveExportedComponent(ast: bt.File): NodePath[] { - const components: NodePath[] = [] +export default function resolveExportedComponent(ast: bt.File): Map { + const components = new Map() - function setComponent(definition: NodePath) { - if (definition && components.indexOf(definition) === -1) { - components.push(normalizeComponentPath(definition)) + function setComponent(exportName: string, definition: NodePath) { + if (definition && !components.get(exportName)) { + components.set(exportName, normalizeComponentPath(definition)) } } // function run for every non "assignment" export declaration // in extenso export default or export myvar function exportDeclaration(path: NodePath) { - const definitions = resolveExportDeclaration(path).reduce((acc: NodePath[], definition) => { + const definitions = resolveExportDeclaration(path) + + definitions.forEach((definition: NodePath, name: string) => { const realDef = resolveIdentifier(ast, definition) if (realDef && isComponentDefinition(realDef)) { - acc.push(realDef) + setComponent(name, realDef) } - return acc - }, []) - - definitions.forEach((definition: NodePath) => { - setComponent(definition) }) return false } @@ -102,12 +100,20 @@ export default function resolveExportedComponent(ast: bt.File): NodePath[] { // Resolve the value of the right hand side. It should resolve to a call // expression, something like Vue.extend({}) const pathRight = path.get('right') + const pathLeft = path.get('left') const realComp = resolveIdentifier(ast, pathRight) if (!realComp || !isComponentDefinition(realComp)) { return false } - setComponent(realComp) + const name = + bt.isMemberExpression(pathLeft.node) && + bt.isIdentifier(pathLeft.node.property) && + pathLeft.node.property.name !== 'exports' + ? pathLeft.node.property.name + : 'default' + + setComponent(name, realComp) return false }, }) diff --git a/src/utils/resolveRequired.ts b/src/utils/resolveRequired.ts index 02c5bcc..34a7945 100644 --- a/src/utils/resolveRequired.ts +++ b/src/utils/resolveRequired.ts @@ -4,6 +4,11 @@ import { NodePath } from 'ast-types' // tslint:disable-next-line:no-var-requires import recast = require('recast') +interface ImportedVariableToken { + filePath: string + exportName: string +} + /** * * @param ast @@ -12,8 +17,8 @@ import recast = require('recast') export default function resolveRequired( ast: bt.File, varNameFilter?: string[], -): { [key: string]: string } { - const varToFilePath: { [key: string]: string } = {} +): { [key: string]: ImportedVariableToken } { + const varToFilePath: { [key: string]: ImportedVariableToken } = {} recast.visit(ast.program, { visitImportDeclaration(astPath: NodePath) { @@ -23,11 +28,19 @@ export default function resolveRequired( specifiers.each((sp: NodePath) => { const nodeSpecifier = sp.node if (bt.isImportDefaultSpecifier(nodeSpecifier) || bt.isImportSpecifier(nodeSpecifier)) { - const varNameDefault = nodeSpecifier.local.name - if (!varNameFilter || varNameFilter.indexOf(varNameDefault) > -1) { + const localVariableName = nodeSpecifier.local.name + + const exportName = bt.isImportDefaultSpecifier(nodeSpecifier) + ? 'default' + : nodeSpecifier.imported.name + + if (!varNameFilter || varNameFilter.indexOf(localVariableName) > -1) { const nodeSource = (astPath.get('source') as NodePath).node - if (bt.isLiteral(nodeSource)) { - varToFilePath[varNameDefault] = (nodeSource as bt.StringLiteral).value + if (bt.isStringLiteral(nodeSource)) { + varToFilePath[localVariableName] = { + filePath: nodeSource.value, + exportName, + } } } } @@ -43,10 +56,16 @@ export default function resolveRequired( astPath.node.declarations.forEach(nodeDeclaration => { let sourceNode: bt.Node let source: string = '' - const init = + + const { init, exportName } = nodeDeclaration.init && bt.isMemberExpression(nodeDeclaration.init) - ? nodeDeclaration.init.object - : nodeDeclaration.init + ? { + init: nodeDeclaration.init.object, + exportName: bt.isIdentifier(nodeDeclaration.init.property) + ? nodeDeclaration.init.property.name + : 'default', + } + : { init: nodeDeclaration.init, exportName: 'default' } if (!init) { return } @@ -56,20 +75,21 @@ export default function resolveRequired( return } sourceNode = init.arguments[0] - if (!bt.isLiteral(sourceNode)) { + if (!bt.isStringLiteral(sourceNode)) { return } - source = (sourceNode as bt.StringLiteral).value + source = sourceNode.value } else { return } if (bt.isIdentifier(nodeDeclaration.id)) { const varName = nodeDeclaration.id.name - varToFilePath[varName] = source + varToFilePath[varName] = { filePath: source, exportName } } else if (bt.isObjectPattern(nodeDeclaration.id)) { nodeDeclaration.id.properties.forEach((p: bt.ObjectProperty) => { - varToFilePath[p.key.name] = source + const varName = p.key.name + varToFilePath[varName] = { filePath: source, exportName } }) } else { return diff --git a/tests/components/button/Button.vue b/tests/components/button/Button.vue index 0b03a18..4eb4360 100644 --- a/tests/components/button/Button.vue +++ b/tests/components/button/Button.vue @@ -1,8 +1,8 @@