UNPKG

bobflux-gen

Version:

Generator for monkey files in bobflux application.

174 lines (150 loc) 6.69 kB
import * as ts from "typescript"; import * as tsa from './tsAnalyzer'; import * as tsch from './tsCompilerHost'; import * as log from './logger'; import * as fs from 'fs'; import * as pathPlatformDependent from 'path'; const path = pathPlatformDependent.posix; // This works everythere, just use forward slashes export interface IGenerationProject { version: string, dir: string appStateName: string appSourcesDirectory: string appStateFileName: string tsOptions: ts.CompilerOptions relativePath?: string writeFileCallback: (filename: string, b: Buffer) => void } export interface IGenerationProcess { run(): Promise<any>; runRecurse(): Promise<any>; } export const mainStateIndex = 0; export const stateNotFoundError = 'Main state file could not be found.'; export const stateImportKey = 's'; export function resolveBobfluxPrefix(mainState: tsa.IStateData): string { let founds = mainState.heritages.filter(h => h.indexOf('.IState') !== -1 || h.indexOf('.IRouteComponentState') !== -1 || h.indexOf('.IComponentState') !== -1) return (founds.length === 0) ? 'bf' : founds[0].split('.')[0]; } export function resolveState(allStates: tsa.IStateData[], stateName: string): tsa.IStateData { const states = allStates.filter(s => { return s.typeName === stateName }); return states.length === 0 ? null : states[0]; } export function resolveSourceFile(sourceFiles: ts.SourceFile[], fullPath: string): ts.SourceFile { let lowFullPath = fullPath.toLowerCase().replace(/\\/g, '/'); let files = sourceFiles.filter(s => path.relative(s.path.toLowerCase(), lowFullPath) === ''); if (files.length === 0) return null; return files[0]; } export interface ILoadedParams { stateFilePath: string data: tsa.IStateSourceData sourceFiles: ts.SourceFile[] typeChecker: ts.TypeChecker } export function loadSourceFiles(project: IGenerationProject, tsAnalyzer: tsa.ITsAnalyzer, logger: log.ILogger): Promise<ILoadedParams> { return new Promise<ILoadedParams>((f, r) => { logger.info('Generator runs in: ' + project.appSourcesDirectory); logger.info('Application state file is: ' + project.appStateFileName); logger.info('Application state name is: ' + project.appStateName); let program = ts.createProgram([project.appStateFileName], project.tsOptions, tsch.createCompilerHost(project.appSourcesDirectory, logger)); let tc = program.getTypeChecker(); let sourceFiles = program.getSourceFiles(); logger.info('Found source files: ', sourceFiles.map(s => s.path)); let foundSource = resolveSourceFile(sourceFiles, path.join(project.appSourcesDirectory, project.appStateFileName)); if (!foundSource) { logger.error('Source files could not be loaded.'); r(stateNotFoundError); return; } let data = tsAnalyzer.getSourceData(foundSource, tc); let filePath = path.join(path.dirname(foundSource.path), foundSource.fileName); f({ stateFilePath: filePath, data: data, sourceFiles: sourceFiles, typeChecker: tc }); }); } export function isExternalState(type: string, data: tsa.IStateSourceData): boolean { return type.split('.')[0] in data.sourceDeps; } export function getExternalAlias(type: string, data: tsa.IStateSourceData, topLevelImports: { [fullPath: string]: tsa.IImportData } = {} ): tsa.IImportData & { sourceType: string } { const typePath = type.split('.'); const dep = data.sourceDeps[typePath[0]]; if (!dep) return null; if (!topLevelImports[dep.fullPath]) { topLevelImports[dep.fullPath] = Object.assign({}, dep, { prefix: createUnusedAlias(dep.prefix, Object.keys(topLevelImports).map(key => topLevelImports[key])) }); } return Object.assign( {}, dep, { prefix: topLevelImports[dep.fullPath].prefix, sourceType: typePath[0] === dep.prefix ? typePath.slice(1).join('.') : dep.types.find(t => t.targetType === typePath[0]).sourceType } ); } export function getFullType(ts: tsa.ITypeData[], data: tsa.IStateSourceData, stateAlias: string, topLevelImports: { [fullPath: string]: tsa.IImportData } = {} ) { return ts.map(t => { let fieldType = t.name; if (isExternalState(t.name, data)) { let alias = getExternalAlias(t.name, data, topLevelImports); fieldType = `${alias.prefix}.${alias.sourceType}`; } if (isFieldEnumType(fieldType, data.enums) || isCustomType(fieldType, data.customTypes) || data.states.filter(s => s.typeName === t.name).length > 0) fieldType = `${stateAlias}.${fieldType}`; if (t.arguments) fieldType += `<${t.arguments.map(t => getFullType(t, data, stateAlias, topLevelImports)).join(', ')}>`; if (t.isArray) fieldType += '[]'; if (t.indexer) fieldType = `{ [${t.indexer}]: ${fieldType} }`; return fieldType; }).join(' | '); } export function composeCursorKey(...parts: string[]): string { return parts.filter(p => p !== null).join('.'); } export function createFullImports(stateAlias: string, stateFilePath: string, imports: tsa.IImportData[]): string { return `import * as ${stateAlias} from '${stateFilePath}'; ${createImports(imports)} export * from '${stateFilePath}'; `; } export const createUnusedAlias = tsa.createUnusedAlias; function createImports(imports: tsa.IImportData[]): string { let s = imports.map(i => `import * as ${i.prefix} from '${i.relativePath}';`).join('\n'); return s ? s + '\n' : ''; } export function isFieldEnumType(fieldType: string, enums: tsa.IEnumData[]): boolean { return enums.filter(e => e.name === fieldType).length > 0; } export function isCustomType(fieldType: string, customeTypes: tsa.ICustomTypeData[]): boolean { return customeTypes.filter(e => e.name === fieldType).length > 0; } export function isRouteComponentState(...heritages: string[]): boolean { return heritages.filter(h => h.indexOf('IRouteComponentState') !== -1).length > 0; } export function isComponentState(...heritages: string[]): boolean { return heritages.filter(h => h.indexOf('IComponentState') !== -1).length > 0; } export function createAutogeneratedHeader(version: string) { return `// // This source code was auto-generated by bobflux-gen, Version=${version}. // Don't modify this file but re-generate it by bobflux-gen. // Bobflux-gen - https://www.npmjs.com/package/bobflux-gen // `; }