diff --git a/.gitignore b/.gitignore index e1b75d02..2d6aff6f 100644 --- a/.gitignore +++ b/.gitignore @@ -6,3 +6,4 @@ coverage/ .nyc_output/ npm-debug.log +dist/ diff --git a/.vscode/tasks.json b/.vscode/tasks.json new file mode 100644 index 00000000..711fa0f3 --- /dev/null +++ b/.vscode/tasks.json @@ -0,0 +1,23 @@ +{ + // See https://go.microsoft.com/fwlink/?LinkId=733558 + // for the documentation about the tasks.json format + "version": "0.1.0", + "command": "npm", + "isShellCommand": true, + "showOutput": "silent", + "suppressTaskName": true, + "tasks": [ + { + "taskName": "build", + "args": [ + "run", + "build", + "--", + "--watch" + ], + "echoCommand": true, + "isBuildCommand": true, + "isWatching": true + } + ] +} \ No newline at end of file diff --git a/cli.js b/cli.js index e4394fa3..bdae0738 100644 --- a/cli.js +++ b/cli.js @@ -1,5 +1,5 @@ #!/usr/bin/env node -var react2dts = require('./index'); +var react2dts = require('./dist/src/index'); var minimist = require('minimist'); var options = minimist(process.argv.slice(2), { diff --git a/package.json b/package.json index 066040aa..27832339 100644 --- a/package.json +++ b/package.json @@ -2,15 +2,14 @@ "name": "react-to-typescript-definitions", "version": "0.13.0", "description": "Create typescript definitions files (d.ts) from react components", - "main": "index.js", + "main": "dist/src/index.js", "bin": { "react2dts": "cli.js" }, "files": [ - "index.js", + "dist", "cli.js", - "index.d.ts", - "index.js.map" + "index.d.ts" ], "scripts": { "start": "npm test", @@ -19,7 +18,7 @@ "build": "tsc --sourceMap", "build:inline": "tsc --inlineSourceMap", "pretest": "npm run clean && npm run build:inline", - "test": "nyc --all --reporter lcov ./node_modules/.bin/mocha --require source-map-support --recursive tests/*-test.js", + "test": "nyc --all --reporter lcov ./node_modules/.bin/mocha --require source-map-support --recursive dist/tests/*-test.js", "coverage": "cat ./coverage/lcov.info | ./node_modules/.bin/coveralls", "changelog": "conventional-changelog -p angular -i CHANGELOG.md -w", "prerelease": "npm test && npm run build", @@ -71,6 +70,7 @@ "exclude": [ "node_modules", "coverage", + "dist/tests", "tests" ] } diff --git a/index.ts b/src/index.ts similarity index 93% rename from index.ts rename to src/index.ts index 74410f54..63fe4e64 100644 --- a/index.ts +++ b/src/index.ts @@ -3,22 +3,23 @@ import * as babylon from 'babylon'; import * as dom from 'dts-dom'; export interface InstanceOfResolver { - (name: string): string; + (name: string): string|undefined; }; export interface IOptions { + /** * Resolves type names to import paths. * * @return Path to given name if resolveable, undefined otherwise */ instanceOfResolver?: InstanceOfResolver; + /** * The Generator generating .d.ts code with. * * This option is deprecated with 0.13 and is not supported anymore. * any new feature will not work with the deprecated Generator interface. - * @deprecated */ generator?: Generator; @@ -71,11 +72,11 @@ export function cli(options: any): void { }); } -export function generateFromFile(moduleName: string, path: string, options?: IOptions): string { +export function generateFromFile(moduleName: string|null, path: string, options?: IOptions): string { return generateFromSource(moduleName, fs.readFileSync(path).toString(), options); } -export function generateFromSource(moduleName: string, code: string, options: IOptions = {}): string { +export function generateFromSource(moduleName: string|null, code: string, options: IOptions = {}): string { const ast = babylon.parse(code, { sourceType: 'module', allowReturnOutsideFunction: true, @@ -101,9 +102,9 @@ export function generateFromSource(moduleName: string, code: string, options: IO return generateFromAst(moduleName, ast, options); } -const defaultInstanceOfResolver: InstanceOfResolver = (name: string): string => undefined; +const defaultInstanceOfResolver: InstanceOfResolver = (_name: string): undefined => undefined; -export function generateFromAst(moduleName: string, ast: any, options: IOptions = {}): string { +export function generateFromAst(moduleName: string|null, ast: any, options: IOptions = {}): string { const parsingResult = parseAst(ast, options.instanceOfResolver); if (options.generator) { return deprecatedGenerator(options.generator, moduleName, parsingResult); @@ -117,7 +118,7 @@ export function generateFromAst(moduleName: string, ast: any, options: IOptions if (propTypes) { Object.keys(propTypes).forEach(propName => { const prop = propTypes[propName]; - if (prop.importPath) { + if (prop.importType && prop.importPath) { code += dom.emit(dom.create.importDefault(prop.importType, prop.importPath)); } }); @@ -135,7 +136,7 @@ export function generateFromAst(moduleName: string, ast: any, options: IOptions if (propTypes) { Object.keys(propTypes).forEach(propName => { const prop = propTypes[propName]; - if (prop.importPath) { + if (prop.importType && prop.importPath) { m.members.push(dom.create.importDefault(prop.importType, prop.importPath)); } }); @@ -190,14 +191,14 @@ function createReactClassDeclaration(classname: string, exportType: ExportType, return classDecl; } -function deprecatedGenerator(generator: Generator, moduleName: string, +function deprecatedGenerator(generator: Generator, moduleName: string|null, {exportType, classname, propTypes}: IParsingResult): string { const generateTypings = () => { generator.import('* as React', 'react'); if (propTypes) { Object.keys(propTypes).forEach(propName => { const prop = propTypes[propName]; - if (prop.importPath) { + if (prop.importType && prop.importPath) { generator.import(prop.importType, prop.importPath); } }); @@ -229,15 +230,15 @@ interface IParsingResult { propTypes: IPropTypes; } -function parseAst(ast: any, instanceOfResolver: InstanceOfResolver): IParsingResult { - let exportType: ExportType; - let classname: string; +function parseAst(ast: any, instanceOfResolver?: InstanceOfResolver): IParsingResult { + let exportType: ExportType|undefined; + let classname: string|undefined; let propTypes: IPropTypes = {}; walk(ast.program, { - 'ExportNamedDeclaration': exportNode => { + 'ExportNamedDeclaration': () => { exportType = ExportType.named; }, - 'ExportDefaultDeclaration': exportNode => { + 'ExportDefaultDeclaration': () => { exportType = ExportType.default; }, 'ClassDeclaration': classNode => { @@ -275,6 +276,12 @@ function parseAst(ast: any, instanceOfResolver: InstanceOfResolver): IParsingRes } }); } + if (exportType === undefined) { + throw new Error('No exported class found'); + } + if (!classname) { + throw new Error('Anonymous classes are not supported'); + } return { exportType, classname, @@ -282,7 +289,7 @@ function parseAst(ast: any, instanceOfResolver: InstanceOfResolver): IParsingRes }; } -function parsePropTypes(node: any, instanceOfResolver: InstanceOfResolver): IPropTypes { +function parsePropTypes(node: any, instanceOfResolver?: InstanceOfResolver): IPropTypes { let propTypes: IPropTypes = {}; walk(node, { 'ObjectProperty': propertyNode => { @@ -369,7 +376,7 @@ function isRequiredPropType(node: any, instanceOfResolver: InstanceOfResolver): } /** - * This is for internal use only + * @internal */ export function getTypeFromPropType(node: IASTNode, instanceOfResolver = defaultInstanceOfResolver): IProp { const result: IProp = { diff --git a/tests/generator-test.d.ts b/tests/generator-test.d.ts deleted file mode 100644 index e69de29b..00000000 diff --git a/tests/generator-test.ts b/tests/generator-test.ts index d0ad69e7..1e8d0877 100644 --- a/tests/generator-test.ts +++ b/tests/generator-test.ts @@ -1,5 +1,5 @@ import { assert } from 'chai'; -import { Generator, generateFromSource } from '../index'; +import { Generator, generateFromSource } from '../src/index'; describe('The Generator', () => { let generator: Generator; @@ -58,25 +58,31 @@ describe('Generating typings with given custom generator', () => { }); it('should delare a module if name given', () => { - let name: string = undefined; + let name: string|undefined; generator.declareModule = moduleName => { name = moduleName; }; - generateFromSource('module', '', {generator}); + const source = ` + export class Test {} + `; + generateFromSource('module', source, {generator}); assert.equal(name, 'module'); }); it('should import react', () => { - let decl: string = undefined; - let from: string = undefined; + let decl: string|undefined; + let from: string|undefined; generator.import = (_decl, _from) => { decl = _decl; from = _from; }; - generateFromSource(null, '', {generator}); + const source = ` + export class Test {} + `; + generateFromSource(null, source, {generator}); assert.equal(decl, '* as React'); assert.equal(from, 'react'); diff --git a/tests/parse-prop-types-test.d.ts b/tests/parse-prop-types-test.d.ts deleted file mode 100644 index e69de29b..00000000 diff --git a/tests/parse-prop-types-test.ts b/tests/parse-prop-types-test.ts index 0120dec3..6f634b24 100644 --- a/tests/parse-prop-types-test.ts +++ b/tests/parse-prop-types-test.ts @@ -1,6 +1,6 @@ import { assert } from 'chai'; import * as dom from 'dts-dom'; -import { getTypeFromPropType, IProp } from '../index'; +import { getTypeFromPropType, IProp } from '../src/index'; describe('The PropType parser', () => { const instanceOfResolver = (): any => undefined; @@ -265,7 +265,7 @@ describe('The PropType parser', () => { } ] }; - const result: IProp = getTypeFromPropType(ast, (name: string): string => './some/path'); + const result: IProp = getTypeFromPropType(ast, (): string => './some/path'); assert.equal(result.type, 'typeof Message'); assert.deepEqual(result.type2, dom.create.typeof(dom.create.namedTypeReference('Message'))); assert.equal(result.optional, true); diff --git a/tests/parsing-test.d.ts b/tests/parsing-test.d.ts deleted file mode 100644 index e69de29b..00000000 diff --git a/tests/parsing-test.ts b/tests/parsing-test.ts index 8845ec32..819a5a2d 100644 --- a/tests/parsing-test.ts +++ b/tests/parsing-test.ts @@ -4,7 +4,9 @@ import * as path from 'path'; import * as chalk from 'chalk'; import * as diff from 'diff'; -import * as react2dts from '../index'; +import * as react2dts from '../src/index'; + +const basedir = path.join(__dirname, '..', '..', 'tests'); function textDiff(actual: string, expected: string): void { const differences = diff.diffChars(expected, actual); @@ -22,38 +24,38 @@ function textDiff(actual: string, expected: string): void { describe('Parsing', () => { it('should create definition from es6 class component', () => { const opts: react2dts.IOptions = { - instanceOfResolver: (name: string): string => './path/to/Message' + instanceOfResolver: (): string => './path/to/Message' }; textDiff( - react2dts.generateFromFile('component', path.join(__dirname, 'es6-class.jsx'), opts), - fs.readFileSync(path.join(__dirname, 'es6-class.d.ts')).toString() + react2dts.generateFromFile('component', path.join(basedir, 'es6-class.jsx'), opts), + fs.readFileSync(path.join(basedir, 'es6-class.d.ts')).toString() ); }); it('should create definition from es7 class component', () => { const opts: react2dts.IOptions = { - instanceOfResolver: (name: string): string => './path/to/Message' + instanceOfResolver: (): string => './path/to/Message' }; textDiff( - react2dts.generateFromFile('component', path.join(__dirname, 'es7-class.jsx'), opts), - fs.readFileSync(path.join(__dirname, 'es7-class.d.ts')).toString() + react2dts.generateFromFile('component', path.join(basedir, 'es7-class.jsx'), opts), + fs.readFileSync(path.join(basedir, 'es7-class.d.ts')).toString() ); }); it('should create top-level module definition from es7 class component', () => { const opts: react2dts.IOptions = { - instanceOfResolver: (name: string): string => './path/to/Message' + instanceOfResolver: (): string => './path/to/Message' }; textDiff( - react2dts.generateFromFile(null, path.join(__dirname, 'es7-class.jsx'), opts), - fs.readFileSync(path.join(__dirname, 'es7-class-top-level-module.d.ts')).toString() + react2dts.generateFromFile(null, path.join(basedir, 'es7-class.jsx'), opts), + fs.readFileSync(path.join(basedir, 'es7-class-top-level-module.d.ts')).toString() ); }); it('should create definition from babeled es7 class component', () => { const opts: react2dts.IOptions = { - instanceOfResolver: (name: string): string => './path/to/Message' + instanceOfResolver: (): string => './path/to/Message' }; textDiff( - react2dts.generateFromFile('component', path.join(__dirname, 'es7-class-babeled.js'), opts), - fs.readFileSync(path.join(__dirname, 'es7-class.d.ts')).toString() + react2dts.generateFromFile('component', path.join(basedir, 'es7-class-babeled.js'), opts), + fs.readFileSync(path.join(basedir, 'es7-class.d.ts')).toString() ); }); }); diff --git a/tsconfig.json b/tsconfig.json index be1392db..2808482e 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -1,13 +1,24 @@ { "compilerOptions": { - "target": "es5", - "module": "commonjs", - "noImplicitAny": true, "declaration": true, "listFiles": false, - "pretty": true + "module": "commonjs", + "noFallthroughCasesInSwitch": true, + "noImplicitAny": true, + "noImplicitReturns": true, + "noImplicitThis": true, + "noUnusedLocals": true, + "noUnusedParameters": true, + "outDir": "dist", + "pretty": true, + "strictNullChecks": true, + "stripInternal": true, + "suppressImplicitAnyIndexErrors": true, + "target": "es5" }, "exclude": [ + "index.d.ts", + "dist", "node_modules", "tests/es6-class.d.ts", "tests/es7-class.d.ts",