mirror of
https://github.com/microsoft/pyright.git
synced 2024-09-11 07:55:56 +03:00
relative path fixes for refactoring (#2389)
Co-authored-by: HeeJae Chang <hechang@microsoft.com>
This commit is contained in:
parent
2315e046c6
commit
d5878534fc
@ -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[] {
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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.
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
@ -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(
|
||||
|
@ -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')}`);
|
||||
});
|
Loading…
Reference in New Issue
Block a user