relative path fixes for refactoring (#2389)

Co-authored-by: HeeJae Chang <hechang@microsoft.com>
This commit is contained in:
Bill Schnurr 2021-10-06 15:24:36 -07:00 committed by GitHub
parent 2315e046c6
commit d5878534fc
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 623 additions and 36 deletions

View File

@ -44,6 +44,7 @@ import * as StringUtils from '../common/stringUtils';
import { isIdentifierChar, isIdentifierStartChar } from '../parser/characters';
import { PyrightFileSystem } from '../pyrightFileSystem';
import { ImplicitImport, ImportResult, ImportType } from './importResult';
import { getDirectoryLeadingDotsPointsTo } from './importStatementUtils';
import { ImportPath, ParentDirectoryCache } from './parentDirectoryCache';
import * as PythonPathUtils from './pythonPathUtils';
import { getPyTypedInfo, PyTypedInfo } from './pyTypedUtils';
@ -1755,18 +1756,18 @@ export class ImportResolver {
importFailureInfo.push('Attempting to resolve relative import');
// Determine which search path this file is part of.
let curDir = getDirectoryPath(sourceFilePath);
for (let i = 1; i < moduleDescriptor.leadingDots; i++) {
if (curDir === '') {
importFailureInfo.push(`Invalid relative path '${importName}'`);
return undefined;
}
curDir = getDirectoryPath(curDir);
const directory = getDirectoryLeadingDotsPointsTo(
getDirectoryPath(sourceFilePath),
moduleDescriptor.leadingDots
);
if (!directory) {
importFailureInfo.push(`Invalid relative path '${importName}'`);
return undefined;
}
// Now try to match the module parts from the current directory location.
const absImport = this.resolveAbsoluteImport(
curDir,
directory,
execEnv,
moduleDescriptor,
importName,
@ -1784,16 +1785,16 @@ export class ImportResolver {
suggestions: Set<string>
) {
// Determine which search path this file is part of.
let curDir = getDirectoryPath(sourceFilePath);
for (let i = 1; i < moduleDescriptor.leadingDots; i++) {
if (curDir === '') {
return;
}
curDir = getDirectoryPath(curDir);
const directory = getDirectoryLeadingDotsPointsTo(
getDirectoryPath(sourceFilePath),
moduleDescriptor.leadingDots
);
if (!directory) {
return;
}
// Now try to match the module parts from the current directory location.
this._getCompletionSuggestionsAbsolute(sourceFilePath, execEnv, curDir, moduleDescriptor, suggestions);
this._getCompletionSuggestionsAbsolute(sourceFilePath, execEnv, directory, moduleDescriptor, suggestions);
}
private _getFilesInDirectory(dirPath: string): string[] {

View File

@ -684,24 +684,35 @@ export function getTextRangeForImportNameDeletion(
return editSpan;
}
export function getRelativeModuleName(fs: FileSystem, sourcePath: string, targetPath: string) {
export function getRelativeModuleName(
fs: FileSystem,
sourcePath: string,
targetPath: string,
ignoreFolderStructure = false,
sourceIsFile?: boolean
) {
let srcPath = sourcePath;
const inputIsFile = isFile(fs, sourcePath);
if (inputIsFile) {
sourceIsFile = sourceIsFile !== undefined ? sourceIsFile : isFile(fs, sourcePath);
if (sourceIsFile) {
srcPath = getDirectoryPath(sourcePath);
}
let symbolName: string | undefined;
let destPath = targetPath;
if (inputIsFile) {
if (sourceIsFile) {
destPath = getDirectoryPath(targetPath);
const fileName = stripFileExtension(getFileName(targetPath));
if (fileName === '__init__') {
if (fileName !== '__init__') {
// ex) src: a.py, dest: b.py -> ".b" will be returned.
symbolName = fileName;
} else if (ignoreFolderStructure) {
// ex) src: nested1/nested2/__init__.py, dest: nested1/__init__.py -> "...nested1" will be returned
// like how it would return for sibling folder.
//
// if folder structure is not ignored, ".." will be returned
symbolName = getFileName(destPath);
destPath = getDirectoryPath(destPath);
} else {
symbolName = fileName;
}
}
@ -731,3 +742,16 @@ export function getRelativeModuleName(fs: FileSystem, sourcePath: string, target
return currentPaths;
}
export function getDirectoryLeadingDotsPointsTo(fromDirectory: string, leadingDots: number) {
let currentDirectory = fromDirectory;
for (let i = 1; i < leadingDots; i++) {
if (currentDirectory === '') {
return undefined;
}
currentDirectory = getDirectoryPath(currentDirectory);
}
return currentDirectory;
}

View File

@ -1601,9 +1601,12 @@ export class Program {
continue;
}
// If module name isn't mentioned in the current file, skip the file.
// If module name isn't mentioned in the current file, skip the file
// except the file that got actually renamed/moved.
// The file that got moved might have relative import paths we need to update.
const filePath = currentFileInfo.sourceFile.getFilePath();
const content = currentFileInfo.sourceFile.getFileContent() ?? '';
if (content.indexOf(renameModuleProvider.symbolName) < 0) {
if (filePath !== path && content.indexOf(renameModuleProvider.symbolName) < 0) {
continue;
}
@ -1613,7 +1616,7 @@ export class Program {
continue;
}
renameModuleProvider.renameReferences(currentFileInfo.sourceFile.getFilePath(), parseResult);
renameModuleProvider.renameReferences(filePath, parseResult);
// This operation can consume significant memory, so check
// for situations where we need to discard the type cache.

View File

@ -8,10 +8,12 @@
import { CancellationToken } from 'vscode-languageserver';
import { getImportInfo } from '../analyzer/analyzerNodeInfo';
import { AliasDeclaration, isAliasDeclaration } from '../analyzer/declaration';
import { createSynthesizedAliasDeclaration } from '../analyzer/declarationUtils';
import { createImportedModuleDescriptor, ImportResolver, ModuleNameAndType } from '../analyzer/importResolver';
import {
getDirectoryLeadingDotsPointsTo,
getImportGroupFromModuleNameAndType,
getRelativeModuleName,
getTextEditsForAutoImportInsertion,
@ -32,6 +34,7 @@ import {
isImportModuleName,
isLastNameOfModuleName,
} from '../analyzer/parseTreeUtils';
import { ParseTreeWalker } from '../analyzer/parseTreeWalker';
import { isStubFile } from '../analyzer/sourceMapper';
import { TypeEvaluator } from '../analyzer/typeEvaluatorTypes';
import { getOrAdd, removeArrayElements } from '../common/collectionUtils';
@ -42,11 +45,13 @@ import { FileEditAction } from '../common/editAction';
import { FileSystem } from '../common/fileSystem';
import {
combinePaths,
getDirectoryPath,
getFileName,
getRelativePathComponentsFromDirectory,
isDirectory,
isFile,
resolvePaths,
stripFileExtension,
} from '../common/pathUtils';
import { convertOffsetToPosition, convertTextRangeToRange } from '../common/positionUtils';
import { doRangesIntersect, extendRange, Range, rangesAreEqual, TextRange } from '../common/textRange';
@ -54,12 +59,15 @@ import {
ImportAsNode,
ImportFromAsNode,
ImportFromNode,
isExpressionNode,
ModuleNameNode,
ModuleNode,
NameNode,
ParseNode,
ParseNodeType,
} from '../parser/parseNodes';
import { ParseResults } from '../parser/parser';
import { DocumentSymbolCollector } from './documentSymbolCollector';
import { CollectionResult, DocumentSymbolCollector } from './documentSymbolCollector';
export class RenameModuleProvider {
static create(
@ -82,7 +90,7 @@ export class RenameModuleProvider {
importResolver.fileSystem.realCasePath(f)
);
// 2 means only last folder name has changed.
// 3 means only last folder name has changed.
if (relativePaths.length !== 3 || relativePaths[1] !== '..' || relativePaths[2] === '..') {
return undefined;
}
@ -150,6 +158,7 @@ export class RenameModuleProvider {
return new RenameModuleProvider(
importResolver.fileSystem,
evaluator,
moduleFilePath,
newModuleFilePath,
moduleName,
newModuleName,
@ -169,6 +178,7 @@ export class RenameModuleProvider {
private constructor(
private _fs: FileSystem,
private _evaluator: TypeEvaluator,
private _moduleFilePath: string,
newModuleFilePath: string,
private _moduleNameAndType: ModuleNameAndType,
private _newModuleNameAndType: ModuleNameAndType,
@ -233,9 +243,87 @@ export class RenameModuleProvider {
/*treatModuleImportAndFromImportSame*/ true
);
const nameRemoved = new Set<NameNode>();
const results = collector.collect();
const nameRemoved = new Set<NameNode>();
// Update module references first.
this._updateModuleReferences(filePath, parseResults, nameRemoved, results);
// If the module file has moved, we need to update all relative paths used in the file to reflect the move.
this._updateRelativeModuleNamePath(filePath, parseResults, nameRemoved, results);
}
private _updateRelativeModuleNamePath(
filePath: string,
parseResults: ParseResults,
nameRemoved: Set<NameNode>,
results: CollectionResult[]
) {
if (filePath !== this._moduleFilePath) {
// We only update relative import paths for the file that has moved.
return;
}
let importStatements: ImportStatements | undefined;
// Filter out module name that is already re-written.
for (const edit of this._getNewRelativeModuleNamesForFileMoved(
filePath,
ModuleNameCollector.collect(parseResults.parseTree).filter(
(m) => !results.some((r) => TextRange.containsRange(m.parent!, r.node))
)
)) {
this._addResultWithTextRange(filePath, edit.moduleName, parseResults, edit.newModuleName);
if (!edit.itemsToMove) {
continue;
}
// This could introduce multiple import statements for same modules with
// different symbols per module name. Unfortunately, there is no easy way to
// prevent it since we can't see changes made by other code until all changes
// are committed. In future, if we support snapshot and diff between snapshots,
// then we can support those complex code generations.
const fromNode = edit.moduleName.parent as ImportFromNode;
// First, delete existing exported symbols from "from import" statement.
for (const importFromAs of edit.itemsToMove) {
this._addImportNameDeletion(filePath, parseResults, nameRemoved, fromNode.imports, importFromAs);
}
importStatements =
importStatements ?? getTopLevelImports(parseResults.parseTree, /*includeImplicitImports*/ false);
// For now, this won't merge absolute and relative path "from import"
// statement.
this._addResultEdits(
this._getTextEditsForNewOrExistingFromImport(
filePath,
fromNode,
parseResults,
nameRemoved,
importStatements,
getRelativeModuleName(
this._fs,
this._newModuleFilePath,
this._newModuleFilePath,
/*ignoreFolderStructure*/ false,
/*sourceIsFile*/ true
),
edit.itemsToMove.map((i) => {
return { name: i.name.value, alias: i.alias?.value };
})
)
);
}
}
private _updateModuleReferences(
filePath: string,
parseResults: ParseResults,
nameRemoved: Set<NameNode>,
results: CollectionResult[]
) {
let importStatements: ImportStatements | undefined;
for (const result of results) {
const nodeFound = result.node;
@ -346,13 +434,20 @@ export class RenameModuleProvider {
if (exportedSymbols.length === 0) {
// We only have sub modules. That means module name actually refers to
// folder name, not module (ex, __init__.py). Since we don't support
// renaming folder, leave things as they are.
// folder name, not module (ex, __init__.py). Folder rename is done by
// different code path.
continue;
}
// Now, we need to split "from import" statement to 2.
// Update module name if needed.
if (fromNode.module.leadingDots > 0) {
for (const edit of this._getNewRelativeModuleNamesForFileMoved(filePath, [fromNode.module])) {
this._addResultWithTextRange(filePath, edit.moduleName, parseResults, edit.newModuleName);
}
}
// First, delete existing exported symbols from "from import" statement.
for (const importFromAs of exportedSymbols) {
this._addImportNameDeletion(filePath, parseResults, nameRemoved, fromNode.imports, importFromAs);
@ -414,6 +509,14 @@ export class RenameModuleProvider {
} else {
// Delete the existing import name including alias.
const importFromAs = nodeFound.parent as ImportFromAsNode;
// Update module name if needed.
if (fromNode.module.leadingDots > 0) {
for (const edit of this._getNewRelativeModuleNamesForFileMoved(filePath, [fromNode.module])) {
this._addResultWithTextRange(filePath, edit.moduleName, parseResults, edit.newModuleName);
}
}
this._addImportNameDeletion(filePath, parseResults, nameRemoved, fromNode.imports, importFromAs);
importStatements =
@ -520,6 +623,107 @@ export class RenameModuleProvider {
}
}
private _getNewRelativeModuleNamesForFileMoved(filePath: string, moduleNames: ModuleNameNode[]) {
if (filePath !== this._moduleFilePath) {
// We only update relative import paths for the file that has moved.
return [];
}
const originalFileName = stripFileExtension(getFileName(filePath));
const originalInit = originalFileName === '__init__';
const originalDirectory = getDirectoryPath(filePath);
const newNames: { moduleName: ModuleNameNode; newModuleName: string; itemsToMove?: ImportFromAsNode[] }[] = [];
for (const moduleName of moduleNames) {
// Filter out all absolute path.
if (moduleName.leadingDots === 0) {
continue;
}
const result = this._getNewModuleNameInfoForFileMoved(moduleName, originalInit, originalDirectory);
if (!result) {
continue;
}
const newModuleName = getRelativeModuleName(
this._fs,
result.src,
result.dest,
/*ignoreFolderStructure*/ false,
/*sourceIsFile*/ true
);
newNames.push({ moduleName, newModuleName, itemsToMove: result.itemsToMove });
}
return newNames;
}
private _getNewModuleNameInfoForFileMoved(
moduleName: ModuleNameNode,
originalInit: boolean,
originalDirectory: string
) {
const importInfo = getImportInfo(moduleName);
if (!importInfo) {
return undefined;
}
let importPath = importInfo.resolvedPaths[importInfo.resolvedPaths.length - 1];
if (!importPath) {
// It is possible for the module name to point to namespace folder (no __init__).
// See whether we can use some heuristic to get importPath
if (moduleName.nameParts.length === 0) {
const directory = getDirectoryLeadingDotsPointsTo(originalDirectory, moduleName.leadingDots);
if (!directory) {
return undefined;
}
// Add fake __init__.py since we know this is namespace folder.
importPath = combinePaths(directory, '__init__.py');
} else {
return undefined;
}
}
// Check whether module is pointing to moved file itself and whether it is __init__
if (this._moduleFilePath !== importPath || !originalInit) {
return { src: this._newModuleFilePath, dest: importPath };
}
// Now, moduleName is pointing to __init__ which point to moved file itself.
// We need to check whether imports of this import statement has
// any implicit submodule imports or not. If there is one, we need to
// either split or leave it as it is.
const exportedSymbols = [];
const subModules = [];
for (const importFromAs of (moduleName.parent as ImportFromNode).imports) {
if (this._isExportedSymbol(importFromAs.name)) {
exportedSymbols.push(importFromAs);
} else {
subModules.push(importFromAs);
}
}
// Point to itself.
if (subModules.length === 0) {
return { src: this._newModuleFilePath, dest: this._newModuleFilePath };
}
// "." is used to point folder location.
if (exportedSymbols.length === 0) {
return { src: this._newModuleFilePath, dest: this._moduleFilePath };
}
// now we need to split, provide split info as well.
return {
src: this._newModuleFilePath,
dest: this._moduleFilePath,
itemsToMove: [...exportedSymbols],
};
}
private _isExportedSymbol(nameNode: NameNode): boolean {
const decls = this._evaluator.getDeclarationsForNameNode(nameNode);
if (!decls) {
@ -531,9 +735,17 @@ export class RenameModuleProvider {
}
private _getNewModuleName(currentFilePath: string, isRelativePath: boolean, isLastPartImportName: boolean) {
const filePath = currentFilePath === this._moduleFilePath ? this._newModuleFilePath : currentFilePath;
// If the existing code was using relative path, try to keep the relative path.
const moduleName = isRelativePath
? getRelativeModuleName(this._fs, currentFilePath, this._newModuleFilePath)
? getRelativeModuleName(
this._fs,
filePath,
this._newModuleFilePath,
isLastPartImportName,
/* sourceIsFile*/ true
)
: this._newModuleName;
if (isLastPartImportName && moduleName.endsWith(this._newSymbolName)) {
@ -751,3 +963,27 @@ export class RenameModuleProvider {
).map((e) => ({ filePath, range: e.range, replacementText: e.replacementText }));
}
}
class ModuleNameCollector extends ParseTreeWalker {
private readonly _result: ModuleNameNode[] = [];
override walk(node: ParseNode): void {
if (isExpressionNode(node)) {
return;
}
super.walk(node);
}
override visitModuleName(node: ModuleNameNode) {
this._result.push(node);
return false;
}
public static collect(root: ModuleNode) {
const collector = new ModuleNameCollector();
collector.walk(root);
return collector._result;
}
}

View File

@ -18,6 +18,7 @@ import {
ImportNameInfo,
} from '../analyzer/importStatementUtils';
import { TextEditAction } from '../common/editAction';
import { combinePaths, getDirectoryPath } from '../common/pathUtils';
import { convertOffsetToPosition } from '../common/positionUtils';
import { rangesAreEqual } from '../common/textRange';
import { Range } from './harness/fourslash/fourSlashTypes';
@ -246,7 +247,7 @@ test('getRelativeModuleName - same file __init__', () => {
//// [|/*src*/|] [|/*dest*/|]
`;
testRelativeModuleName(code, '..common');
testRelativeModuleName(code, '.');
});
test('getRelativeModuleName - same folder', () => {
@ -285,7 +286,7 @@ test('getRelativeModuleName - different folder move up', () => {
testRelativeModuleName(code, '.common.dest');
});
test('getRelativeModuleName - different folder move down __init__', () => {
test('getRelativeModuleName - folder move down __init__ parent folder', () => {
const code = `
// @filename: nest1/nest2/source.py
//// [|/*src*/|]
@ -294,7 +295,31 @@ test('getRelativeModuleName - different folder move down __init__', () => {
//// [|/*dest*/|]
`;
testRelativeModuleName(code, '...nest1');
testRelativeModuleName(code, '..');
});
test('getRelativeModuleName - folder move down __init__ parent folder ignore folder structure', () => {
const code = `
// @filename: nest1/nest2/source.py
//// [|/*src*/|]
// @filename: nest1/__init__.py
//// [|/*dest*/|]
`;
testRelativeModuleName(code, '...nest1', /*ignoreFolderStructure*/ true);
});
test('getRelativeModuleName - different folder move down __init__ sibling folder', () => {
const code = `
// @filename: nest1/nest2/source.py
//// [|/*src*/|]
// @filename: different/__init__.py
//// [|/*dest*/|]
`;
testRelativeModuleName(code, '...different');
});
test('getRelativeModuleName - different folder move up __init__', () => {
@ -321,12 +346,33 @@ test('getRelativeModuleName - root __init__', () => {
testRelativeModuleName(code, '.');
});
function testRelativeModuleName(code: string, expected: string) {
test('getRelativeModuleName over fake file', () => {
const code = `
// @filename: target.py
//// [|/*dest*/|]
`;
const state = parseAndGetTestState(code).state;
const dest = state.getMarkerByName('dest')!.fileName;
assert.strictEqual(
getRelativeModuleName(
state.fs,
combinePaths(getDirectoryPath(dest), 'source.py'),
dest,
/*ignoreFolderStructure*/ false,
/*sourceIsFile*/ true
),
'.target'
);
});
function testRelativeModuleName(code: string, expected: string, ignoreFolderStructure = false) {
const state = parseAndGetTestState(code).state;
const src = state.getMarkerByName('src')!.fileName;
const dest = state.getMarkerByName('dest')!.fileName;
assert.strictEqual(getRelativeModuleName(state.fs, src, dest), expected);
assert.strictEqual(getRelativeModuleName(state.fs, src, dest, ignoreFolderStructure), expected);
}
function testAddition(

View File

@ -0,0 +1,277 @@
/*
* renameModule.misc.test.ts
* Copyright (c) Microsoft Corporation.
* Licensed under the MIT license.
*
* Tests Program.RenameModule
*/
import { combinePaths, getDirectoryPath } from '../common/pathUtils';
import { parseAndGetTestState } from './harness/fourslash/testState';
import { testRenameModule } from './renameModuleTestUtils';
test('relative path for self', () => {
const code = `
// @filename: self.py
//// from .self import foo
//// def foo():
//// [|/*marker*/pass|]
`;
const state = parseAndGetTestState(code).state;
const fileName = state.getMarkerByName('marker').fileName;
testRenameModule(state, fileName, `${combinePaths(getDirectoryPath(fileName), 'moved', 'self.py')}`);
});
test('relative path for self - different name', () => {
const code = `
// @filename: self.py
//// from [|{|"r":".renamedModule"|}.self|] import foo
//// def foo():
//// [|/*marker*/pass|]
`;
const state = parseAndGetTestState(code).state;
const fileName = state.getMarkerByName('marker').fileName;
testRenameModule(state, fileName, `${combinePaths(getDirectoryPath(fileName), 'moved', 'renamedModule.py')}`);
});
test('relative path for self - __init__', () => {
const code = `
// @filename: common/__init__.py
//// from . import foo
//// def foo():
//// [|/*marker*/pass|]
`;
const state = parseAndGetTestState(code).state;
const fileName = state.getMarkerByName('marker').fileName;
testRenameModule(state, fileName, `${combinePaths(getDirectoryPath(fileName), 'moved', '__init__.py')}`);
});
test('relative path for self - __init__ different name', () => {
const code = `
// @filename: common/__init__.py
//// from [|{|"r":".renamedModule"|}.|] import foo
//// def foo():
//// [|/*marker*/pass|]
`;
const state = parseAndGetTestState(code).state;
const fileName = state.getMarkerByName('marker').fileName;
testRenameModule(state, fileName, `${combinePaths(getDirectoryPath(fileName), 'moved', 'renamedModule.py')}`);
});
test('relative path for self - __init__ folder name', () => {
const code = `
// @filename: common/__init__.py
//// from [|{|"r":"."|}..common|] import foo
//// def foo():
//// [|/*marker*/pass|]
`;
const state = parseAndGetTestState(code).state;
const fileName = state.getMarkerByName('marker').fileName;
testRenameModule(state, fileName, `${combinePaths(getDirectoryPath(fileName), 'moved', '__init__.py')}`);
});
test('relative path for self - __init__ different folder name', () => {
const code = `
// @filename: common/__init__.py
//// from [|{|"r":".renamedModule"|}..common|] import foo
//// def foo():
//// [|/*marker*/pass|]
`;
const state = parseAndGetTestState(code).state;
const fileName = state.getMarkerByName('marker').fileName;
testRenameModule(state, fileName, `${combinePaths(getDirectoryPath(fileName), 'moved', 'renamedModule.py')}`);
});
test('relative path for self - import name', () => {
const code = `
// @filename: self.py
//// from . import self
//// [|/*marker*/|]
`;
const state = parseAndGetTestState(code).state;
const fileName = state.getMarkerByName('marker').fileName;
testRenameModule(state, fileName, `${combinePaths(getDirectoryPath(fileName), 'moved', 'self.py')}`);
});
test('relative path for modules', () => {
const code = `
// @filename: self.py
//// from [|{|"r":".."|}.|] import module
//// [|/*marker*/|]
// @filename: module.py
//// # empty
`;
const state = parseAndGetTestState(code).state;
const fileName = state.getMarkerByName('marker').fileName;
testRenameModule(state, fileName, `${combinePaths(getDirectoryPath(fileName), 'moved', 'self.py')}`);
});
test('relative path to self with multiple import names', () => {
const code = `
// @filename: common/self.py
//// [|{|"r":"from . import self!n!"|}|]from [|{|"r":".."|}.|] import [|{|"r":""|}self, |]module, foo
//// [|/*marker*/|]
// @filename: common/module.py
//// # empty
// @filename: common/__init__.py
//// def foo():
//// pass
`;
const state = parseAndGetTestState(code).state;
const fileName = state.getMarkerByName('marker').fileName;
testRenameModule(state, fileName, `${combinePaths(getDirectoryPath(fileName), 'moved', 'self.py')}`);
});
test('relative path to module - move up', () => {
const code = `
// @filename: common/test.py
//// from [|{|"r":"...sub.foo"|}..sub.foo|] import bar
//// [|/*marker*/|]
// @filename: sub/foo.py
//// def bar():
//// pass
// @filename: sub/__init__.py
//// # empty
`;
const state = parseAndGetTestState(code).state;
const fileName = state.getMarkerByName('marker').fileName;
testRenameModule(state, fileName, `${combinePaths(getDirectoryPath(fileName), 'moved', 'self.py')}`);
});
test('relative path to module - move down', () => {
const code = `
// @filename: common/test.py
//// from [|{|"r":".sub.foo"|}..sub.foo|] import bar
//// [|/*marker*/|]
// @filename: sub/foo.py
//// def bar():
//// pass
// @filename: sub/__init__.py
//// # empty
`;
const state = parseAndGetTestState(code).state;
const fileName = state.getMarkerByName('marker').fileName;
testRenameModule(state, fileName, `${combinePaths(getDirectoryPath(fileName), '..', 'self.py')}`);
});
test('relative path to module - sibling', () => {
const code = `
// @filename: common/test.py
//// from ..sub.foo import bar
//// [|/*marker*/|]
// @filename: sub/foo.py
//// def bar():
//// pass
// @filename: sub/__init__.py
//// # empty
`;
const state = parseAndGetTestState(code).state;
const fileName = state.getMarkerByName('marker').fileName;
testRenameModule(state, fileName, `${combinePaths(getDirectoryPath(fileName), '..', 'moved', 'self.py')}`);
});
test('relative path to self __init__ with sub modules and symbol with dots', () => {
const code = `
// @filename: common/__init__.py
//// [|{|"r":"from .self import bar!n!"|}|]from [|{|"r":".."|}.|] import module[|{|"r":""|}, bar|]
//// [|/*marker*/|]
//// def bar():
//// pass
// @filename: common/module.py
//// # empty
`;
const state = parseAndGetTestState(code).state;
const fileName = state.getMarkerByName('marker').fileName;
testRenameModule(state, fileName, `${combinePaths(getDirectoryPath(fileName), 'moved', 'self.py')}`);
});
test('relative path to self __init__ with sub modules and symbol with dotted name', () => {
const code = `
// @filename: common/__init__.py
//// [|{|"r":"from common.moved.self import bar!n!"|}|]from [|{|"r":".."|}..common|] import module[|{|"r":""|}, bar|]
//// [|/*marker*/|]
//// def bar():
//// pass
// @filename: common/module.py
//// # empty
`;
const state = parseAndGetTestState(code).state;
const fileName = state.getMarkerByName('marker').fileName;
testRenameModule(state, fileName, `${combinePaths(getDirectoryPath(fileName), 'moved', 'self.py')}`);
});
test('relative path to self __init__ with sub modules and symbol with dots to __init__', () => {
const code = `
// @filename: common/__init__.py
//// [|{|"r":"from . import bar!n!"|}|]from [|{|"r":".."|}.|] import module[|{|"r":""|}, bar|]
//// [|/*marker*/|]
//// def bar():
//// pass
// @filename: common/module.py
//// # empty
`;
const state = parseAndGetTestState(code).state;
const fileName = state.getMarkerByName('marker').fileName;
testRenameModule(state, fileName, `${combinePaths(getDirectoryPath(fileName), 'moved', '__init__.py')}`);
});
test('relative path to self __init__ with sub modules and symbol with dotted name to __init__', () => {
const code = `
// @filename: common/__init__.py
//// [|{|"r":"from common.moved import bar!n!"|}|]from [|{|"r":".."|}..common|] import module[|{|"r":""|}, bar|]
//// [|/*marker*/|]
//// def bar():
//// pass
// @filename: common/module.py
//// # empty
`;
const state = parseAndGetTestState(code).state;
const fileName = state.getMarkerByName('marker').fileName;
testRenameModule(state, fileName, `${combinePaths(getDirectoryPath(fileName), 'moved', '__init__.py')}`);
});