diff --git a/lib/Declaration.ts b/lib/Declaration.ts index 03dff11..3b6954f 100644 --- a/lib/Declaration.ts +++ b/lib/Declaration.ts @@ -6,22 +6,15 @@ import ResolutionError from '@error/Resolution'; import { DeclarationInterface as Interface, File } from '@lib/convert'; import Path from '@lib/Path'; -export interface IOptionsDeclaration { +export interface IDerivedOptions { declaration: T; -} - -export interface IOptionsFile { file: File; } -export interface IOptionsPath { +export interface IOptions extends IDerivedOptions { path: string; } -export type IDerivedOptions = IOptionsDeclaration & IOptionsFile; - -export type IOptions = IDerivedOptions & IOptionsPath; - function isBuiltinModule(module: string): boolean { // TODO: change to use 'is-builtin-module', need to submit @types/is-builtin-module const builtin = [ diff --git a/lib/File.ts b/lib/File.ts index 2dd0d9b..d8cb132 100644 --- a/lib/File.ts +++ b/lib/File.ts @@ -5,26 +5,16 @@ import FileNotFoundError from '@error/FileNotFound'; import { Declaration } from '@lib/convert'; import Path from '@lib/Path'; -export interface IOptionsPath { +export interface IDerivedOptions { path: string | Path; -} - -export interface IOptionsOptions { options: ICompilerOptions; -} - -export interface IOptionsConfig { config: ICompilerConfig; } -export interface IOptionsExtension { +export interface IOptions extends IDerivedOptions { extension: string; } -export type IDerivedOptions = IOptionsPath & IOptionsOptions & IOptionsConfig; - -export type IOptions = IDerivedOptions & IOptionsExtension; - function commonPathPrefix(paths: IterableIterator): string { const { done, value: left } = paths.next(); if (done) { diff --git a/lib/Mapper.ts b/lib/Mapper.ts index faf2a47..d7cd313 100644 --- a/lib/Mapper.ts +++ b/lib/Mapper.ts @@ -2,13 +2,10 @@ import { existsSync as fileExistsSync, readFileSync as fileReadSync } from 'fs'; import { dirname, resolve } from 'path'; import * as ts from 'typescript'; -import UnsupportedError from '@error/Unsupported'; -import EsExport from '@es/Export'; +import { Declaration, File } from '@lib/convert'; + import EsFile from '@es/File'; -import EsImport from '@es/Import'; -import TsExport from '@ts/Export'; import TsFile from '@ts/File'; -import TsImport from '@ts/Import'; export interface IOptions { tsconfig: string; @@ -49,14 +46,11 @@ export default class Mapper { })); } - if (parsed.options.module !== ts.ModuleKind.ES2015) { - throw new UnsupportedError('Only ECMA2015 module outputs are supported at the moment'); - } parsed.options.rootDir = parsed.options.rootDir || projectRoot; this.parsed = parsed; } - async *files(): AsyncIterableIterator { + async *files(): AsyncIterableIterator { const { options } = this.parsed; yield* this.parsed.fileNames.map(path => new EsFile({ path, options, config: this.parsed })); if (options.declaration) { @@ -64,7 +58,7 @@ export default class Mapper { } } - async *map(): AsyncIterableIterator { + async *map(): AsyncIterableIterator { for await (const file of this.files()) { yield* file.map(this.parsed.options); } diff --git a/lib/convert.ts b/lib/convert.ts index 95aced3..94b4997 100644 --- a/lib/convert.ts +++ b/lib/convert.ts @@ -1,13 +1,29 @@ import EsImport, { Interface as EsImportInterface } from '@es/Export'; import EsFile from '@es/File'; import EsExport, { Interface as EsExportInterface } from '@es/Import'; +import EsRequire, { Interface as EsRequireInterface } from '@es/Require'; import Mapper, { IOptions as IMapperOptions } from '@lib/Mapper'; import TsImport, { Interface as TsImportInterface } from '@ts/Export'; import TsFile from '@ts/File'; import TsExport, { Interface as TsExportInterface } from '@ts/Import'; -export type DeclarationInterface = EsImportInterface | EsExportInterface | TsImportInterface | TsExportInterface; -export type Declaration = EsImport | EsExport | TsImport | TsExport; -export type File = EsFile | TsFile; + +export type DeclarationInterface = + EsRequireInterface | + EsImportInterface | + EsExportInterface | + TsImportInterface | + TsExportInterface; + +export type Declaration = + EsImport | + EsExport | + TsImport | + TsExport | + EsRequire; + +export type File = + EsFile | + TsFile; export type IOptions = IMapperOptions; diff --git a/lib/es/Declaration.ts b/lib/es/Declaration.ts index 611bef5..30c5b3a 100644 --- a/lib/es/Declaration.ts +++ b/lib/es/Declaration.ts @@ -1,27 +1,46 @@ import { ExportAllDeclaration, ExportNamedDeclaration, ImportDeclaration, SimpleLiteral } from 'estree'; +import isExportNamedDeclaration from '@es/isExportNamedDeclaration'; +import isImportDeclaration from '@es/isImportDeclaration'; +import isRequireCallExpression from '@es/isRequireCallExpression'; +import isSimpleLiteral from '@es/isSimpleLiteral'; +import RequireCallExpression from '@es/RequireCallExpression'; +import { File } from '@lib/convert'; + import Base, { IDerivedOptions as IBaseOptions } from '@lib/Declaration'; -export type Interface = (ExportAllDeclaration | ExportNamedDeclaration) | ImportDeclaration; +export type Interface = (ExportAllDeclaration | ExportNamedDeclaration) | ImportDeclaration | RequireCallExpression; export type IOptions = IBaseOptions; +function getLiteral(declaration: Interface, file: File): SimpleLiteral { + const literal = + isExportNamedDeclaration(declaration) ? declaration.source : + isImportDeclaration(declaration) ? declaration.source : + isRequireCallExpression(declaration) ? declaration.arguments[0] : + undefined; + if (literal === undefined) { + throw new TypeError(`Failed to find ES declaration in '${file.source}'`); + } else if (!isSimpleLiteral(literal)) { + const type = literal ? literal.type : 'unknown'; + throw new TypeError(`Invalid ES declaration source type '${type}' in '${file.source}'`); + } + return literal; +} + export default class Declaration extends Base { constructor({ declaration, file, ...rest}: IOptions) { // RAII checks - const { type, value } = (declaration.source as SimpleLiteral); - if (type !== 'Literal') { - throw new TypeError(`Invalid ES declaration source type '${type}' in '${file.source}'`); - } - if (typeof value !== 'string') { - throw new TypeError(`The type '${typeof value}' of the ES source value was not a 'string' for '${file.source}'`); + const literal = getLiteral(declaration, file); + if (typeof literal.value !== 'string') { + throw new TypeError(`The type '${typeof literal.value}' of the ES source must be 'string' for '${file.source}'`); } - super({declaration, file, ...rest, path: value}); + super({declaration, file, ...rest, path: literal.value}); } private get literal(): SimpleLiteral { - return (this.declaration.source as SimpleLiteral); + return getLiteral(this.declaration, this.file); } get path(): string { diff --git a/lib/es/File.ts b/lib/es/File.ts index 47899ce..eb22673 100644 --- a/lib/es/File.ts +++ b/lib/es/File.ts @@ -1,11 +1,17 @@ -import { ExportNamedDeclaration, ImportDeclaration, Program } from 'estree'; +import { Program } from 'estree'; import { PathLike, readFile as readFileSync, writeFile as writeFileSync } from 'fs'; +import { ModuleKind } from 'typescript'; import { promisify } from 'util'; import ParseError from '@error/Parse'; import Export from '@es/Export'; import Import from '@es/Import'; +import isExportNamedDeclaration from '@es/isExportNamedDeclaration'; +import isImportDeclaration from '@es/isImportDeclaration'; +import isRequireCallExpression from '@es/isRequireCallExpression'; +import isVariableDeclaration from '@es/isVariableDeclaration'; import { attachComments, Comment, generate, parse, plugins, Token } from '@es/parser'; +import Require from '@es/Require'; import Base, { IDerivedOptions as IBaseOptions } from '@lib/File'; const readFile = promisify(readFileSync); @@ -13,7 +19,7 @@ const writeFile = promisify(writeFileSync); export type IOptions = IBaseOptions; -export default class File extends Base { +export default class File extends Base { private program: Program | undefined; constructor({ ...options }: IOptions) { @@ -35,7 +41,7 @@ export default class File extends Base { ranges: true, onComment: comments, onToken: tokens, - sourceType: 'module', + sourceType: this.options.module === ModuleKind.ES2015 ? 'module' : 'script', ecmaVersion: 8, plugins }); @@ -52,25 +58,25 @@ export default class File extends Base { } } - async *imports(): AsyncIterableIterator { + async *imports(): AsyncIterableIterator { const { body } = await this.ast; yield* body - .filter(({type}) => type === 'ImportDeclaration') - .map(n => new Import({file: this, declaration: n as ImportDeclaration})); + .filter(isImportDeclaration) + .map(declaration => new Import({file: this, declaration})); + yield* body + .filter(isVariableDeclaration) + .map(({declarations}) => declarations) + .reduce((a, n) => a.concat(n), []) + .map(({init}) => init) + .filter(isRequireCallExpression) + .map(declaration => new Require({file: this, declaration})); } async *exports(): AsyncIterableIterator { const { body } = await this.ast; yield* body - .filter(n => - n.type === 'ExportAllDeclaration' || - (n.type === 'ExportNamedDeclaration' && (n).source !== null)) - .map(n => new Export({ - file: this, - declaration: n.type === 'ExportAllDeclaration' ? - n : - n as ExportNamedDeclaration - })); + .filter(isExportNamedDeclaration) + .map(declaration => new Export({ file: this, declaration })); } async write(path?: PathLike | number, options?: { diff --git a/lib/es/Require.ts b/lib/es/Require.ts new file mode 100644 index 0000000..e60f4a5 --- /dev/null +++ b/lib/es/Require.ts @@ -0,0 +1,9 @@ +import RequireCallExpression from '@es/RequireCallExpression'; + +import Declaration, { IOptions as IDeclarationOptions } from '@es/Declaration'; + +export type Interface = RequireCallExpression; + +export type IOptions = IDeclarationOptions; + +export default class Require extends Declaration {} diff --git a/lib/es/RequireCallExpression.ts b/lib/es/RequireCallExpression.ts new file mode 100644 index 0000000..67c5106 --- /dev/null +++ b/lib/es/RequireCallExpression.ts @@ -0,0 +1,7 @@ +import { Identifier, SimpleCallExpression, SimpleLiteral } from 'estree'; + +// tslint:disable-next-line:interface-name +export default interface RequireCallExpression extends SimpleCallExpression { + callee: Identifier; + arguments: [ SimpleLiteral ]; +} diff --git a/lib/es/isExportNamedDeclaration.ts b/lib/es/isExportNamedDeclaration.ts new file mode 100644 index 0000000..179f2c4 --- /dev/null +++ b/lib/es/isExportNamedDeclaration.ts @@ -0,0 +1,8 @@ +import { ExportNamedDeclaration, Node } from 'estree'; + +export default function isExportNamedDeclaration(data: Node | null | undefined): data is ExportNamedDeclaration { + return data !== null && + data !== undefined && + (data.type === 'ExportAllDeclaration' + || (data.type === 'ExportNamedDeclaration' && data.source !== null)); +} diff --git a/lib/es/isIdentifier.ts b/lib/es/isIdentifier.ts new file mode 100644 index 0000000..41f7205 --- /dev/null +++ b/lib/es/isIdentifier.ts @@ -0,0 +1,5 @@ +import { Identifier, Node } from 'estree'; + +export default function isIdentifier(data: Node | null | undefined): data is Identifier { + return data !== null && data !== undefined && data.type === 'Identifier'; +} diff --git a/lib/es/isImportDeclaration.ts b/lib/es/isImportDeclaration.ts new file mode 100644 index 0000000..9b5f7bd --- /dev/null +++ b/lib/es/isImportDeclaration.ts @@ -0,0 +1,5 @@ +import { ImportDeclaration, Node } from 'estree'; + +export default function isImportDeclaration(data: Node | null | undefined): data is ImportDeclaration { + return data !== null && data !== undefined && data.type === 'ImportDeclaration'; +} diff --git a/lib/es/isRequireCallExpression.ts b/lib/es/isRequireCallExpression.ts new file mode 100644 index 0000000..af12e87 --- /dev/null +++ b/lib/es/isRequireCallExpression.ts @@ -0,0 +1,15 @@ +import isIdentifier from '@es/isIdentifier'; +import isSimpleCallExpression from '@es/isSimpleCallExpression'; +import isSimpleLiteral from '@es/isSimpleLiteral'; +import RequireCallExpression from '@es/RequireCallExpression'; +import { Node } from 'estree'; + +export default function isRequireCallExpression(data: Node | undefined | null): data is RequireCallExpression { + return data !== null && + data !== undefined && + isSimpleCallExpression(data) && + isIdentifier(data.callee) && + data.callee.name === 'require' && + data.arguments.length === 1 && + isSimpleLiteral(data.arguments[0]); +} diff --git a/lib/es/isSimpleCallExpression.ts b/lib/es/isSimpleCallExpression.ts new file mode 100644 index 0000000..e5bc279 --- /dev/null +++ b/lib/es/isSimpleCallExpression.ts @@ -0,0 +1,5 @@ +import { Node, SimpleCallExpression } from 'estree'; + +export default function isSimpleCallExpression(data: Node | undefined | null): data is SimpleCallExpression { + return data !== null && data !== undefined && data.type === 'CallExpression'; +} diff --git a/lib/es/isSimpleLiteral.ts b/lib/es/isSimpleLiteral.ts new file mode 100644 index 0000000..a8456a2 --- /dev/null +++ b/lib/es/isSimpleLiteral.ts @@ -0,0 +1,5 @@ +import { Node, SimpleLiteral } from 'estree'; + +export default function isIdentifier(data: Node | null | undefined): data is SimpleLiteral { + return data !== null && data !== undefined && data.type === 'Literal'; +} diff --git a/lib/es/isVariableDeclaration.ts b/lib/es/isVariableDeclaration.ts new file mode 100644 index 0000000..27ebc4c --- /dev/null +++ b/lib/es/isVariableDeclaration.ts @@ -0,0 +1,5 @@ +import { Node, VariableDeclaration } from 'estree'; + +export default function isVariableDeclaration(data: Node | null | undefined): data is VariableDeclaration { + return data !== null && data !== undefined && data.type === 'VariableDeclaration'; +}