UNPKG

sardines-compile-time-tools

Version:

sardines.compile-time-tools.js is part of the sardines.io project

324 lines (305 loc) 13.8 kB
import * as ts from "typescript" import * as fs from 'fs' const builtInTypes = ['Promise', 'Map', 'Array', 'Set', 'void', 'any', 'null', 'undefined', 'object', 'number', 'string', 'boolean'] export interface IdentifierSyntax { name: string type: ts.SyntaxKind typeStr: string isExport: boolean isAsync: boolean text: string param?: IdentifierSyntax[]|null typeRef?: string[] returnType?: string } export const legalExportTypes = [ ts.SyntaxKind.FunctionExpression, ts.SyntaxKind.FunctionDeclaration, ts.SyntaxKind.ArrowFunction, ts.SyntaxKind.InterfaceDeclaration, ts.SyntaxKind.ObjectLiteralExpression, ts.SyntaxKind.ArrayLiteralExpression, ts.SyntaxKind.PropertyAccessExpression, ts.SyntaxKind.EnumDeclaration, ] export const illegalExportTypes = [ ts.SyntaxKind.ClassDeclaration, ] export function gatherExports(sourceFilePath: string): [Map<string, IdentifierSyntax>, string[], string[], string[]] { const sourceFile = ts.createSourceFile( sourceFilePath, fs.readFileSync(sourceFilePath).toString(), ts.ScriptTarget.ES2015, /*setParentNodes */ true ); const idnetifiers: Map<string, IdentifierSyntax> = new Map<string, IdentifierSyntax>() const referencedTypes: string[] = [] const importedIds: string[] = [] const proxyIds: string[] = [] const storeSyntax = (syntax: IdentifierSyntax) => { if (syntax.name) { if (idnetifiers.has(syntax.name)) { throw `duplicated identifier: ${syntax.name}` } else if (syntax.isExport && illegalExportTypes.indexOf(syntax.type) >= 0) { throw `illegal export type: ${ts.SyntaxKind[syntax.type]} for syntax [${syntax.name}]` } else { idnetifiers.set(syntax.name, syntax!) } } } const storeReferencedTypes = (types: string[]) => { for (let t of types) { if (referencedTypes.indexOf(t) <0) referencedTypes.push(t) } } const storeImportedOrExportedIds = (ids: string[], source:string|null, isImport:boolean = true) => { if (!source) return for (let item of ids) { let line = `${item}|${source}` if (isImport && importedIds.indexOf(line) < 0) importedIds.push(line) else if (!isImport && proxyIds.indexOf(line) < 0) proxyIds.push(line) } } const checkSyntaxType = (node: ts.Node, type: ts.SyntaxKind = ts.SyntaxKind.ExportKeyword):boolean => { if (node.kind === type) return true let result = false if (node.getChildCount() > 0) { for (let child of node.getChildren()) { if (checkSyntaxType(child, type)) { result = true break } } } return result } const printNode = (node: ts.Node, prefix: string = '') => { console.log(prefix, ts.SyntaxKind[node.kind], node.getText()) for (let item of node.getChildren()){ printNode(item, prefix + ' ') } } const getTypeRefInParam = (param: ts.Node): string[] => { const result = [] for (let item of param.getChildren()) { let digIn = true if (item.kind === ts.SyntaxKind.TypeReference) { let text = item.getText() if (text.indexOf('<') < 0 && builtInTypes.indexOf(text) < 0 && text.indexOf('|') < 0) { result.push(text) digIn = false } } if (digIn) { const subResult = getTypeRefInParam(item) for (let t of subResult) { if (result.indexOf(t) < 0) { result.push(t) } } } } return result } const getParamSyntax = (node: ts.Node) => { let paramSyntax = null, pre = null, preBeforePre = null for (let item of node.getChildren()) { if (item.kind === ts.SyntaxKind.CloseParenToken) { if (pre && pre.kind !== ts.SyntaxKind.OpenParenToken && preBeforePre && preBeforePre.kind === ts.SyntaxKind.OpenParenToken) { paramSyntax = pre } break } preBeforePre = pre pre = item } const params = [] if (paramSyntax) { for (let item of (paramSyntax as ts.SyntaxList).getChildren()) { if (item.kind === ts.SyntaxKind.Parameter) { let paramName = null for (let subItem of (item as ts.ParameterDeclaration).getChildren()) { if (subItem.kind === ts.SyntaxKind.Identifier) { paramName = subItem.getText() } } if (paramName) { const paramObj: IdentifierSyntax = { name: paramName, type: item.kind, typeStr:ts.SyntaxKind[item.kind], isExport: false, isAsync: false, text: item.getText() } params.push(paramObj) } } } return params } return null } const getReturnType = (node: ts.Node) => { let preBeforePre = null, pre = null, returnType = null for (let child of node.getChildren()) { if (pre && preBeforePre && preBeforePre.kind === ts.SyntaxKind.ColonToken) if (child.kind === ts.SyntaxKind.EqualsGreaterThanToken || child.kind === ts.SyntaxKind.Block) { returnType = pre.getText() } preBeforePre = pre pre = child } return returnType } const parseParamsIntoSyntax = (node: ts.Node, syntax: IdentifierSyntax) => { syntax.param = getParamSyntax(node) if (syntax.param) { syntax.typeRef = getTypeRefInParam(node) if (syntax.typeRef!.length > 0) storeReferencedTypes(syntax.typeRef!) } syntax.isAsync = checkSyntaxType(node, ts.SyntaxKind.AsyncKeyword) let returnType = getReturnType(node) if (!returnType) returnType = 'any' else if (syntax.isAsync && returnType.indexOf('Promise<') === 0) { returnType = returnType.substr(8) returnType = returnType.substr(0, returnType.length - 1) } syntax.returnType = returnType return syntax } const getNamedImportsOrExports = (node: ts.Node): [string[], string|null] => { const importedOrExportedIds: string[] = [] let source = null, isSource = false for (let item of node.getChildren()) { if (item.kind === ts.SyntaxKind.ImportSpecifier || item.kind === ts.SyntaxKind.ExportSpecifier) { const children = item.getChildren() if (children.length ===1) { importedOrExportedIds.push(children[0].getText()) } else if (children.length === 3 && children[1].kind === ts.SyntaxKind.AsKeyword) { importedOrExportedIds.push(children[0].getText() + ':' + children[2].getText()) } } else if (item.kind === ts.SyntaxKind.FromKeyword) { isSource = true } else if (item.kind === ts.SyntaxKind.StringLiteral && isSource) { source = item.getText() } else { const [subIds, subSource] = getNamedImportsOrExports(item) for (let subId of subIds) { if (importedOrExportedIds.indexOf(subId) < 0) importedOrExportedIds.push(subId) } if (subSource) { if (!source) source = subSource } } } return [importedOrExportedIds, source] } const analyzeNode = (node: ts.Node) => { if (node.kind === ts.SyntaxKind.SourceFile) { ts.forEachChild(node, analyzeNode) return } let name = '', isAsync = false, isExport = checkSyntaxType(node), text = node.getText().replace(/export +/g, ''), syntax: IdentifierSyntax = { name, isAsync, isExport, type: node.kind, typeStr: ts.SyntaxKind[node.kind], text } // DEBUG // if (node.getText().indexOf('StorageType') >=0 && node.kind === ts.SyntaxKind.VariableDeclaration) { // console.log('======================') // console.log('node kind:', ts.SyntaxKind[node.kind]) // printNode(node) // console.log('======================') // console.log('isExport:', isExport) // console.log('======================') // } // DEBUG switch (node.kind) { case ts.SyntaxKind.VariableStatement: for (let item of (node as ts.VariableStatement).declarationList.declarations) { analyzeNode(item) } break case ts.SyntaxKind.ExportDeclaration: case ts.SyntaxKind.ImportDeclaration: let [importedIds, source] = getNamedImportsOrExports(node) storeImportedOrExportedIds(importedIds, source, node.kind === ts.SyntaxKind.ImportDeclaration) break case ts.SyntaxKind.ExportAssignment: // Export default throw 'export default is not supported' case ts.SyntaxKind.InterfaceDeclaration: syntax.name = (node as ts.InterfaceDeclaration).name.text break case ts.SyntaxKind.EnumDeclaration: syntax.name = (node as ts.EnumDeclaration).name.text break case ts.SyntaxKind.ClassDeclaration: syntax.name = (node as ts.ClassDeclaration).name!.text break case ts.SyntaxKind.ArrowFunction: syntax.name = (node as ts.ArrowFunction).name syntax.isExport = checkSyntaxType(node, ts.SyntaxKind.ExportKeyword) parseParamsIntoSyntax(node, syntax) break case ts.SyntaxKind.FunctionDeclaration: case ts.SyntaxKind.FunctionExpression: syntax.name = (node as ts.FunctionDeclaration).name!.text syntax.isExport = checkSyntaxType(node, ts.SyntaxKind.ExportKeyword) parseParamsIntoSyntax(node, syntax) break case ts.SyntaxKind.VariableDeclaration: isExport = checkSyntaxType(node.parent.parent) // check the VariableStatement name = (node as ts.VariableDeclaration).name.getText() let isValueSyntax = false for (let item of (node as ts.VariableDeclaration).getChildren()) { if (isValueSyntax) { // DEBUG // if (node.getText().indexOf('StorageType') >=0 && node.kind === ts.SyntaxKind.VariableDeclaration) { // console.log('======================') // console.log(ts.SyntaxKind[node.kind]) // printNode(node) // console.log('======================') // console.log('isExport:', isExport, ', name:', name, ', itemSyntax:') // console.log('======================') // } // DEBU const sourceId = item.getText() if (item.kind === ts.SyntaxKind.Identifier && idnetifiers.has(sourceId)) { const tmpSyntax = Object.assign({}, idnetifiers.get(sourceId)) tmpSyntax!.name = name tmpSyntax!.isExport = isExport storeSyntax(tmpSyntax) } else if (item.kind !== ts.SyntaxKind.Identifier) { const itemSyntax: IdentifierSyntax = { name, isAsync: false, isExport, type: item.kind, typeStr: ts.SyntaxKind[item.kind], text: sourceId} if (item.kind === ts.SyntaxKind.FunctionExpression || item.kind === ts.SyntaxKind.ArrowFunction) { parseParamsIntoSyntax(item, itemSyntax) } // some variables do not have handler, but could be stored as syntax // console.log('b tmp syntax:', itemSyntax) storeSyntax(itemSyntax) } isValueSyntax = false } else if (item.kind === ts.SyntaxKind.FirstAssignment) { isValueSyntax = true } } break default: break; } storeSyntax(syntax) } analyzeNode(sourceFile) // filter out non-export items const iterator = idnetifiers.keys() let item = iterator.next() while (!item.done) { const idenObj = <IdentifierSyntax>(idnetifiers.get(item.value)) if (!idenObj.isExport || legalExportTypes.indexOf(idenObj.type)<0) { idnetifiers.delete(item.value) } item = iterator.next() } return [idnetifiers, referencedTypes, importedIds, proxyIds] }