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

Commit

Permalink
feat: write multiple outputs per file (#211)
Browse files Browse the repository at this point in the history
* feat: write multiple outputs per file
* feat: reimplemented simple types
* feat: readd all simple and complex types
* feat: feature complete
* chore: do not cover deprecated code
* chore: add missing dependency to rimraf
* refactor: use meow instead of minimist
* refactor: use get-stdin
* fix: fix smaller changes to previous behaviour
  • Loading branch information
KnisterPeter committed Nov 3, 2016
1 parent 597c266 commit f6f63ae
Show file tree
Hide file tree
Showing 25 changed files with 848 additions and 412 deletions.
15 changes: 15 additions & 0 deletions .vscode/settings.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
{
"typescript.tsdk": "./node_modules/typescript/lib/",
"files.exclude": {
"**/.git": true,
"**/.svn": true,
"**/.hg": true,
"**/.DS_Store": true
},
"search.exclude": {
"**/dist": true,
"**/coverage": true,
"**/node_modules": true,
"**/bower_components": true
}
}
25 changes: 20 additions & 5 deletions cli.js
Original file line number Diff line number Diff line change
@@ -1,13 +1,28 @@
#!/usr/bin/env node
var react2dts = require('./dist/src/index');
var minimist = require('minimist');
var meow = require('meow');

var options = minimist(process.argv.slice(2), {
string: ['name', 'module-name'],
boolean: ['top-level-module'],
const cli = meow(`
Usage
$ react2dts [--module-name <name> | --top-level-module]
react2dts reads from stdin to process a file.
Options
--module-name, --name name of the module to create
--top-level-module if the created module should live in top-level
Examples
$ cat <some/react/component.jsx> |react2dts --module-name module-name
$ cat <some/react/component.jsx> |react2dts --top-level-module
`, {
alias: {
'module-name': 'name'
}
});
if (Object.keys(cli.flags).length === 0) {
cli.showHelp(1);
}

react2dts.cli(options);
react2dts.cli(cli.flags);
10 changes: 7 additions & 3 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@
],
"scripts": {
"start": "npm test",
"clean": "rm -f index.js index.js.map tests/*-test.js tests/*-test.js.map",
"clean": "rimraf dist",
"prebuild": "npm run clean",
"build": "tsc --sourceMap",
"build:inline": "tsc --inlineSourceMap",
Expand Down Expand Up @@ -51,6 +51,7 @@
"mocha": "3.1.2",
"nyc": "8.4.0",
"react": "15.3.2",
"rimraf": "2.5.4",
"source-map-support": "0.4.6",
"standard-version": "3.0.0",
"tslint": "3.15.1",
Expand All @@ -60,7 +61,8 @@
"astq": "1.8.0",
"babylon": "6.13.1",
"dts-dom": "0.1.11",
"minimist": "1.2.0"
"get-stdin": "5.0.1",
"meow": "3.7.0"
},
"config": {
"commitizen": {
Expand All @@ -72,7 +74,9 @@
"node_modules",
"coverage",
"dist/tests",
"tests"
"tests",
"dist/src/deprecated.js",
"dist/src/analyzer.js"
]
}
}
34 changes: 14 additions & 20 deletions src/analyzer.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,19 @@
import * as dom from 'dts-dom';
import * as astqts from 'astq';
const ASTQ: typeof astqts.ASTQ = astqts as any;

import { InstanceOfResolver, IPropTypes, IProp, IASTNode } from './index';
import { InstanceOfResolver } from './index';
import { IPropTypes, IProp } from './deprecated';

interface IASTNode {
type: string;
loc: Object;
[name: string]: any;
value?: any;
key?: any;
expression?: any;
id?: any;
body?: any;
}

const defaultInstanceOfResolver: InstanceOfResolver = (_name: string): undefined => undefined;

Expand Down Expand Up @@ -30,7 +41,6 @@ function getOptionalDocumentation(propertyNode: any): string {
export function getTypeFromPropType(node: IASTNode, instanceOfResolver = defaultInstanceOfResolver): IProp {
const result: IProp = {
type: 'any',
type2: 'any',
optional: true
};
if (isNode(node)) {
Expand All @@ -39,54 +49,41 @@ export function getTypeFromPropType(node: IASTNode, instanceOfResolver = default
switch (type.name) {
case 'any':
result.type = 'any';
result.type2 = 'any';
break;
case 'array':
result.type = (type.arrayType || 'any') + '[]';
result.type2 = dom.create.array(type.arrayType2 || 'any');
break;
case 'bool':
result.type = 'boolean';
result.type2 = 'boolean';
break;
case 'func':
result.type = '(...args: any[]) => any';
result.type2 = dom.create.functionType([
dom.create.parameter('args', dom.create.array('any'), dom.ParameterFlags.Rest)], 'any');
break;
case 'number':
result.type = 'number';
result.type2 = 'number';
break;
case 'object':
result.type = 'Object';
result.type2 = dom.create.namedTypeReference('Object');
break;
case 'string':
result.type = 'string';
result.type2 = 'string';
break;
case 'node':
result.type = 'React.ReactNode';
result.type2 = dom.create.namedTypeReference('React.ReactNode');
break;
case 'element':
result.type = 'React.ReactElement<any>';
result.type2 = dom.create.namedTypeReference('React.ReactElement<any>');
break;
case 'union':
result.type = type.types.map((unionType: string) => unionType).join('|');
result.type2 = dom.create.union(type.types2);
break;
case 'instanceOf':
if (type.importPath) {
result.type = 'typeof ' + type.type;
result.type2 = dom.create.typeof(type.type2);
(result as any).importType = type.type;
(result as any).importPath = type.importPath;
} else {
result.type = 'any';
result.type2 = 'any';
}
break;
}
Expand All @@ -109,23 +106,20 @@ function getReactPropTypeFromExpression(node: any, instanceOfResolver: InstanceO
return {
name: 'instanceOf',
type: node.arguments[0].name,
type2: dom.create.namedTypeReference(node.arguments[0].name),
importPath: instanceOfResolver(node.arguments[0].name)
};
case 'arrayOf':
const arrayType = getTypeFromPropType(node.arguments[0], instanceOfResolver);
return {
name: 'array',
arrayType: arrayType.type,
arrayType2: arrayType.type2
};
case 'oneOfType':
const unionTypes = node.arguments[0].elements.map((element: IASTNode) =>
getTypeFromPropType(element, instanceOfResolver));
return {
name: 'union',
types: unionTypes.map((type: any) => type.type),
types2: unionTypes.map((type: any) => type.type2)
types: unionTypes.map((type: any) => type.type)
};
}
}
Expand Down
199 changes: 199 additions & 0 deletions src/deprecated.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,199 @@
import * as astqts from 'astq';
const ASTQ: typeof astqts.ASTQ = astqts as any;
import { IOptions, InstanceOfResolver } from './index';
import { Generator } from './generator';
import { parsePropTypes } from './analyzer';

export enum ExportType {
default,
named
}

export interface IProp {
type: string;
optional: boolean;
importType?: string;
importPath?: string;
documentation?: string;
}

export interface IPropTypes {
[name: string]: IProp;
}

export function generateTypings(moduleName: string|null, ast: any, options: IOptions): string {
const parsingResult = parseAst(ast, options.instanceOfResolver);
return deprecatedGenerator(options.generator as Generator, moduleName, parsingResult);
}

function deprecatedGenerator(generator: Generator, moduleName: string|null,
{exportType, classname, propTypes}: IParsingResult): string {
const componentName = classname || 'Anonymous';
const generateTypings = () => {
generator.import('* as React', 'react');
if (propTypes) {
Object.keys(propTypes).forEach(propName => {
const prop = propTypes[propName];
if (prop.importType && prop.importPath) {
generator.import(prop.importType, prop.importPath);
}
});
}
generator.nl();
generator.props(componentName, propTypes);
generator.nl();
generator.exportDeclaration(exportType, () => {
generator.class(componentName, !!propTypes);
});
};

if (moduleName === null) {
generateTypings();
} else {
generator.declareModule(moduleName, generateTypings);
}
return generator.toString();
}

/**
* @internal
*/
export interface IParsingResult {
exportType: ExportType;
classname: string|undefined;
functionname: string|undefined;
propTypes: IPropTypes;
}

function parseAst(ast: any, instanceOfResolver?: InstanceOfResolver): IParsingResult {
let exportType: ExportType|undefined;
let functionname: string|undefined;
let propTypes: IPropTypes|undefined;

let classname = getClassName(ast);
if (classname) {
propTypes = getEs7StyleClassPropTypes(ast, classname, instanceOfResolver);
exportType = getClassExportType(ast, classname);
}
if (!propTypes) {
const componentName = getComponentNameByPropTypeAssignment(ast);
if (componentName) {
const astq = new ASTQ();
const exportTypeNodes = astq.query(ast, `
//ExportNamedDeclaration // VariableDeclarator[
/:id Identifier[@name=='${componentName}'] &&
/:init ArrowFunctionExpression // JSXElement
],
//ExportNamedDeclaration // FunctionDeclaration[/:id Identifier[@name == '${componentName}']] // JSXElement,
//ExportDefaultDeclaration // AssignmentExpression[/:left Identifier[@name == '${componentName}']]
// ArrowFunctionExpression // JSXElement,
//ExportDefaultDeclaration // FunctionDeclaration[/:id Identifier[@name == '${componentName}']] // JSXElement
`);
if (exportTypeNodes.length > 0) {
functionname = componentName;
exportType = ExportType.named;
}
propTypes = getPropTypesFromAssignment(ast, componentName, instanceOfResolver);
}
if (!exportType) {
const astq = new ASTQ();
const commonJsExports = astq.query(ast, `
// AssignmentExpression[
/:left MemberExpression[
/:object Identifier[@name == 'exports'] &&
/:property Identifier[@name == 'default']
] &&
/:right Identifier[@name == '${componentName}']
]
`);
if (commonJsExports.length > 0) {
classname = componentName;
exportType = ExportType.default;
}
}
}

if (exportType === undefined) {
throw new Error('No exported component found');
}
return {
exportType,
classname,
functionname,
propTypes: propTypes || {}
};
}

function getClassName(ast: any): string|undefined {
const astq = new ASTQ();
const classDeclarationNodes = astq.query(ast, `
//ClassDeclaration[
/:id Identifier[@name]
]
`);
if (classDeclarationNodes.length > 0) {
return classDeclarationNodes[0].id.name;
}
return undefined;
}

function getEs7StyleClassPropTypes(ast: any, classname: string,
instanceOfResolver?: InstanceOfResolver): IPropTypes|undefined {
const astq = new ASTQ();
const propTypesNodes = astq.query(ast, `
//ClassDeclaration[/:id Identifier[@name == '${classname}']]
//ClassProperty[/:key Identifier[@name == 'propTypes']]
`);
if (propTypesNodes.length > 0) {
return parsePropTypes(propTypesNodes[0].value, instanceOfResolver);
}
return undefined;
}

function getClassExportType(ast: any, classname: string): ExportType|undefined {
const astq = new ASTQ();
const exportTypeNodes = astq.query(ast, `
//ExportNamedDeclaration [
/ClassDeclaration [ /:id Identifier[@name=='${classname}'] ]
],
//ExportDefaultDeclaration [
/ClassDeclaration [ /:id Identifier[@name=='${classname}'] ]
]
`);
if (exportTypeNodes.length > 0) {
return exportTypeNodes[0].type === 'ExportDefaultDeclaration' ? ExportType.default : ExportType.named;
}
return undefined;
}

function getComponentNameByPropTypeAssignment(ast: any): string|undefined {
const astq = new ASTQ();
const componentNames = astq.query(ast, `
//AssignmentExpression
/:left MemberExpression[
/:object Identifier &&
/:property Identifier[@name == 'propTypes']
]
`);
if (componentNames.length > 0) {
return componentNames[0].object.name;
}
return undefined;
}

function getPropTypesFromAssignment(ast: any, componentName: string,
instanceOfResolver?: InstanceOfResolver): IPropTypes|undefined {
const astq = new ASTQ();
const propTypesNodes = astq.query(ast, `
//AssignmentExpression[
/:left MemberExpression[
/:object Identifier[@name == '${componentName}'] &&
/:property Identifier[@name == 'propTypes']
]
] /:right *
`);
if (propTypesNodes.length > 0) {
return parsePropTypes(propTypesNodes[0], instanceOfResolver);
}
return undefined;
}
Loading

0 comments on commit f6f63ae

Please sign in to comment.