Skip to content

Commit 82f1a02

Browse files
milesjpvasek
authored andcommitted
Support shorthand props.
1 parent 4341d98 commit 82f1a02

File tree

4 files changed

+130
-59
lines changed

4 files changed

+130
-59
lines changed
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
import * as React from 'react';
2+
3+
export interface StatelessShorthandDefaultPropsProps {
4+
/** regularProp description */
5+
regularProp: string;
6+
/** shorthandProp description */
7+
shorthandProp?: number;
8+
/** onCallback description */
9+
onCallback?: () => void;
10+
}
11+
12+
/** StatelessShorthandDefaultProps description */
13+
export const StatelessShorthandDefaultProps: React.SFC<
14+
StatelessShorthandDefaultPropsProps
15+
> = props => <div />;
16+
17+
const shorthandProp = 123;
18+
19+
StatelessShorthandDefaultProps.defaultProps = {
20+
regularProp: 'foo',
21+
shorthandProp,
22+
onCallback() {}
23+
};

src/__tests__/parser.ts

Lines changed: 28 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -364,9 +364,34 @@ describe('parser', () => {
364364
check('StatelessWithReferencedDefaultProps', expectation);
365365
});
366366

367-
// it('supports spread props', () => {
368-
// check('StatelessWithSpreadDefaultProps', expectation);
369-
// });
367+
it('should parse props with shorthands', () => {
368+
check('StatelessShorthandDefaultProps', {
369+
StatelessShorthandDefaultProps: {
370+
onCallback: {
371+
defaultValue: null,
372+
description: 'onCallback description',
373+
required: false,
374+
type: '() => void'
375+
},
376+
regularProp: {
377+
defaultValue: 'foo',
378+
description: 'regularProp description',
379+
required: true,
380+
type: 'string'
381+
},
382+
shorthandProp: {
383+
defaultValue: '123',
384+
description: 'shorthandProp description',
385+
required: false,
386+
type: 'number'
387+
}
388+
}
389+
});
390+
});
391+
392+
it('supports spread props', () => {
393+
check('StatelessWithSpreadDefaultProps', expectation);
394+
});
370395
});
371396

372397
it('should parse functional component component defined as function', () => {

src/__tests__/testUtils.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ export interface ExpectedProp {
1919
type: string;
2020
required?: boolean;
2121
description?: string;
22-
defaultValue?: string;
22+
defaultValue?: string | null;
2323
}
2424

2525
export function fixturePath(componentName: string) {

src/parser.ts

Lines changed: 78 additions & 55 deletions
Original file line numberDiff line numberDiff line change
@@ -447,7 +447,9 @@ class Parser {
447447
let propMap = {};
448448

449449
if (properties) {
450-
propMap = getPropMap(properties as ts.NodeArray<ts.PropertyAssignment>);
450+
propMap = this.getPropMap(properties as ts.NodeArray<
451+
ts.PropertyAssignment
452+
>);
451453
}
452454

453455
return propMap;
@@ -458,7 +460,7 @@ class Parser {
458460
if (right) {
459461
const { properties } = right as ts.ObjectLiteralExpression;
460462
if (properties) {
461-
propMap = getPropMap(properties as ts.NodeArray<
463+
propMap = this.getPropMap(properties as ts.NodeArray<
462464
ts.PropertyAssignment
463465
>);
464466
}
@@ -468,6 +470,80 @@ class Parser {
468470
}
469471
return {};
470472
}
473+
474+
public getLiteralValueFromPropertyAssignment(
475+
property: ts.PropertyAssignment
476+
): string | null {
477+
let { initializer } = property;
478+
479+
// Shorthand properties, so inflect their actual value
480+
if (!initializer) {
481+
if (ts.isShorthandPropertyAssignment(property)) {
482+
const symbol = this.checker.getShorthandAssignmentValueSymbol(property);
483+
const decl =
484+
symbol && (symbol.valueDeclaration as ts.VariableDeclaration);
485+
486+
if (decl && decl.initializer) {
487+
initializer = decl.initializer!;
488+
}
489+
}
490+
}
491+
492+
if (!initializer) {
493+
return null;
494+
}
495+
496+
// Literal values
497+
switch (initializer.kind) {
498+
case ts.SyntaxKind.FalseKeyword:
499+
return 'false';
500+
case ts.SyntaxKind.TrueKeyword:
501+
return 'true';
502+
case ts.SyntaxKind.StringLiteral:
503+
return (initializer as ts.StringLiteral).text.trim();
504+
case ts.SyntaxKind.PrefixUnaryExpression:
505+
return initializer.getFullText().trim();
506+
case ts.SyntaxKind.NumericLiteral:
507+
return `${(initializer as ts.NumericLiteral).text}`;
508+
case ts.SyntaxKind.NullKeyword:
509+
return 'null';
510+
case ts.SyntaxKind.Identifier:
511+
// can potentially find other identifiers in the source and map those in the future
512+
return (initializer as ts.Identifier).text === 'undefined'
513+
? 'undefined'
514+
: null;
515+
case ts.SyntaxKind.ObjectLiteralExpression:
516+
// return the source text for an object literal
517+
return (initializer as ts.ObjectLiteralExpression).getText();
518+
default:
519+
return null;
520+
}
521+
}
522+
523+
public getPropMap(
524+
properties: ts.NodeArray<ts.PropertyAssignment>
525+
): StringIndexedObject<string> {
526+
const propMap = properties.reduce(
527+
(acc, property) => {
528+
if (ts.isSpreadAssignment(property) || !property.name) {
529+
return acc;
530+
}
531+
532+
const literalValue = this.getLiteralValueFromPropertyAssignment(
533+
property
534+
);
535+
const propertyName = getPropertyName(property.name);
536+
537+
if (typeof literalValue === 'string' && propertyName !== null) {
538+
acc[propertyName] = literalValue;
539+
}
540+
541+
return acc;
542+
},
543+
{} as StringIndexedObject<string>
544+
);
545+
return propMap;
546+
}
471547
}
472548

473549
function statementIsClassDeclaration(
@@ -490,29 +566,6 @@ function statementIsStateless(statement: ts.Statement): boolean {
490566
return false;
491567
}
492568

493-
function getPropMap(
494-
properties: ts.NodeArray<ts.PropertyAssignment>
495-
): StringIndexedObject<string> {
496-
const propMap = properties.reduce(
497-
(acc, property) => {
498-
if (ts.isSpreadAssignment(property) || !property.name) {
499-
return acc;
500-
}
501-
502-
const literalValue = getLiteralValueFromPropertyAssignment(property);
503-
const propertyName = getPropertyName(property.name);
504-
505-
if (typeof literalValue === 'string' && propertyName !== null) {
506-
acc[propertyName] = literalValue;
507-
}
508-
509-
return acc;
510-
},
511-
{} as StringIndexedObject<string>
512-
);
513-
return propMap;
514-
}
515-
516569
function getPropertyName(name: ts.PropertyName): string | null {
517570
switch (name.kind) {
518571
case ts.SyntaxKind.NumericLiteral:
@@ -526,36 +579,6 @@ function getPropertyName(name: ts.PropertyName): string | null {
526579
}
527580
}
528581

529-
function getLiteralValueFromPropertyAssignment(
530-
property: ts.PropertyAssignment
531-
): string | null {
532-
const { initializer } = property;
533-
switch (initializer.kind) {
534-
case ts.SyntaxKind.FalseKeyword:
535-
return 'false';
536-
case ts.SyntaxKind.TrueKeyword:
537-
return 'true';
538-
case ts.SyntaxKind.StringLiteral:
539-
return (initializer as ts.StringLiteral).text.trim();
540-
case ts.SyntaxKind.PrefixUnaryExpression:
541-
return initializer.getFullText().trim();
542-
case ts.SyntaxKind.NumericLiteral:
543-
return `${(initializer as ts.NumericLiteral).text}`;
544-
case ts.SyntaxKind.NullKeyword:
545-
return 'null';
546-
case ts.SyntaxKind.Identifier:
547-
// can potentially find other identifiers in the source and map those in the future
548-
return (initializer as ts.Identifier).text === 'undefined'
549-
? 'undefined'
550-
: null;
551-
case ts.SyntaxKind.ObjectLiteralExpression:
552-
// return the source text for an object literal
553-
return (initializer as ts.ObjectLiteralExpression).getText();
554-
default:
555-
return null;
556-
}
557-
}
558-
559582
function formatTag(tag: ts.JSDocTagInfo) {
560583
let result = '@' + tag.name;
561584
if (tag.text) {

0 commit comments

Comments
 (0)