Skip to content
Permalink
Browse files

fix(parse): Parse types definitions for commonjs

  • Loading branch information...
iamthes committed Nov 27, 2017
1 parent d63c8b1 commit a298f31160a8d5164e060fbaafccdc26dc51fe27
Showing with 131 additions and 62 deletions.
  1. +1 −1 package.json
  2. +8 −0 src/module.spec.ts
  3. +8 −4 src/module.ts
  4. +51 −34 src/parse.spec.ts
  5. +63 −23 src/parse.ts
@@ -5,7 +5,7 @@
"main": "lib",
"typings": "src/index.ts",
"scripts": {
"fixtures": "npm i --no-save angular2-calendar rxjs gulp-tslint @angular/core",
"fixtures": "npm i --no-save angular2-calendar rxjs gulp-tslint @angular/core @types/express",
"pretest": "npm run fixtures",
"test": "npm run eslint && npm run tscheck && npm run t",
"t": "npm run mocha -- src/*.spec.ts",
@@ -96,3 +96,11 @@ it('commonjs modules pkg-dir', async () => {
assert(entry.module === 'pkg-dir');
assert(entry.cjs === true);
});

it('types express', async () => {
const result = await parse('@types/express', { basedir: rootPath });
assert(result.length > 0);
const request = result.find(n => n.name === 'Request');
assert(request);
assert(request.module === 'express');
});
@@ -23,9 +23,13 @@ type ModuleOptions = {
basedir?: string;
};

export function module(name: string, options: ModuleOptions = {}): Promise<Entry[]> {
export function module(id: string, options: ModuleOptions = {}): Promise<Entry[]> {
let name = id;
if (name.indexOf('@types/') === 0) {
name = name.slice(7);
}
return new Promise<{ entries: Entry[], resolved?: string }>((done, reject) => {
resolve(name, { ...resolveOptions, ...options }, (err, resolved) => {
resolve(id, { ...resolveOptions, ...options }, (err, resolved) => {
if (err) {
return reject(err);
}
@@ -75,7 +79,7 @@ export function module(name: string, options: ModuleOptions = {}): Promise<Entry
return entries;
});
}).then(entries => {
const dirpath = resolvePath(options.basedir || '.', 'node_modules', name);
const dirpath = resolvePath(options.basedir || '.', 'node_modules', id);
const submodules: string[] = [];
return new Promise<string[]>((done, reject) => {
readdir(dirpath, (err, items) => {
@@ -89,7 +93,7 @@ export function module(name: string, options: ModuleOptions = {}): Promise<Entry
items.forEach(item => {
stat(resolvePath(dirpath, item, 'package.json'), (err, stats) => {
if (stats && stats.isFile()) {
submodules.push(`${name}/${item}`);
submodules.push(`${id}/${item}`);
}
if (--count === 0) {
done(submodules);
@@ -5,98 +5,98 @@ it('smoke test', () => {
assert(parse);
});

it('var export', async () => {
it('var export', () => {
let code = `export var aaa = 1`;
let [result] = await parse(code);
let [result] = parse(code);
assert.equal(result.name, 'aaa');
});

it('several var export', async () => {
it('several var export', () => {
let code = `export var aaa, bbb`;
let [result, result2] = await parse(code);
let [result, result2] = parse(code);
assert.equal(result.name, 'aaa');
assert.equal(result2.name, 'bbb');
});

it('export all', async () => {
it('export all', () => {
let code = `export * from './provider'`;
let [result] = await parse(code);
let [result] = parse(code);
assert.equal(result.specifier, './provider');
});

it('export some from module', async () => {
it('export some from module', () => {
let code = `export {var1} from './provider'`;
let [result] = await parse(code);
let [result] = parse(code);
assert.equal(result.name, 'var1');
assert.equal(result.specifier, './provider');
});

it('pick export', async () => {
it('pick export', () => {
let code = `export { CalendarEvent, EventAction } from 'calendar-utils'`;
var [first, second] = await parse(code);
var [first, second] = parse(code);
assert.equal(first.name, 'CalendarEvent');
assert.equal(first.specifier, 'calendar-utils');
assert.equal(second.name, 'EventAction');
});

it('export declare class', async () => {
it('export declare class', () => {
let code = `export declare class Calendar`;
let [result] = await parse(code);
let [result] = parse(code);
assert.equal(result.name, 'Calendar');
});

it('export class', async () => {
it('export class', () => {
let code = `export class Aaa`;
let [result] = await parse(code);
let [result] = parse(code);
assert.equal(result.name, 'Aaa');
});

it('export interface', async () => {
it('export interface', () => {
let code = `export interface Entry {}`;
let [result] = await parse(code);
let [result] = parse(code);
assert.equal(result.name, 'Entry');
});

it('export function', async () => {
it('export function', () => {
let code = `export function dummy() {}`;
let [result] = await parse(code);
let [result] = parse(code);
assert.equal(result.name, 'dummy');
});

it('export several vars', async () => {
it('export several vars', () => {
let code = `export {somevar, otherVar}`;
let [result, other] = await parse(code);
let [result, other] = parse(code);
assert.equal(result.name, 'somevar');
assert.equal(other.name, 'otherVar');
});

it('export default', async () => {
it('export default', () => {
let code = `export default function foo() {}`;
var [entry] = await parse(code);
var [entry] = parse(code);
assert.equal(entry.name, 'foo');
assert.equal(entry.isDefault, true);
});

it('empty source', async () => {
it('empty source', () => {
let code = ``;
var result = await parse(code);
var result = parse(code);
assert.deepEqual(result, []);
});

it('object binding', async () => {
it('object binding', () => {
let code = `export const {ModuleStore} = $traceurRuntime;`;
let [result1] = await parse(code);
let [result1] = parse(code);
assert.equal(result1.name, 'ModuleStore');
});

it('should extract declared module http', async () => {
it('should extract declared module http', () => {
let source = `declare module "http" {
export var METHODS: string[];
export const c1, c2: any;
}
export var out1: number = 1;
`;
let result = await parse(source);
let result = parse(source);
let out1 = result.find(e => e.name === 'out1');
assert(out1);
let httpEntries = result.filter(e => e.module === 'http');
@@ -106,7 +106,7 @@ it('should extract declared module http', async () => {
assert.equal(httpEntries[2].name, 'c2');
});

it('should extract declared module events', async () => {
it('should extract declared module events', () => {
let source = `declare module "events" {
class internal extends NodeJS.EventEmitter { }
namespace internal {
@@ -115,14 +115,14 @@ it('should extract declared module events', async () => {
}
export = internal;
}`;
let entries = await parse(source);
let entries = parse(source);
let event = entries.find(e => e.name === 'EventEmitter');
assert(event);
assert.equal(event.name, 'EventEmitter');
assert.equal(event.module, 'events');
});

it('not too deep parse', async () => {
it('not too deep parse', () => {
const source = `
export function deep() {
const x = {a: {c: {d: 1}}};
@@ -131,17 +131,34 @@ it('not too deep parse', async () => {
});
};
}`;
const result = await parse(source);
const result = parse(source);
assert.equal(result.length, 1);
assert.equal(result[0].name, 'deep');
});

it('duplicates must be removed', async () => {
it('duplicates must be removed', () => {
const source = `
export function spawnSync(command: string): SpawnSyncReturns<Buffer>;
export function spawnSync(command: string, options?: SpawnSyncOptionsWithStringEncoding): SpawnSyncReturns<string>;
`;
const result = await parse(source);
const result = parse(source);
assert.equal(result.length, 1);
assert.equal(result[0].name, 'spawnSync');
});

it('simulated commonjs', () => {
const source = `
declare namespace e {
interface Request extends core.Request { }
}
declare namespace d {
declare const x = 1;
function y() {}
}
export = e;
`;
const result = parse(source);
assert.equal(result.length, 1);
assert.equal(result[0].name, 'Request');
assert.equal(result[0].cjs, true);
});
@@ -1,6 +1,7 @@
import * as ts from 'typescript';
import { Entry } from './entry';
import { get } from './get';
import resolve = require('resolve');

export type ParseOptions = {
module?: string;
@@ -26,26 +27,74 @@ class EntrySet {
}
}

function getDeclarations(node: ts.Node, options: any) {
const result: Entry[] = [];
const declarations = get('declarationList.declarations', node) || [];
declarations.forEach(d => {
const name: string = d && d.name && d.name.text;
if (name) {
result.push(new Entry({ ...options, name }));
}
const names = get('name.elements', d) || [];
names.forEach(d => {
const name: string = d && d.name && d.name.text;
result.push(new Entry({ ...options, name }));
});
});
const name: string = (node as any).name && (node as any).name.text;
if (name) {
const isDefault = hasDefaultKeyword(node);
result.push(new Entry({ ...options, name, isDefault }));
}
return result;
}

export function parse(sourceText: string, options: ParseOptions = {}): Entry[] {
const sourceFile = ts.createSourceFile('dummy.ts', sourceText, ts.ScriptTarget.ES2015, true);
let { module, filepath } = options;
let moduleEnd: number;
let moduleEnd: number | undefined;
let moduleName: string | undefined;
let moduleBlockDeclarations: { [k: string]: Entry[] } = {};
const entrySet = new EntrySet();
walk(sourceFile);
function walk(statement: ts.Node) {
const node = statement;
if (node.pos >= moduleEnd) {
module = undefined;
module = options.module;
moduleName = undefined;
moduleEnd = undefined;
}
switch (node.kind) { // eslint-disable-line tslint/config
case ts.SyntaxKind.ModuleDeclaration: {
const isDeclare = Boolean(node.modifiers && node.modifiers.find(m => m.kind === ts.SyntaxKind.DeclareKeyword));
if (!isDeclare) {
break;
}
module = get('name.text', node) as string;
moduleName = (node as any).name && (node as any).name.text;
if (moduleName) {
if (resolve.isCore(moduleName)) {
module = moduleName;
} else {
moduleBlockDeclarations[moduleName] = [];
}
}
moduleEnd = node.end;
} break;
// case ts.SyntaxKind.VariableStatement:
case ts.SyntaxKind.VariableDeclarationList:
case ts.SyntaxKind.FunctionDeclaration:
case ts.SyntaxKind.ClassDeclaration:
case ts.SyntaxKind.InterfaceDeclaration:
case ts.SyntaxKind.TypeAliasDeclaration:
case ts.SyntaxKind.EnumDeclaration:
case ts.SyntaxKind.VariableDeclaration: {
if (node.parent!.kind === ts.SyntaxKind.ModuleBlock) {
if (moduleName && moduleBlockDeclarations[moduleName]) {
const entries = getDeclarations(node, { module, filepath });
moduleBlockDeclarations[moduleName].push(...entries);
}
}
} break;
case ts.SyntaxKind.ExportDeclaration: {
const node = statement as ts.ExportDeclaration;
const names: Array<(string | undefined)> = [];
@@ -63,30 +112,21 @@ export function parse(sourceText: string, options: ParseOptions = {}): Entry[] {
});
} break;
case ts.SyntaxKind.ExportKeyword: {
const declarations = get('declarationList.declarations', node.parent) || [];
declarations.forEach(d => {
const name: string = d && d.name && d.name.text;
if (name) {
const entry = new Entry({ name, module, filepath });
entrySet.push(entry);
}
const names = get('name.elements', d) || [];
names.forEach(d => {
const name: string = d && d.name && d.name.text;
const entry = new Entry({ name, module, filepath });
const entries = getDeclarations(node.parent!, { module, filepath });
entries.forEach(entry => entrySet.push(entry));
} break;
case ts.SyntaxKind.ExportAssignment: {
const expr = (node as any).expression && (node as any).expression.text;
if (expr && moduleBlockDeclarations[expr]) {
moduleBlockDeclarations[expr].forEach(entry => {
entry.cjs = true;
entry.ts = true;
entrySet.push(entry);
});
});
const name: string = get('name.text', node.parent);
if (name) {
const isDefault = hasDefaultKeyword(node.parent);
const entry = new Entry({ name, module, filepath, isDefault });
entrySet.push(entry);
} else {
entrySet.result.push(new Entry({ module, cjs: true, ts: true }));
}
} break;
case ts.SyntaxKind.ExportAssignment: {
entrySet.result.push(new Entry({ module, cjs: true, ts: true }));
} break;
}
if (node.kind === ts.SyntaxKind.SourceFile
|| node.kind === ts.SyntaxKind.ModuleDeclaration

0 comments on commit a298f31

Please sign in to comment.
You can’t perform that action at this time.