bobflux-gen
Version:
Generator for monkey files in bobflux application.
174 lines (150 loc) • 6.69 kB
text/typescript
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
//
`;
}