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

Commit

Permalink
Showing 6 changed files with 155 additions and 6 deletions.
19 changes: 19 additions & 0 deletions packages/analyzer/src/fixtures/prop-slots.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import * as React from 'react';

export interface PropsWithSlots {
reactNode: React.ReactNode;
reactNodeArray: React.ReactNode[];
explicitReactNodeArray: React.ReactNodeArray;
reactChild: React.ReactChild;
reactChildArray: React.ReactChild[];
reactElement: React.ReactElement<any>;
reactElementArray: React.ReactElement<any>[];
jsxElement: JSX.Element;
jsxElementArray: JSX.Element[];
union: React.ReactChild | React.ReactElement<any> | JSX.Element | string;
unionArray: (React.ReactChild | React.ReactElement<any> | JSX.Element | string)[];
disjunct: string | any;
disjunctArray: string[];
}

export const ReactElement: React.SFC<PropsWithSlots> = () => null;
75 changes: 75 additions & 0 deletions packages/analyzer/src/react-utils/is-react-slot-type.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
import * as TestUtils from '../test-utils';
import { isReactSlotType } from './is-react-slot-type';

const fixtures = require('fixturez')(__dirname);

let ctx: ReturnType<typeof TestUtils.getFixtureSourceFile>;

beforeAll(() => {
ctx = TestUtils.getFixtureSourceFile('prop-slots.tsx', { fixtures });
});

test('returns true for React.ReactNode', () => {
const prop = TestUtils.getNamedPropType('reactNode', ctx);
expect(isReactSlotType(prop.type, ctx)).toBe(true);
});

test('returns true for React.ReactNodeArray', () => {
const prop = TestUtils.getNamedPropType('explicitReactNodeArray', ctx);
expect(isReactSlotType(prop.type, ctx)).toBe(true);
});

test('returns true for React.ReactChild', () => {
const prop = TestUtils.getNamedPropType('reactChild', ctx);
expect(isReactSlotType(prop.type, ctx)).toBe(true);
});

test('returns true for React.ReactElement', () => {
const prop = TestUtils.getNamedPropType('reactElement', ctx);
expect(isReactSlotType(prop.type, ctx)).toBe(true);
});

test('returns true for JSX.Element', () => {
const prop = TestUtils.getNamedPropType('jsxElement', ctx);
expect(isReactSlotType(prop.type, ctx)).toBe(true);
});

test('returns true for union type with slot members', () => {
const prop = TestUtils.getNamedPropType('union', ctx);
expect(isReactSlotType(prop.type, ctx)).toBe(true);
});

test('returns true for union type with only disjunct members', () => {
const prop = TestUtils.getNamedPropType('disjunct', ctx);
expect(isReactSlotType(prop.type, ctx)).toBe(false);
});

test('returns true for React.ReactNode[]', () => {
const prop = TestUtils.getNamedPropType('reactNodeArray', ctx);
expect(isReactSlotType(prop.type, ctx)).toBe(true);
});

test('returns true for React.ReactChild[]', () => {
const prop = TestUtils.getNamedPropType('reactChildArray', ctx);
expect(isReactSlotType(prop.type, ctx)).toBe(true);
});

test('returns true for React.ReactElement[]', () => {
const prop = TestUtils.getNamedPropType('reactElementArray', ctx);
expect(isReactSlotType(prop.type, ctx)).toBe(true);
});

test('returns true for JSX.Element[]', () => {
const prop = TestUtils.getNamedPropType('jsxElementArray', ctx);
expect(isReactSlotType(prop.type, ctx)).toBe(true);
});

test('returns true for Union[]', () => {
const prop = TestUtils.getNamedPropType('unionArray', ctx);
expect(isReactSlotType(prop.type, ctx)).toBe(true);
});

test('returns true for Disjunct[]', () => {
const prop = TestUtils.getNamedPropType('disjunctArray', ctx);
expect(isReactSlotType(prop.type, ctx)).toBe(false);
});
18 changes: 17 additions & 1 deletion packages/analyzer/src/react-utils/is-react-slot-type.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,19 @@
// tslint:disable:no-bitwise
import { hasReactTypings } from './has-react-typings';
import * as TypeScript from 'typescript';
import { isUnionType } from '../typescript-utils/is-union-type';
import { isTypeReference } from '../typescript-utils/is-type-reference';

const REACT_SLOT_TYPES = ['Element', 'ReactNode', 'ReactChild'];
const REACT_SLOT_TYPES = ['Element', 'ReactElement', 'ReactNode', 'ReactNodeArray', 'ReactChild'];

export function isReactSlotType(
type: TypeScript.Type,
ctx: { program: TypeScript.Program }
): boolean {
if (isUnionType(type)) {
return type.types.some(typeMember => isReactSlotType(typeMember, ctx));
}

const typechecker = ctx.program.getTypeChecker();
const symbol = type.aliasSymbol || type.symbol || type.getSymbol();

@@ -20,6 +26,16 @@ export function isReactSlotType(
? typechecker.getAliasedSymbol(symbol)
: symbol;

if (resolvedSymbol.name === 'Array' && isTypeReference(type) && type.typeArguments) {
const arg = type.typeArguments[0]!;

if (!arg) {
return false;
}

return isReactSlotType(arg, ctx);
}

if (!REACT_SLOT_TYPES.includes(resolvedSymbol.name)) {
return false;
}
28 changes: 23 additions & 5 deletions packages/analyzer/src/test-utils.ts
Original file line number Diff line number Diff line change
@@ -16,14 +16,16 @@ export interface Export {
type: TypeScript.Type;
}

export const getFixtureSourceFile = (
name: string | string[],
ctx: { fixtures: Fixtures }
): {
export interface FixtureSourceFile {
sourceFile: TypeScript.SourceFile;
sourceFiles: TypeScript.SourceFile[];
program: TypeScript.Program;
} => {
}

export const getFixtureSourceFile = (
name: string | string[],
ctx: { fixtures: Fixtures }
): FixtureSourceFile => {
const names = Array.isArray(name) ? name : [name];
const paths = names
.map(n => ctx.fixtures.find(n))
@@ -61,6 +63,22 @@ export const getFirstPropType = (
return propTypes[0];
};

export const getNamedPropType = (name: string, ctx: FixtureSourceFile): Prop => {
const props = getPropTypes(ctx.sourceFile, ctx);

const result = props.find(prop => prop.symbol.getName() === name);

if (!result) {
throw new Error(
`Could not find prop with name ${name}. Available props: ${props
.map(p => p.symbol.getName())
.join(', ')}`
);
}

return result;
};

export const getPropTypes = (
sourceFile: TypeScript.SourceFile,
ctx: { program: TypeScript.Program }
15 changes: 15 additions & 0 deletions packages/analyzer/src/typescript-utils/is-type-reference.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import * as TypeScript from 'typescript';

export function isTypeReference(t: TypeScript.Type): t is TypeScript.TypeReference {
if (!isObjectType(t)) {
return false;
}

// tslint:disable-next-line:no-bitwise
return (t.objectFlags & TypeScript.ObjectFlags.Reference) === TypeScript.ObjectFlags.Reference;
}

function isObjectType(t: TypeScript.Type): t is TypeScript.ObjectType {
// tslint:disable-next-line:no-bitwise
return (t.flags & TypeScript.TypeFlags.Object) === TypeScript.TypeFlags.Object;
}
6 changes: 6 additions & 0 deletions packages/analyzer/src/typescript-utils/is-union-type.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
import * as TypeScript from 'typescript';

export function isUnionType(t: TypeScript.Type): t is TypeScript.UnionType {
// tslint:disable-next-line:no-bitwise
return (t.flags & TypeScript.TypeFlags.Union) === TypeScript.TypeFlags.Union;
}

1 comment on commit f2fc8e1

@marionebl
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please sign in to comment.