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

Commit c3d79b6

Browse files
committedMar 2, 2017
fix: fix and improve error output
1 parent 32307a7 commit c3d79b6

File tree

6 files changed

+81
-42
lines changed

6 files changed

+81
-42
lines changed
 

‎package.json

+3-2
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,6 @@
4646
"babel-core": "6.23.1",
4747
"babel-preset-es2015": "6.18.0",
4848
"babel-register": "6.18.0",
49-
"chalk": "1.1.3",
5049
"chokidar-cli": "1.2.0",
5150
"conventional-changelog-cli": "1.2.0",
5251
"coveralls": "^2.11.15",
@@ -65,10 +64,12 @@
6564
"astq": "2.0.0",
6665
"babel-generator": "6.23.0",
6766
"babylon": "6.16.1",
67+
"chalk": "1.1.3",
6868
"dts-dom": "0.1.15",
6969
"get-stdin": "5.0.1",
7070
"meow": "3.7.0",
71-
"pascal-case": "2.0.0"
71+
"pascal-case": "2.0.0",
72+
"strip-ansi": "3.0.1"
7273
},
7374
"config": {
7475
"commitizen": {

‎src/index.ts

+26-2
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,24 @@ export interface Options {
2929
* @deprecated
3030
*/
3131
generator?: Generator;
32+
33+
/**
34+
* Could be given if the generator is started with an AST.
35+
*
36+
* This is helpful to create better messages in case of errors/warnings.
37+
*
38+
* @type {string}
39+
* @memberOf Options
40+
*/
41+
source?: string;
42+
43+
/**
44+
* Could be given to show filename in error/warning messages.
45+
*
46+
* @type {string}
47+
* @memberOf Options
48+
*/
49+
filename?: string;
3250
}
3351

3452
export function cli(options: any): void {
@@ -41,7 +59,10 @@ export function cli(options: any): void {
4159
});
4260
}
4361

44-
export function generateFromFile(moduleName: string|null, path: string, options?: IOptions): string {
62+
export function generateFromFile(moduleName: string|null, path: string, options: IOptions = {}): string {
63+
if (!options.filename) {
64+
options.filename = path;
65+
}
4566
return generateFromSource(moduleName, fs.readFileSync(path).toString(), options);
4667
}
4768

@@ -68,12 +89,15 @@ export function generateFromSource(moduleName: string|null, code: string, option
6889
'functionSent'
6990
]
7091
});
92+
if (!options.source) {
93+
options.source = code;
94+
}
7195
return generateFromAst(moduleName, ast, options);
7296
}
7397

7498
export function generateFromAst(moduleName: string|null, ast: any, options: IOptions = {}): string {
7599
if (options.generator) {
76100
return generateTypings(moduleName, ast, options);
77101
}
78-
return createTypings(moduleName, ast, options.instanceOfResolver);
102+
return createTypings(moduleName, ast, options);
79103
}

‎src/types.ts

+38-21
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
import astToCode from 'babel-generator';
2+
import * as chalk from 'chalk';
23
import * as dom from 'dts-dom';
4+
import { IOptions } from './index';
35
import { propTypeQueryExpression, AstQuery } from './typings';
46

57
export interface TypeDeclaration {
@@ -14,22 +16,22 @@ function getTypeDeclaration(type: any, optional: boolean): TypeDeclaration {
1416
};
1517
}
1618

17-
export function get(ast: AstQuery, propertyAst: any, propTypesName: string|undefined): TypeDeclaration {
19+
export function get(ast: AstQuery, propertyAst: any, propTypesName: string|undefined,
20+
options: IOptions): TypeDeclaration {
1821
try {
1922
const simpleType = getSimpleType(ast, propertyAst, propTypesName);
2023
if (simpleType) {
2124
return simpleType;
2225
}
23-
const complexType = getComplexType(ast, propertyAst, propTypesName);
26+
const complexType = getComplexType(ast, propertyAst, propTypesName, options);
2427
if (complexType) {
2528
return complexType;
2629
}
2730
} catch (e) {
28-
console.error('Failed to infer PropType; Fallback to any');
2931
if (e.loc) {
30-
const src = astToCode(ast.ast).code;
31-
console.error(`Line ${e.loc.start.line}: ${src.split('\n')[e.loc.start.line - 1]}`);
32+
printErrorWithContext(e, ast.ast, options);
3233
} else {
34+
console.error('Failed to infer PropType; Fallback to any');
3335
console.error(e.stack);
3436
}
3537
}
@@ -73,18 +75,18 @@ function getSimpleType(ast: AstQuery, propertyAst: any,
7375
}
7476

7577
function getComplexType(ast: AstQuery, propertyAst: any,
76-
propTypesName: string|undefined): TypeDeclaration|undefined {
78+
propTypesName: string|undefined, options: IOptions): TypeDeclaration|undefined {
7779
const [required, complexTypeName, typeAst] = getComplexTypeName(ast, propertyAst, propTypesName);
7880
switch (complexTypeName) {
7981
case 'instanceOf':
8082
return getTypeDeclaration(dom.create.typeof(
8183
dom.create.namedTypeReference(typeAst.arguments[0].name)), !required);
8284
case 'oneOfType':
8385
const typeDecls = typeAst.arguments[0].elements
84-
.map((subtree: any) => get(ast, subtree, propTypesName)) as TypeDeclaration[];
86+
.map((subtree: any) => get(ast, subtree, propTypesName, options)) as TypeDeclaration[];
8587
return getTypeDeclaration(dom.create.union(typeDecls.map(type => type.type)), !required);
8688
case 'arrayOf':
87-
const typeDecl = get(ast, typeAst.arguments[0], propTypesName);
89+
const typeDecl = get(ast, typeAst.arguments[0], propTypesName, options);
8890
return getTypeDeclaration(dom.create.array(typeDecl.type), !required);
8991
case 'oneOf':
9092
// tslint:disable:next-line comment-format
@@ -93,7 +95,7 @@ function getComplexType(ast: AstQuery, propertyAst: any,
9395
return getTypeDeclaration(dom.create.union(enumEntries as dom.Type[]), !required);
9496
case 'shape':
9597
const entries = getShapeProperties(ast, typeAst.arguments[0]).map((entry: any) => {
96-
const typeDecl = get(ast, entry.value, propTypesName);
98+
const typeDecl = get(ast, entry.value, propTypesName, options);
9799
return dom.create.property(entry.key.name, typeDecl.type,
98100
typeDecl.optional ? dom.DeclarationFlags.Optional : dom.DeclarationFlags.None);
99101
});
@@ -144,16 +146,12 @@ function getEnumValues(ast: AstQuery, oneOfTypes: any): any[] {
144146
/:init *
145147
`);
146148
if (!res[0]) {
147-
const error = new Error('Failed to lookup enum values');
148-
(error as any).loc = oneOfTypes.loc;
149-
throw error;
149+
throwWithLocation('Failed to lookup enum values', oneOfTypes);
150150
}
151151
oneOfTypes = res[0];
152152
}
153153
if (!oneOfTypes.elements) {
154-
const error = new Error('Failed to lookup enum values');
155-
(error as any).loc = oneOfTypes.loc;
156-
throw error;
154+
throwWithLocation('Failed to lookup enum values', oneOfTypes);
157155
}
158156
return (oneOfTypes.elements as any[]).map((element: any) => {
159157
// fixme: This are not named references!
@@ -178,14 +176,33 @@ function getShapeProperties(ast: AstQuery, input: any): any[] {
178176
if (res[0]) {
179177
return res[0].properties;
180178
}
181-
const error = new Error('Failed to lookup shape properties');
182-
(error as any).loc = input.loc;
183-
throw error;
179+
throwWithLocation('Failed to lookup shape properties', input);
184180
}
185181
if (!input.properties) {
186-
const error = new Error('Failed to lookup shape properties');
187-
(error as any).loc = input.loc;
188-
throw error;
182+
throwWithLocation('Failed to lookup shape properties', input);
189183
}
190184
return input.properties;
191185
}
186+
187+
function throwWithLocation(message: string, ast: any): never {
188+
const error = new Error(message);
189+
(error as any).loc = ast.loc;
190+
(error as any).start = ast.start;
191+
(error as any).end = ast.end;
192+
throw error;
193+
}
194+
195+
function printErrorWithContext(e: any, ast: any, options: IOptions): void {
196+
console.error(`${(options.filename || '')} ${e.message}`);
197+
const src = options.source || astToCode(ast.ast).code;
198+
// console.log(src.substring(e.start, e.end));
199+
const lines = src.split('\n');
200+
const errorLine = lines[e.loc.start.line - 1];
201+
202+
console.error(`Line ${e.loc.start.line - 1}: ${lines[e.loc.start.line - 2]}`);
203+
console.error(`Line ${e.loc.start.line}: ` + errorLine.substring(0, e.loc.start.column)
204+
+ chalk.red(errorLine.substring(e.loc.start.column, e.loc.end.column))
205+
+ errorLine.substring(e.loc.end.column));
206+
console.error(`Line ${e.loc.start.line + 1}: ${lines[e.loc.start.line]}`);
207+
console.error();
208+
}

‎src/typings.ts

+8-9
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import * as ASTQ from 'astq';
22
import * as dom from 'dts-dom';
33
import pascalCase = require('pascal-case');
4-
import { InstanceOfResolver } from './index';
4+
import { InstanceOfResolver, IOptions } from './index';
55
import * as types from './types';
66

77
export interface AstQuery {
@@ -10,8 +10,7 @@ export interface AstQuery {
1010
querySubtree(subtree: any, query: string): any[];
1111
}
1212

13-
export function createTypings(moduleName: string|null, programAst: any,
14-
instanceOfResolver: InstanceOfResolver | undefined): string {
13+
export function createTypings(moduleName: string|null, programAst: any, options: IOptions): string {
1514
const astq = new ASTQ();
1615
const ast = {
1716
ast: programAst,
@@ -25,7 +24,7 @@ export function createTypings(moduleName: string|null, programAst: any,
2524
const reactComponentName = getReactComponentName(ast);
2625
const propTypesName = getPropTypesName(ast);
2726
const importedTypes = getInstanceOfPropTypes(ast, propTypesName);
28-
const importStatements = getImportStatements(ast, importedTypes, instanceOfResolver);
27+
const importStatements = getImportStatements(ast, importedTypes, options.instanceOfResolver);
2928
const componentNames = getUniqueNames([
3029
...getComponentNamesByPropTypeAssignment(ast),
3130
...getComponentNamesByStaticPropTypeAttribute(ast),
@@ -50,7 +49,7 @@ export function createTypings(moduleName: string|null, programAst: any,
5049
const propTypes = getPropTypesFromAssignment(ast, componentName) ||
5150
getPropTypesFromStaticAttribute(ast, componentName);
5251
if (exportType) {
53-
createExportedTypes(m, ast, componentName, reactComponentName, propTypes, propTypesName, exportType);
52+
createExportedTypes(m, ast, componentName, reactComponentName, propTypes, propTypesName, exportType, options);
5453
}
5554
});
5655

@@ -65,13 +64,13 @@ export function createTypings(moduleName: string|null, programAst: any,
6564

6665
function createExportedTypes(m: dom.ModuleDeclaration, ast: AstQuery, componentName: string,
6766
reactComponentName: string|undefined, propTypes: any, propTypesName: string|undefined,
68-
exportType: dom.DeclarationFlags): void {
67+
exportType: dom.DeclarationFlags, options: IOptions): void {
6968
const classComponent = isClassComponent(ast, componentName, reactComponentName);
7069

7170
const interf = dom.create.interface(`${componentName}Props`);
7271
interf.flags = dom.DeclarationFlags.Export;
7372
if (propTypes) {
74-
createPropTypeTypings(interf, ast, propTypes, propTypesName);
73+
createPropTypeTypings(interf, ast, propTypes, propTypesName, options);
7574
extractComplexTypes(m, interf, componentName);
7675
}
7776

@@ -93,12 +92,12 @@ function createExportedTypes(m: dom.ModuleDeclaration, ast: AstQuery, componentN
9392
}
9493

9594
function createPropTypeTypings(interf: dom.InterfaceDeclaration, ast: AstQuery, propTypes: any,
96-
propTypesName: string|undefined): void {
95+
propTypesName: string|undefined, options: IOptions): void {
9796
const res = ast.querySubtree(propTypes, `
9897
/ ObjectProperty
9998
`);
10099
res.forEach(propertyAst => {
101-
const typeDecl = types.get(ast, propertyAst.value, propTypesName);
100+
const typeDecl = types.get(ast, propertyAst.value, propTypesName, options);
102101
const property = dom.create.property(propertyAst.key.name || propertyAst.key.value, typeDecl.type,
103102
typeDecl.optional ? dom.DeclarationFlags.Optional : 0);
104103
if (propertyAst.leadingComments && propertyAst.leadingComments[0].type === 'CommentBlock') {

‎tests/error-reporting-test.ts

+5-8
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import test from 'ava';
2+
import * as stripAnsi from 'strip-ansi';
23

34
import * as react2dts from '../src/index';
45

@@ -29,8 +30,7 @@ test('In case of error during shape type inference the error information should
2930
}
3031
`);
3132
const args = t.context.args.reduce((akku: any[], args: any[]) => [...akku, ...args], []);
32-
const idx = args.indexOf('Line 6: someShape: React.PropTypes.shape(shape)');
33-
t.is(idx, 1);
33+
t.is(stripAnsi(args[2]), 'Line 6: someShape: React.PropTypes.shape(shape)');
3434
});
3535

3636
test('In case of error during enum type inference the error information should be retained', t => {
@@ -44,8 +44,7 @@ test('In case of error during enum type inference the error information should b
4444
}
4545
`);
4646
const args = t.context.args.reduce((akku: any[], args: any[]) => [...akku, ...args], []);
47-
const idx = args.indexOf('Line 6: list: React.PropTypes.oneOf(list)');
48-
t.is(idx, 1);
47+
t.is(stripAnsi(args[2]), 'Line 6: list: React.PropTypes.oneOf(list)');
4948
});
5049

5150
test('In case of error during enum value creation inference the error information should be retained', t => {
@@ -59,8 +58,7 @@ test('In case of error during enum value creation inference the error informatio
5958
}
6059
`);
6160
const args = t.context.args.reduce((akku: any[], args: any[]) => [...akku, ...args], []);
62-
const idx = args.indexOf('Line 6: list: React.PropTypes.oneOf(Object.keys(object))');
63-
t.is(idx, 1);
61+
t.is(stripAnsi(args[2]), 'Line 6: list: React.PropTypes.oneOf(Object.keys(object))');
6462
});
6563

6664
test('In case of error during shape type inference the error information should be retained', t => {
@@ -74,6 +72,5 @@ test('In case of error during shape type inference the error information should
7472
}
7573
`);
7674
const args = t.context.args.reduce((akku: any[], args: any[]) => [...akku, ...args], []);
77-
const idx = args.indexOf('Line 6: shape: React.PropTypes.shape(some.shape)');
78-
t.is(idx, 1);
75+
t.is(stripAnsi(args[2]), 'Line 6: shape: React.PropTypes.shape(some.shape)');
7976
});

‎typings/strip-ansi.d.ts

+1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
declare module 'strip-ansi';

0 commit comments

Comments
 (0)
This repository has been archived.