Skip to content

Instantly share code, notes, and snippets.

@maraisr
Created June 6, 2019 07:31
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save maraisr/01bd7bec89b1393e997f6cf0ad527d3e to your computer and use it in GitHub Desktop.
Save maraisr/01bd7bec89b1393e997f6cf0ad527d3e to your computer and use it in GitHub Desktop.
/* tslint:disable */
import fs from 'fs';
import path from 'path';
import ts from 'typescript';
const aliasWhitelist = ['ResponsiveProp'];
const tsconfigPath = path.join(__dirname, '../tsconfig.json');
const componentsFile = path.join(__dirname, '../lib/components/index.ts');
export interface PropDetails {
propName: string;
required: boolean;
type: NormalisedPropType;
}
export type NormalisedPropType =
| string
| { type: 'union'; types: Array<NormalisedPropType> }
| { type: 'alias'; alias: string; params: Array<NormalisedPropType> };
export default () => {
const basePath = path.dirname(tsconfigPath);
const { config, error } = ts.readConfigFile(tsconfigPath, filename =>
fs.readFileSync(filename, 'utf8'),
);
if (error) {
console.error(error);
throw error;
}
const { options, errors } = ts.parseJsonConfigFileContent(
config,
ts.sys,
basePath,
{},
tsconfigPath,
);
if (errors && errors.length) {
console.error(errors[0]);
throw errors[0];
}
const program = ts.createProgram([componentsFile], options);
const checker = program.getTypeChecker();
const getComponentPropsType = (exp: ts.Symbol) => {
const type = checker.getTypeOfSymbolAtLocation(
exp,
exp.valueDeclaration || exp.declarations[0],
);
const callSignatures = type.getCallSignatures();
if (callSignatures.length) {
for (const sig of callSignatures) {
const params = sig.getParameters();
if (params.length === 0) {
continue;
}
const propsParam = params[0];
if (propsParam.name === 'props' || params.length === 1) {
return propsParam;
}
}
}
return null;
};
const normaliseType = (type: ts.Type): NormalisedPropType => {
if (checker.typeToString(type) === 'boolean') {
return 'boolean';
}
if (type.aliasSymbol) {
const alias = type.aliasSymbol.getName();
if (aliasWhitelist.includes(alias)) {
return {
params: type.aliasTypeArguments
? type.aliasTypeArguments.map(aliasArg => normaliseType(aliasArg))
: [],
alias,
type: 'alias',
};
}
}
if (type.isUnion()) {
return {
type: 'union',
types: type.types.map(unionItem => normaliseType(unionItem)),
};
}
return checker.typeToString(type);
};
const getComponentDocs = (exp: ts.Symbol) => {
const propsObj = getComponentPropsType(exp);
if (!propsObj || !propsObj.valueDeclaration) {
return {};
}
const propsType = checker.getTypeOfSymbolAtLocation(
propsObj,
propsObj.valueDeclaration,
);
return Object.assign(
{},
...propsType.getProperties().map(prop => {
const propName = prop.getName();
// Find type of prop by looking in context of the props object itself.
const propType = checker
.getTypeOfSymbolAtLocation(prop, propsObj.valueDeclaration)
.getNonNullableType();
const isOptional = (prop.getFlags() & ts.SymbolFlags.Optional) !== 0;
const typeAlias = checker.typeToString(
checker.getTypeAtLocation(prop.valueDeclaration),
);
return {
[propName]: {
propName,
required: !isOptional,
type: aliasWhitelist.includes(typeAlias)
? typeAlias
: normaliseType(propType),
},
};
}),
);
};
for (const sourceFile of program.getSourceFiles()) {
if (
!sourceFile.isDeclarationFile &&
sourceFile.fileName === componentsFile
) {
const moduleSymbol = checker.getSymbolAtLocation(sourceFile);
if (moduleSymbol) {
return Object.assign(
{},
...checker.getExportsOfModule(moduleSymbol).map(moduleExport => {
return {
[moduleExport.escapedName as string]: {
props: getComponentDocs(moduleExport),
},
};
}),
);
}
}
}
};
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment