Skip to content
This repository has been archived by the owner on Oct 23, 2023. It is now read-only.

Commit

Permalink
fix: resolve export names correctly
Browse files Browse the repository at this point in the history
  • Loading branch information
marionebl authored and tilmx committed Mar 21, 2019
1 parent a6e3b0e commit 47c7384
Show file tree
Hide file tree
Showing 10 changed files with 337 additions and 212 deletions.
19 changes: 18 additions & 1 deletion .vscode/launch.json
Expand Up @@ -107,6 +107,23 @@
"sourceMaps": true,
"console": "integratedTerminal",
"internalConsoleOptions": "neverOpen"
}
},
{
"type": "node",
"request": "launch",
"name": "Debug Experiments",
"program": "${workspaceRoot}/node_modules/.bin/ts-node",
"runtimeArgs": [
"--inspect-brk"
],
"args": [
"-O",
"{\"module\": \"commonjs\"}",
"test.ts"
],
"sourceMaps": true,
"console": "integratedTerminal",
"internalConsoleOptions": "neverOpen"
},
]
}
3 changes: 2 additions & 1 deletion package.json
Expand Up @@ -54,12 +54,13 @@
"jest-dom": "^3.0.0",
"jest-util": "^23.4.0",
"lerna": "^3.6.0",
"monaco-editor-webpack-plugin": "1.4.0",
"lint-staged": "7.0.4",
"monaco-editor-webpack-plugin": "1.4.0",
"prettier": "1.12.0",
"style-loader": "0.21.0",
"ts-jest": "^23.10.5",
"ts-loader": "4.4.1",
"ts-node": "^8.0.3",
"tslint": "5.11.0",
"tslint-config-prettier": "1.15.0",
"typescript": "3.2.2",
Expand Down
Expand Up @@ -79,6 +79,7 @@ test('creates defaults for valid blocks', () => {
program: file.program,
project,
pkg: { name: 'name' },
pkgPath: 'package.json',
getSlotId: id => id
});

Expand Down
Expand Up @@ -13,6 +13,7 @@ export interface SlotAnalyzeContext {
program: Ts.Program;
project: tsa.Project;
pkg: unknown;
pkgPath: string;
getSlotId(contextId: string): string;
}

Expand Down Expand Up @@ -77,6 +78,7 @@ export function analyzeSlots(
id,
project: ctx.project,
pkg: ctx.pkg,
pkgPath: ctx.pkgPath,
path: path
? Path.dirname(path)
: ctx.project.getRootDirectories()[0]!.getPath()
Expand Down
@@ -1,79 +1,46 @@
import * as Path from 'path';
import * as tsa from 'ts-simple-ast';
import { analyzeSlotDefault } from './slot-default-analyzer';
import { analyzeSlotDefault, getExportSpecifier } from './slot-default-analyzer';
import * as uuid from 'uuid';

interface TestContext {
project: tsa.Project;
pkgPath: string;
path: string;
}

const ctx = ({
path: __dirname,
pkgPath: Path.join(__dirname, 'package.json')
} as any) as TestContext;
jest.mock('find-pkg', () => ({
sync: (input: string) => '/package.json'
}));

beforeAll(() => {
ctx.project = new tsa.Project();
});

test('works with Text from @meetalva/essentials', () => {
const result = analyzeSlotDefault(
`
import * as React from 'react';
import { Text } from '@meetalva/essentials';
export default () => <Text text="Hello, World!"/>
`,
{ ...ctx, pkg: { name: 'name' }, id: 'meetalva-essentials-text' }
);

expect(result).toEqual(
expect.objectContaining({
id: 'meetalva-essentials-text:default',
parent: 'meetalva-essentials-text'
})
);
});

test('works with JSX.IntrinsicElement', () => {
const result = analyzeSlotDefault(
`
import * as React from 'react';
export default () => <div>Hello, World</div>;
`,
{ ...ctx, pkg: { name: 'name' }, id: 'jsxintrinsic' }
);
jest.mock('../react-utils/find-react-component-type', () => ({
findReactComponentType: () => true
}));

expect(result).toBeUndefined();
});
test('picks up string props', () => {
const project = new tsa.Project({ useVirtualFileSystem: true, addFilesFromTsConfig: false });

test('ignores modules without default export', () => {
const result = analyzeSlotDefault(
`
import * as React from 'react';
import { Text } from '@meetalva/essentials';
export const HelloWorld () => <Text/>;
`,
{ ...ctx, pkg: { name: 'name' }, id: 'no-default-export' }
project.createSourceFile(
'/a.ts',
`import * as React from 'react'; export const A: React.SFC = () => null;`
);

expect(result).toBeUndefined();
});

test('picks up string props', () => {
const result = analyzeSlotDefault(
`
import * as React from 'react';
import { Text } from '@meetalva/essentials';
export default () => <Text text="Hello, World!"/>
import { A } from '/a';
export default () => <A text="Hello, World!"/>
`,
{ ...ctx, pkg: { name: 'name' }, id: 'meetalva-essentials-text' }
{
project,
pkgPath: '/package.json',
path: '/b.ts',
pkg: { name: 'name' },
id: 'string-props'
}
);

expect(result).toEqual(
expect.objectContaining({
id: 'meetalva-essentials-text:default',
parent: 'meetalva-essentials-text',
props: [
{
propName: 'text',
Expand All @@ -85,83 +52,152 @@ test('picks up string props', () => {
});

test('picks up number props', () => {
const project = new tsa.Project({ useVirtualFileSystem: true, addFilesFromTsConfig: false });

project.createSourceFile(
'/a.ts',
`import * as React from 'react'; export const A: React.SFC = () => null;`
);

const result = analyzeSlotDefault(
`
import * as React from 'react';
import { Box } from '@meetalva/essentials';
export default () => <Box flex={0} />
import { A } from '/a';
export default () => <A num={0} />
`,
{ ...ctx, pkg: { name: 'name' }, id: 'meetalva-essentials-box' }
{ project, pkgPath: 'package.json', pkg: { name: 'name' }, id: 'number-props', path: '/b.ts' }
);

expect(result).toEqual(
expect.objectContaining({
props: expect.arrayContaining([
{
propName: 'flex',
propName: 'num',
value: 0
}
])
})
);
});

test('exposes pattern id via .patternContextId', () => {
test('supports multiple levels of JSX elements', () => {
const project = new tsa.Project({ useVirtualFileSystem: true, addFilesFromTsConfig: false });

project.createSourceFile(
'/ab.ts',
`import * as React from 'react'; export const A: React.SFC = () => null; export const B: React.SFC = () => null`
);

const result = analyzeSlotDefault(
`
import * as React from 'react';
import { Text } from '@meetalva/essentials';
export default () => <Text text="Hello, World!"/>
import { A, B } from '/ab';
export default () => <A><B/></A>
`,
{ ...ctx, pkg: { name: 'name' }, id: 'meetalva-essentials-text-id' }
{
path: '/b.ts',
project,
pkg: { name: 'name' },
pkgPath: '/package.json',
id: 'nested-jsx'
}
);

expect(result).toEqual(
expect.objectContaining({
patternContextId: 'lib/text.d.ts:Text'
patternContextId: 'ab.ts:A',
children: [
expect.objectContaining({
patternContextId: 'ab.ts:B'
})
]
})
);
});

test('determines library id with scopes', () => {
test('works with property access jsx tag names', () => {
const project = new tsa.Project({ useVirtualFileSystem: true, addFilesFromTsConfig: false });

project.createSourceFile(
'/a.ts',
`import * as React from 'react'; export const B: React.SFC = () => null`
);

const result = analyzeSlotDefault(
`
import * as React from 'react';
import { Text } from '@meetalva/essentials';
export default () => <Text text="Hello, World!"/>
import * as A from '/a';
export default () => <A.B />
`,
{
...ctx,
pkg: { name: '@meetalva/essentials' },
id: 'meetalva-essentials-text-library-id-scopes'
path: '/b.ts',
project,
pkg: { name: 'name' },
pkgPath: '/package.json',
id: 'property-access'
}
);

expect(result).toEqual(
expect.objectContaining({
libraryId: '@meetalva/essentials'
patternContextId: 'a.ts:B'
})
);
});

test('supports multiple levels of JSX elements', () => {
const result = analyzeSlotDefault(
test('determines export names correctly', () => {
const project = new tsa.Project({ useVirtualFileSystem: true, addFilesFromTsConfig: false });

const file = project.createSourceFile(
`${uuid.v4()}`,
`
import * as React from 'react';
import { Box, Text } from '@meetalva/essentials';
export default () => <Box><Text text="Hello, World!"/></Box>
export const A = () => {};
export function B() {}
export class C {}
const D = 'D';
const F = D;
export const G = F;
`,
{ ...ctx, pkg: { name: 'name' }, id: 'meetalva-essentials-nested' }
{ overwrite: true }
);

expect(result).toEqual(
expect.objectContaining({
patternContextId: 'lib/box.d.ts:Box',
children: expect.arrayContaining([
expect.objectContaining({
patternContextId: 'lib/text.d.ts:Text'
})
])
})
);
const namedExports = file
.getStatements()
.filter(statement => tsa.TypeGuards.isExportableNode(statement) && statement.isNamedExport());

expect(namedExports.map(namedExport => getExportSpecifier(namedExport))).toEqual([
'A',
'B',
'C',
'G'
]);
});

test('determines export from aliased import', () => {
const project = new tsa.Project({ useVirtualFileSystem: true, addFilesFromTsConfig: false });

const file = project.createSourceFile('/aa.ts', `import { B as A } from './ab'; export { A };`);

project.createSourceFile('/ab.ts', `export const B = () => {}`);

const exportDeclaration = file.getStatements().filter(tsa.TypeGuards.isExportDeclaration)[0];

const namedExport = exportDeclaration.getNamedExports()[0];
expect(getExportSpecifier(namedExport)).toEqual('B');
});

test('determines last export from named import/export chain', () => {
const project = new tsa.Project({ useVirtualFileSystem: true, addFilesFromTsConfig: false });

const file = project.createSourceFile('/ba.ts', `import { A } from './bb'; export { A };`);

project.createSourceFile('/bb.ts', `export { C as A } from './bc';`);

project.createSourceFile('/bc.ts', `export const C = () => {}`);

const exportDeclaration = file.getStatements().filter(tsa.TypeGuards.isExportDeclaration)[0];

const namedExport = exportDeclaration.getNamedExports()[0];
expect(getExportSpecifier(namedExport)).toEqual('C');
});

0 comments on commit 47c7384

Please sign in to comment.