Skip to content

Instantly share code, notes, and snippets.

@Marvin9
Created March 25, 2020 13:10
Show Gist options
  • Save Marvin9/193161674f938b016dce02f8582cc4ab to your computer and use it in GitHub Desktop.
Save Marvin9/193161674f938b016dce02f8582cc4ab to your computer and use it in GitHub Desktop.
const ts = require('typescript');
const fs = require('fs');
const path = require('path');
const FILENAME_PROVIDED_BY_DOCER = 'foo.tsx';
const FILEPATH_PROVIDED_BY_DOCER = '/path/to/foo.tsx';
// UTILS
// object has key?
const has = (object, key) => Object.prototype.hasOwnProperty.call(object, key);
// STEP 1 : CONVERT INPUT FILE INTO AST FORM.
const node = ts.createSourceFile(
FILENAME_PROVIDED_BY_DOCER,
fs.readFileSync(FILEPATH_PROVIDED_BY_DOCER),
ts.ScriptTarget.Latest
);
// MEMORY OF TINY
let allProps = [];
let importMemory = {};
let typeDeclarationMemory = {};
let interfaceDeclarationMemory = {};
const resolveType = node => {
const referenceName = node.typeName.escapedText;
// IF it has declared in same file.
if (has(typeDeclarationMemory, referenceName)) {
return typeDeclarationMemory[referenceName];
} else if (has(importMemory, referenceName)) { // IF it has declared in other file.
resolveImport(node, FILEPATH_PROVIDED_BY_DOCER);
// After resolving import, we should have required value in typeDeclarationMemory
return typeDeclarationMemory[referenceName];
} else { // If neither of above, It means type is third party reference
// example -> Ref<HTMLElement>
let reference = referenceName;
if (node.typeArguments.length) {
let referenceArguments = [];
node.typeArguments.forEach(
argument => {
referenceArguments = [...referenceArguments, argument.typeName.escapedText];
}
);
reference += `<${referenceArguments.join(',')}>`;
}
return reference;
}
};
const iterator = (node, goal) => {
switch (node.kind) {
// STEP 2 : SCAN EACH STATEMENT
case ts.SyntaxKind.SourceFile: {
node.statements.forEach(
// USE RECURSION
statement => iterator(statement, goal)
);
break;
}
// STEP 3 : IF IMPORT DECLARATION, STORE IN IMPORT MEMORY
case ts.SyntaxKind.ImportDeclaration: {
/**
* Two types of import
*
* 1. name bindings
* Example : import { foo, bar } from './reference';
* 2. simple import
* Example : import foo from './reference';
*/
const name_path_pair = extractFromImport(node);
Object.keys(name_path_pair).forEach(
key => {
// STORE IN IMPORT MEMORY
importMemory = {
...importMemory,
[key]: name_path_pair[key]
};
}
);
break;
} // END OF CASE IMPORT DECLARATION
// STEP 4 : IF TYPE ALIAS DECLARATION
case ts.SyntaxKind.TypeAliasDeclaration: {
/**
* STEP 4.a and 4.b could be combined,
* 4.a is to demonstrate resolveType
*/
switch (node.type.kind) {
// STEP 4.a : IF RIGHT HAND SIDE IS TYPE REFERENCE
// EXAMPLE : type foo = bar;
case ts.SyntaxKind.TypeReference: {
/**
* I would recommend use https://ts-ast-viewer.com/ for better understanding
*
* Write code:
* type foo = bar;
*
* See what is TypeReference of foo.
* It may be declared same file OR other file.
* That is what we are passing to resolveType
*/
const typeValues = resolveType(node.type);
typeDeclarationMemory = {
...typeDeclarationMemory,
[node.name.escapedText]: [...typeValues]
};
break;
}
/**
* STEP 4.b : TO STORE DIRECTLY, WE NEED TO EVALUATE INTERNALLY
*
* IT MAY BE UnionType, Keyword, LiteralType, IntersectionType
*
* EXAMPLE:
* type foo = 'a' | 'b';
* type bar = 'a' & 'b';
* type baz = external | 'b'; extractValuesOfTypes would include resolveImport in this case.
* */
default: {
const typeValues = extractValuesOfTypes(node.type);
typeDeclarationMemory = {
...typeDeclarationMemory,
[node.name.escapedText]: [...typeValues]
};
}
}
break;
} // END OF TYPE ALIAS DECLARATION
// STEP 5 : IF INTERFACE DECLARATION
case ts.SyntaxKind.InterfaceDeclaration: {
const interfaceName = node.name.escapedText;
let currentInterfaceInfo = [];
// if it extend any other interfaces
if (node.heritageClauses) {
/**
* for each extended interface, include in current interface
* currentInterfaceInfo = [...currentInterfaceInfo, extendedInterfaceInfo]
*/
}
// ITERATE THROUGH all TYPES in interface
node.members.forEach(
member => {
// TREAT EACH ONE AS PROP
let prop = {
name: member.name.escapedText,
values: [],
comments: [],
};
switch (member.type.kind) {
/**
* IT IS SAME PROBLEM AS LINE NO. 93
* BUT NOW TINY WILL STORE THOSE TYPES IN prop[values]
* AS WELL AS comments
*/
}
// PUSH prop IN current interface info
currentInterfaceInfo = [...currentInterfaceInfo, prop];
}
);
// INCLUDE Interface in memory
interfaceDeclarationMemory = {
...interfaceDeclarationMemory,
[interfaceName]: [...currentInterfaceInfo]
};
break;
} // END OF INTERFACE DECLARATION
// STEP 6 : CHECK IF THIS IS GOAL
case ts.SyntaxKind.VariableStatement: {
if (node.declarationList && node.declarationList.declarations) {
const goalNode = node.declarationList.declarations.find(
declarationNode => declarationNode.name.escapedText === goal
);
// IF GOAL NODE FOUND
if (goalNode && goalNode.type) {
// CHECK IN TYPE'S ARGUMENTS
if (goalNode.typeArguments.length) {
goalNode.typeArguments.forEach(
argument => {
const argumentName = argument.typeName.escapedText;
if (has(interfaceDeclarationMemory, argumentName)) {
allProps = [...allProps, interfaceDeclarationMemory[argumentName]];
}
}
);
// FINALLY TINY WILL PROVIDE allProps
return allProps;
} else {
// NO TYPE'S ARGUMENTS
}
} else {
// NO TYPE'S DECLARATION
}
}
break;
}
}
};
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment