Created
March 25, 2020 13:10
-
-
Save Marvin9/193161674f938b016dce02f8582cc4ab to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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