'use strict'; import * as path from 'path'; import * as ts from 'typescript'; import * as dependentFilesUtils from './get-dependent-files'; import {Change, ReplaceChange} from './change'; import {NodeHost, Host} from '../lib/ast-tools'; /** * Rewrites import module of dependent files when the file is moved. * Also, rewrites export module of related index file of the given file. */ export class ModuleResolver { constructor(public oldFilePath: string, public newFilePath: string, public rootPath: string) {} /** * Changes are applied from the bottom of a file to the top. * An array of Change instances are sorted based upon the order, * then apply() method is called sequentially. * * @param changes {Change []} * @param host {Host} * @return Promise after all apply() method of Change class is called * to all Change instances sequentially. */ applySortedChangePromise(changes: Change[], host: Host = NodeHost): Promise { return changes .sort((currentChange, nextChange) => nextChange.order - currentChange.order) .reduce((newChange, change) => newChange.then(() => change.apply(host)), Promise.resolve()); } /** * Assesses the import specifier and determines if it is a relative import. * * @return {boolean} boolean value if the import specifier is a relative import. */ isRelativeImport(importClause: dependentFilesUtils.ModuleImport): boolean { let singleSlash = importClause.specifierText.charAt(0) === '/'; let currentDirSyntax = importClause.specifierText.slice(0, 2) === './'; let parentDirSyntax = importClause.specifierText.slice(0, 3) === '../'; return singleSlash || currentDirSyntax || parentDirSyntax; } /** * Rewrites the import specifiers of all the dependent files (cases for no index file). * * @todo Implement the logic for rewriting imports of the dependent files when the file * being moved has index file in its old path and/or in its new path. * * @return {Promise} */ resolveDependentFiles(): Promise { return dependentFilesUtils.getDependentFiles(this.oldFilePath, this.rootPath) .then((files: dependentFilesUtils.ModuleMap) => { let changes: Change[] = []; let fileBaseName = path.basename(this.oldFilePath, '.ts'); // Filter out the spec file associated with to-be-promoted component unit. let relavantFiles = Object.keys(files).filter((file) => { if (path.extname(path.basename(file, '.ts')) === '.spec') { return path.basename(path.basename(file, '.ts'), '.spec') !== fileBaseName; } else { return true; } }); relavantFiles.forEach(file => { let tempChanges: ReplaceChange[] = files[file] .map(specifier => { let componentName = path.basename(this.oldFilePath, '.ts'); let fileDir = path.dirname(file); let changeText = path.relative(fileDir, path.join(this.newFilePath, componentName)); if (changeText.length > 0 && changeText.charAt(0) !== '.') { changeText = `.${path.sep}${changeText}`; }; let position = specifier.end - specifier.specifierText.length; return new ReplaceChange(file, position - 1, specifier.specifierText, changeText); }); changes = changes.concat(tempChanges); }); return changes; }); } /** * Rewrites the file's own relative imports after it has been moved to new path. * * @return {Promise} */ resolveOwnImports(): Promise { return dependentFilesUtils.createTsSourceFile(this.oldFilePath) .then((tsFile: ts.SourceFile) => dependentFilesUtils.getImportClauses(tsFile)) .then(moduleSpecifiers => { let changes: Change[] = moduleSpecifiers .filter(importClause => this.isRelativeImport(importClause)) .map(specifier => { let specifierText = specifier.specifierText; let moduleAbsolutePath = path.resolve(path.dirname(this.oldFilePath), specifierText); let changeText = path.relative(this.newFilePath, moduleAbsolutePath); if (changeText.length > 0 && changeText.charAt(0) !== '.') { changeText = `.${path.sep}${changeText}`; } let position = specifier.end - specifier.specifierText.length; return new ReplaceChange(this.oldFilePath, position - 1, specifierText, changeText); }); return changes; }); } }