<commit_before>import {ResolveItem} from '../models/ResolveItem';
import {TsDeclaration, TsDefaultDeclaration, TsExportableDeclaration, TsModuleDeclaration} from '../models/TsDeclaration';
import {TsAllFromExport, TsAssignedExport, TsDefaultExport, TsExport, TsFromExport, TsNamedFromExport} from '../models/TsExport';
import {TsDefaultImport, TsExternalModuleImport, TsImport, TsNamedImport, TsNamespaceImport} from '../models/TsImport';
import {TsResolveFile} from '../models/TsResolveFile';
import {injectable} from 'inversify';
import path = require('path');
import vscode = require('vscode');

type LibExports = { [libName: string]: { declarations: ResolveItem[], exports: TsExport[] } };

function isExportable(declaration: TsDeclaration): declaration is TsExportableDeclaration {
    return declaration instanceof TsExportableDeclaration;
}

function isModule(declaration: TsDeclaration): declaration is TsModuleDeclaration {
    return declaration instanceof TsModuleDeclaration;
}

@injectable()
export class ResolveItemFactory {
    public getResolvableItems(files: TsResolveFile[], openDocument: vscode.Uri, filterImports?: TsImport[]): ResolveItem[] {
        let libExports: LibExports = {},
            relativePath = path.parse(openDocument.fsPath);

        // process all the files 
        let fileFilter = (o: TsResolveFile) => (!!o.declarations.length || !!o.exports.length) && o.fsPath !== openDocument.fsPath;

        for (let file of files.filter(fileFilter)) {
            if (!file.isDeclarationFile) {
                this.processLocalFile(libExports, file, relativePath);
            } else {
                if (file.declarations.some(o => isModule(o))) {
                    this.processTypingsFile(libExports, file);
                } else {
                    this.processNodeFile(libExports, file);
                }
            }
        }

        // calculate all export statements (shovel around declarations.)
        Object
            .keys(libExports)
            .sort((k1, k2) => k2.length - k1.length)
            .forEach(key => {
                let lib = libExports[key];
                for (let ex of lib.exports) {
                    if (ex instanceof TsFromExport) {
                        if (!libExports[key] || !ex.from) {
                            continue;
                        }
                        let fromLib = path.resolve(key, ex.from);
                        fromLib = fromLib.substring(fromLib.indexOf(key));

                        if (!libExports[fromLib]) {
                            continue;
                        } else if (ex instanceof TsAllFromExport) {
                            libExports[fromLib].declarations.forEach(o => o.libraryName = key);
                            libExports[key].declarations.push(...libExports[fromLib].declarations);
                            libExports[fromLib].declarations = [];
                        } else if (ex instanceof TsNamedFromExport) {
                            libExports[fromLib].declarations
                                .filter(o => ex.specifiers.some(s => s.specifier === (o.alias || o.declaration.name)))
                                .forEach(o => {
                                    o.libraryName = key;
                                    let spec = ex.specifiers.find(s => s.specifier === (o.alias || o.declaration.name));
                                    if (spec.alias) {
                                        o.alias = spec.alias;
                                    }
                                    libExports[fromLib].declarations.splice(libExports[fromLib].declarations.indexOf(o), 1);
                                    libExports[key].declarations.push(o);
                                });
                        }
                    }
                }
            });

        let items = Object.keys(libExports).reduce((all, key) => all.concat(libExports[key].declarations), []);

        if (filterImports) {
            for (let imp of filterImports) {
                if (imp instanceof TsNamedImport) {
                    items = items.filter(o => o.libraryName !== imp.libraryName || !imp.specifiers.some(s => s.specifier === o.declaration.name));
                } else if (imp instanceof TsNamespaceImport || imp instanceof TsExternalModuleImport) {
                    items = items.filter(o => o.libraryName !== imp.libraryName);
                } else if (imp instanceof TsDefaultImport) {
                    items = items.filter(o => (!(o.declaration instanceof TsDefaultDeclaration) || imp.libraryName !== o.libraryName));
                }
            }
        }

        return items;
    }

    private processLocalFile(libExports: LibExports, file: TsResolveFile, relativeDocument: path.ParsedPath): void {
        let libname = path.relative(relativeDocument.dir, path.format(file.path)).replace('.ts', '');
        if (!libname.startsWith('.')) {
            libname = './' + libname;
        }
<commit_msg>Recursively process export * from and so on... (#26)<commit_after>import {ResolveItem} from '../models/ResolveItem';
import {TsDeclaration, TsDefaultDeclaration, TsExportableDeclaration, TsModuleDeclaration} from '../models/TsDeclaration';
import {TsAllFromExport, TsAssignedExport, TsDefaultExport, TsExport, TsFromExport, TsNamedFromExport} from '../models/TsExport';
import {TsDefaultImport, TsExternalModuleImport, TsImport, TsNamedImport, TsNamespaceImport} from '../models/TsImport';
import {TsResolveFile} from '../models/TsResolveFile';
import {injectable} from 'inversify';
import path = require('path');
import vscode = require('vscode');

type Lib = { declarations: ResolveItem[], exports: TsExport[] };
type LibExports = { [libName: string]: Lib };

function isExportable(declaration: TsDeclaration): declaration is TsExportableDeclaration {
    return declaration instanceof TsExportableDeclaration;
}

function isModule(declaration: TsDeclaration): declaration is TsModuleDeclaration {
    return declaration instanceof TsModuleDeclaration;
}

@injectable()
export class ResolveItemFactory {
    public getResolvableItems(files: TsResolveFile[], openDocument: vscode.Uri, filterImports?: TsImport[]): ResolveItem[] {
        let libExports: LibExports = {},
            relativePath = path.parse(openDocument.fsPath);

        // process all the files 
        let fileFilter = (o: TsResolveFile) => (!!o.declarations.length || !!o.exports.length) && o.fsPath !== openDocument.fsPath;

        for (let file of files.filter(fileFilter)) {
            if (!file.isDeclarationFile) {
                this.processLocalFile(libExports, file, relativePath);
            } else {
                if (file.declarations.some(o => isModule(o))) {
                    this.processTypingsFile(libExports, file);
                } else {
                    this.processNodeFile(libExports, file);
                }
            }
        }

        // calculate all export statements (shovel around declarations.)
        Object
            .keys(libExports)
            .sort((k1, k2) => k1.length - k2.length)
            .forEach(libImportPath => {
                let lib = libExports[libImportPath];
                try {
                    this.processLibrary(lib, libImportPath, libExports);
                } catch (e) {
                    console.log(e);
                }
            });

        let items = Object.keys(libExports).reduce((all, key) => all.concat(libExports[key].declarations), []);

        if (filterImports) {
            for (let imp of filterImports) {
                if (imp instanceof TsNamedImport) {
                    items = items.filter(o => o.libraryName !== imp.libraryName || !imp.specifiers.some(s => s.specifier === o.declaration.name));
                } else if (imp instanceof TsNamespaceImport || imp instanceof TsExternalModuleImport) {
                    items = items.filter(o => o.libraryName !== imp.libraryName);
                } else if (imp instanceof TsDefaultImport) {
                    items = items.filter(o => (!(o.declaration instanceof TsDefaultDeclaration) || imp.libraryName !== o.libraryName));
                }
            }
        }

        return items;
    }
    private processLibrary(lib: Lib, baseLibPath: string, allLibs: LibExports) {
        for (let ex of lib.exports) {
            if (ex instanceof TsFromExport) {
                if (!allLibs[baseLibPath] || !ex.from) {
                    continue;
                }
                let sourceLib = path.resolve(baseLibPath, ex.from);
                sourceLib = sourceLib.substring(sourceLib.indexOf(baseLibPath));

                if (!allLibs[sourceLib]) {
                    continue;
                } else if (ex instanceof TsAllFromExport) {
                    this.processAllFromExports(sourceLib, allLibs, baseLibPath);
                } else if (ex instanceof TsNamedFromExport) {
                    this.processNamedFromExports(ex, sourceLib, allLibs, baseLibPath);
                }
            }
        }
    }
    private processAllFromExports(libPath: string, allLibs: LibExports, baseLibPath: string) {
        if (allLibs[libPath]) {
            let lib = allLibs[libPath];
            lib.declarations.forEach(o => o.libraryName = baseLibPath);
            allLibs[baseLibPath].declarations.push(...allLibs[libPath].declarations);
            lib.declarations = [];
            lib.exports.forEach(ex => {
                if (ex instanceof TsFromExport) {
                    let sourceLib = path.resolve(path.dirname(libPath), ex.from);
                    sourceLib = sourceLib.substring(sourceLib.indexOf(path.dirname(libPath)));
                    if (ex instanceof TsAllFromExport) {
                        this.processAllFromExports(sourceLib, allLibs, baseLibPath);
                    } else if (ex instanceof TsNamedFromExport) {
                        this.processNamedFromExports(ex, sourceLib, allLibs, baseLibPath);
                    }
                }
            });
        }
    }
    private processNamedFromExports(ex: TsNamedFromExport, libPath: string, allLibs: LibExports, baseLibPath: string) {
        let lib = allLibs[libPath];
        if (lib) {
            lib.declarations
                .filter(o => ex.specifiers.some(s => s.specifier === (o.alias || o.declaration.name)))
                .forEach(o => {
                    o.libraryName = baseLibPath;
                    let spec = ex.specifiers.find(s => s.specifier === (o.alias || o.declaration.name));
                    if (spec.alias) {
                        o.alias = spec.alias;
                    }
                    allLibs[libPath].declarations.splice(allLibs[libPath].declarations.indexOf(o), 1);
                    allLibs[baseLibPath].declarations.push(o);
                });
            lib.exports.forEach(ex => {
                if (ex instanceof TsFromExport) {
                    let sourceLib = path.resolve(path.dirname(libPath), ex.from);
                    sourceLib = sourceLib.substring(sourceLib.indexOf(baseLibPath));
                    if (ex instanceof TsAllFromExport) {
                        this.processAllFromExports(sourceLib, allLibs, baseLibPath);
                    } else if (ex instanceof TsNamedFromExport) {
                        this.processNamedFromExports(ex, sourceLib, allLibs, baseLibPath);
                    }
                }
            });
        }
    }
    private processLocalFile(libExports: LibExports, file: TsResolveFile, relativeDocument: path.ParsedPath): void {
        let libname = path.relative(relativeDocument.dir, path.format(file.path)).replace('.ts', '');
        if (!libname.startsWith('.')) {
            libname = './' + libname;
        }

{'fraction_non_alphanumeric': 0.11145930380282883, 'fraction_numerical': 0.0010163462352841535, 'mean_word_length': 1.9249442655437206, 'pattern_counts': {'":': 0, '<': 3, '<?xml version=': 0, '>': 33, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 8, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '4023262', 'n_tokens_mistral': 3130, 'n_tokens_neox': 3013, 'n_words': 629}