[PylanceBot] Pull Pylance with Pyright 1.1.295 (#4675)

Co-authored-by: Bill Schnurr <bschnurr@microsoft.com>
Co-authored-by: HeeJae Chang <hechang@microsoft.com>
Co-authored-by: Erik De Bonte <erikd@microsoft.com>
Co-authored-by: Rich Chiodo <rchiodo@microsoft.com>
This commit is contained in:
PylanceBot 2023-02-22 10:01:56 -08:00 committed by GitHub
parent f5e43ca29f
commit e411e53e85
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
27 changed files with 2742 additions and 3765 deletions

4456
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -22,20 +22,20 @@
"devDependencies": {
"@types/glob": "^7.2.0",
"@types/node": "^17.0.45",
"@types/yargs": "^16.0.4",
"@typescript-eslint/eslint-plugin": "^5.48.0",
"@typescript-eslint/parser": "^5.48.0",
"@types/yargs": "^16.0.5",
"@typescript-eslint/eslint-plugin": "^5.52.0",
"@typescript-eslint/parser": "^5.52.0",
"detect-indent": "^6.1.0",
"eslint": "^8.31.0",
"eslint": "^8.34.0",
"eslint-config-prettier": "^8.6.0",
"eslint-plugin-simple-import-sort": "^7.0.0",
"glob": "^7.2.3",
"jsonc-parser": "^3.2.0",
"lerna": "^5.6.2",
"npm-check-updates": "^16.6.2",
"lerna": "^6.5.1",
"npm-check-updates": "^16.7.4",
"p-queue": "^6.6.2",
"prettier": "2.8.1",
"syncpack": "^5.8.15",
"prettier": "2.8.4",
"syncpack": "^9.0.2",
"typescript": "~4.4.4",
"yargs": "^16.2.0"
}

View File

@ -10,7 +10,7 @@
"license": "MIT",
"dependencies": {
"@iarna/toml": "2.2.5",
"@yarnpkg/fslib": "2.10.0",
"@yarnpkg/fslib": "2.10.1",
"@yarnpkg/libzip": "2.2.4",
"chalk": "^4.1.2",
"chokidar": "^3.5.3",
@ -22,7 +22,7 @@
"typescript-char": "^0.0.0",
"vscode-jsonrpc": "8.1.0-next.6",
"vscode-languageserver": "8.1.0-next.5",
"vscode-languageserver-textdocument": "^1.0.8",
"vscode-languageserver-textdocument": "^1.0.9",
"vscode-languageserver-types": "3.17.2",
"vscode-uri": "^3.0.7"
},
@ -1044,9 +1044,9 @@
"dev": true
},
"node_modules/@yarnpkg/fslib": {
"version": "2.10.0",
"resolved": "https://registry.npmjs.org/@yarnpkg/fslib/-/fslib-2.10.0.tgz",
"integrity": "sha512-eHqvrVlzlhd4owKoLsMRaL4wTGer+r9BXi95u1omHYcAcEQbKnHH3PqYf3j7nxsc8apa09WyA1XNCiiIniFXLg==",
"version": "2.10.1",
"resolved": "https://registry.npmjs.org/@yarnpkg/fslib/-/fslib-2.10.1.tgz",
"integrity": "sha512-pVMLtOYu87N5y5G2lyPNYTY2JbTco99v7nGFI34Blx01Ct9LmoKVOc91vnLOYIMMljKr1c8xs1O2UamRdMG5Pg==",
"dependencies": {
"@yarnpkg/libzip": "^2.2.4",
"tslib": "^1.13.0"
@ -4218,9 +4218,9 @@
"integrity": "sha512-3kkNSCycNKUalSJIrjIptGeY9UTJr1Nk5HT/aT00jjIwiCvIUNbRdK90av2Y3j1Jityot68dBVc3YYdwwH3zOQ=="
},
"node_modules/vscode-languageserver-textdocument": {
"version": "1.0.8",
"resolved": "https://registry.npmjs.org/vscode-languageserver-textdocument/-/vscode-languageserver-textdocument-1.0.8.tgz",
"integrity": "sha512-1bonkGqQs5/fxGT5UchTgjGVnfysL0O8v1AYMBjqTbWQTFn721zaPGDYFkOKtfDgFiSgXM3KwaG3FMGfW4Ed9Q=="
"version": "1.0.9",
"resolved": "https://registry.npmjs.org/vscode-languageserver-textdocument/-/vscode-languageserver-textdocument-1.0.9.tgz",
"integrity": "sha512-NPfHVGFW2/fQEWHspr8x3PXhRgtFbuDZdl7p6ifuN3M7nk2Yjf5POr/NfDBuAiQG88DehDyJ7nGOT+p+edEtbw=="
},
"node_modules/vscode-languageserver-types": {
"version": "3.17.2",
@ -5251,9 +5251,9 @@
"dev": true
},
"@yarnpkg/fslib": {
"version": "2.10.0",
"resolved": "https://registry.npmjs.org/@yarnpkg/fslib/-/fslib-2.10.0.tgz",
"integrity": "sha512-eHqvrVlzlhd4owKoLsMRaL4wTGer+r9BXi95u1omHYcAcEQbKnHH3PqYf3j7nxsc8apa09WyA1XNCiiIniFXLg==",
"version": "2.10.1",
"resolved": "https://registry.npmjs.org/@yarnpkg/fslib/-/fslib-2.10.1.tgz",
"integrity": "sha512-pVMLtOYu87N5y5G2lyPNYTY2JbTco99v7nGFI34Blx01Ct9LmoKVOc91vnLOYIMMljKr1c8xs1O2UamRdMG5Pg==",
"requires": {
"@yarnpkg/libzip": "^2.2.4",
"tslib": "^1.13.0"
@ -7631,9 +7631,9 @@
}
},
"vscode-languageserver-textdocument": {
"version": "1.0.8",
"resolved": "https://registry.npmjs.org/vscode-languageserver-textdocument/-/vscode-languageserver-textdocument-1.0.8.tgz",
"integrity": "sha512-1bonkGqQs5/fxGT5UchTgjGVnfysL0O8v1AYMBjqTbWQTFn721zaPGDYFkOKtfDgFiSgXM3KwaG3FMGfW4Ed9Q=="
"version": "1.0.9",
"resolved": "https://registry.npmjs.org/vscode-languageserver-textdocument/-/vscode-languageserver-textdocument-1.0.9.tgz",
"integrity": "sha512-NPfHVGFW2/fQEWHspr8x3PXhRgtFbuDZdl7p6ifuN3M7nk2Yjf5POr/NfDBuAiQG88DehDyJ7nGOT+p+edEtbw=="
},
"vscode-languageserver-types": {
"version": "3.17.2",

View File

@ -16,7 +16,7 @@
},
"dependencies": {
"@iarna/toml": "2.2.5",
"@yarnpkg/fslib": "2.10.0",
"@yarnpkg/fslib": "2.10.1",
"@yarnpkg/libzip": "2.2.4",
"chalk": "^4.1.2",
"chokidar": "^3.5.3",
@ -28,7 +28,7 @@
"typescript-char": "^0.0.0",
"vscode-jsonrpc": "8.1.0-next.6",
"vscode-languageserver": "8.1.0-next.5",
"vscode-languageserver-textdocument": "^1.0.8",
"vscode-languageserver-textdocument": "^1.0.9",
"vscode-languageserver-types": "3.17.2",
"vscode-uri": "^3.0.7"
},

View File

@ -125,9 +125,9 @@ export class BackgroundAnalysisProgram {
this._reportDiagnosticsForRemovedFiles(diagnostics);
}
addTrackedFile(filePath: string, isThirdPartyImport: boolean) {
this._backgroundAnalysis?.addTrackedFile(filePath, isThirdPartyImport);
this._program.addTrackedFile(filePath, isThirdPartyImport);
addInterimFile(filePath: string) {
this._backgroundAnalysis?.addInterimFile(filePath);
this._program.addInterimFile(filePath);
}
markAllFilesDirty(evenIfContentsAreSame: boolean, indexingNeeded = true) {

View File

@ -11,7 +11,7 @@
import type { Dirent } from 'fs';
import { appendArray, flatten, getMapValues, getOrAdd } from '../common/collectionUtils';
import { ConfigOptions, ExecutionEnvironment } from '../common/configOptions';
import { ConfigOptions, ExecutionEnvironment, matchFileSpecs } from '../common/configOptions';
import { FileSystem } from '../common/fileSystem';
import { Host } from '../common/host';
import { stubsSuffix } from '../common/pathConsts';
@ -199,6 +199,7 @@ export class ImportResolver {
let current = origin;
while (this._shouldWalkUp(current, root, execEnv)) {
const result = this.resolveAbsoluteImport(
sourceFilePath,
current,
execEnv,
moduleDescriptor,
@ -243,6 +244,7 @@ export class ImportResolver {
moduleDescriptor: ImportedModuleDescriptor,
importFailureInfo: string[]
) {
const fromUserFile = matchFileSpecs(this._configOptions, sourceFilePath);
const notFoundResult: ImportResult = {
importName,
isRelative: false,
@ -279,7 +281,12 @@ export class ImportResolver {
}
} else {
// Is it already cached?
const cachedResults = this._lookUpResultsInCache(execEnv, importName, moduleDescriptor.importedSymbols);
const cachedResults = this._lookUpResultsInCache(
execEnv,
importName,
moduleDescriptor.importedSymbols,
fromUserFile
);
if (cachedResults) {
// In most cases, we can simply return a cached entry. However, there are cases
@ -315,11 +322,23 @@ export class ImportResolver {
) || notFoundResult;
}
return this.addResultsToCache(execEnv, importName, bestImport, moduleDescriptor.importedSymbols);
return this.addResultsToCache(
execEnv,
importName,
bestImport,
moduleDescriptor.importedSymbols,
fromUserFile
);
}
}
return this.addResultsToCache(execEnv, importName, notFoundResult, /* importedSymbols */ undefined);
return this.addResultsToCache(
execEnv,
importName,
notFoundResult,
/* importedSymbols */ undefined,
fromUserFile
);
}
getCompletionSuggestions(
@ -879,10 +898,11 @@ export class ImportResolver {
execEnv: ExecutionEnvironment,
importName: string,
importResult: ImportResult,
importedSymbols: string[] | undefined
importedSymbols: string[] | undefined,
fromUserFile: boolean
) {
getOrAdd(this._cachedImportResults, execEnv.root, () => new Map<string, ImportResult>()).set(
importName,
this._getCacheKey(importName, fromUserFile),
importResult
);
@ -892,6 +912,7 @@ export class ImportResolver {
// Follows import resolution algorithm defined in PEP-420:
// https://www.python.org/dev/peps/pep-0420/
protected resolveAbsoluteImport(
sourceFilePath: string | undefined,
rootPath: string,
execEnv: ExecutionEnvironment,
moduleDescriptor: ImportedModuleDescriptor,
@ -908,6 +929,7 @@ export class ImportResolver {
// their stubs separately from their package implementation by appending the string
// '-stubs' to its top - level directory name. We'll look there first.
const importResult = this._resolveAbsoluteImport(
sourceFilePath,
rootPath,
execEnv,
moduleDescriptor,
@ -931,6 +953,7 @@ export class ImportResolver {
}
return this._resolveAbsoluteImport(
sourceFilePath,
rootPath,
execEnv,
moduleDescriptor,
@ -963,6 +986,7 @@ export class ImportResolver {
}
private _resolveAbsoluteImport(
sourceFilePath: string | undefined,
rootPath: string,
execEnv: ExecutionEnvironment,
moduleDescriptor: ImportedModuleDescriptor,
@ -1137,6 +1161,18 @@ export class ImportResolver {
importFound = resolvedPaths.length >= moduleDescriptor.nameParts.length;
}
// Set import type based on if import is in the excluded list or not
let importType = ImportType.Local;
if (importFound && sourceFilePath && !isNamespacePackage && resolvedPaths.length > 0) {
// Check the resolved path. If it's for an included file or the importing
// file is also excluded, treat as a local import.
importType =
!matchFileSpecs(this._configOptions, sourceFilePath, true) ||
matchFileSpecs(this._configOptions, resolvedPaths[resolvedPaths.length - 1], true)
? ImportType.Local
: ImportType.ThirdParty;
}
return {
importName,
isRelative: false,
@ -1146,7 +1182,7 @@ export class ImportResolver {
isImportFound: importFound,
isPartlyResolved,
importFailureInfo,
importType: ImportType.Local,
importType: importType,
resolvedPaths,
searchPath: rootPath,
isStubFile,
@ -1197,17 +1233,22 @@ export class ImportResolver {
return undefined;
}
private _getCacheKey(importName: string, fromUserFile: boolean) {
return `${importName}-${fromUserFile}`;
}
private _lookUpResultsInCache(
execEnv: ExecutionEnvironment,
importName: string,
importedSymbols: string[] | undefined
importedSymbols: string[] | undefined,
fromUserFile: boolean
) {
const cacheForExecEnv = this._cachedImportResults.get(execEnv.root);
if (!cacheForExecEnv) {
return undefined;
}
const cachedEntry = cacheForExecEnv.get(importName);
const cachedEntry = cacheForExecEnv.get(this._getCacheKey(importName, fromUserFile));
if (!cachedEntry) {
return undefined;
}
@ -1315,6 +1356,7 @@ export class ImportResolver {
if (allowPyi && this._configOptions.stubPath) {
importFailureInfo.push(`Looking in stubPath '${this._configOptions.stubPath}'`);
const typingsImport = this.resolveAbsoluteImport(
sourceFilePath,
this._configOptions.stubPath,
execEnv,
moduleDescriptor,
@ -1356,6 +1398,7 @@ export class ImportResolver {
importFailureInfo.push(`Looking in root directory of execution environment ` + `'${execEnv.root}'`);
localImport = this.resolveAbsoluteImport(
sourceFilePath,
execEnv.root,
execEnv,
moduleDescriptor,
@ -1373,6 +1416,7 @@ export class ImportResolver {
for (const extraPath of execEnv.extraPaths) {
importFailureInfo.push(`Looking in extraPath '${extraPath}'`);
localImport = this.resolveAbsoluteImport(
sourceFilePath,
extraPath,
execEnv,
moduleDescriptor,
@ -1394,6 +1438,7 @@ export class ImportResolver {
importFailureInfo.push(`Looking in python search path '${searchPath}'`);
const thirdPartyImport = this.resolveAbsoluteImport(
sourceFilePath,
searchPath,
execEnv,
moduleDescriptor,
@ -1504,6 +1549,9 @@ export class ImportResolver {
if (bestImportSoFar.importType === ImportType.Local && !bestImportSoFar.isNamespacePackage) {
return bestImportSoFar;
}
if (newImport.importType === ImportType.Local && !newImport.isNamespacePackage) {
return newImport;
}
// If both are namespace imports, select the one that resolves the symbols.
if (
@ -1607,6 +1655,7 @@ export class ImportResolver {
for (const typeshedPath of typeshedPaths) {
if (this.dirExistsCached(typeshedPath)) {
const importInfo = this.resolveAbsoluteImport(
undefined,
typeshedPath,
execEnv,
moduleDescriptor,
@ -1975,6 +2024,7 @@ export class ImportResolver {
// Now try to match the module parts from the current directory location.
const absImport = this.resolveAbsoluteImport(
sourceFilePath,
directory,
execEnv,
moduleDescriptor,
@ -1989,6 +2039,7 @@ export class ImportResolver {
// the same folder for the real module. Otherwise, it will
// error out on runtime.
absImport.nonStubImportResult = this.resolveAbsoluteImport(
sourceFilePath,
directory,
execEnv,
moduleDescriptor,

View File

@ -51,6 +51,7 @@ import {
getEmptyRange,
Position,
Range,
TextRange,
} from '../common/textRange';
import { Duration, timingStats } from '../common/timing';
import {
@ -73,7 +74,7 @@ import { DocumentSymbolCollector, DocumentSymbolCollectorUseCase } from '../lang
import { IndexOptions, IndexResults, WorkspaceSymbolCallback } from '../languageService/documentSymbolProvider';
import { HoverResults } from '../languageService/hoverProvider';
import { ImportAdder } from '../languageService/importAdder';
import { getNewlineIndentation, reindentSpan } from '../languageService/indentationUtils';
import { getModuleStatementIndentation, reindentSpan } from '../languageService/indentationUtils';
import { getInsertionPointForSymbolUnderModule } from '../languageService/insertionPointUtils';
import { ReferenceCallback, ReferencesResult } from '../languageService/referencesProvider';
import { RenameModuleProvider } from '../languageService/renameModuleProvider';
@ -88,7 +89,7 @@ import { Declaration } from './declaration';
import { getNameFromDeclaration } from './declarationUtils';
import { ImportResolver } from './importResolver';
import { ImportResult, ImportType } from './importResult';
import { findNodeByOffset, getDocString, getFullStatementRange, isBlankLine } from './parseTreeUtils';
import { findNodeByOffset, getDocString, isBlankLine } from './parseTreeUtils';
import { Scope } from './scope';
import { getScopeForNode } from './scopeUtils';
import { IPythonMode, parseFile, SourceFile } from './sourceFile';
@ -308,6 +309,16 @@ export class Program {
});
}
addInterimFile(filePath: string): SourceFileInfo {
// Double check not already there.
let fileInfo = this.getSourceFileInfo(filePath);
if (!fileInfo) {
fileInfo = this._createInterimFileInfo(filePath);
this._addToSourceFileListAndMap(fileInfo);
}
return fileInfo;
}
addTrackedFile(filePath: string, isThirdPartyImport = false, isInPyTypedPackage = false): SourceFile {
let sourceFileInfo = this.getSourceFileInfo(filePath);
const importName = this._getImportNameForFile(filePath);
@ -394,11 +405,6 @@ export class Program {
}
sourceFileInfo.sourceFile.setClientVersion(version, contents);
// Tell any extensions that this source file changed.
Extensions.getProgramExtensions(filePath).forEach((e) =>
e.sourceFileChanged ? e.sourceFileChanged(sourceFileInfo!) : undefined
);
}
getChainedFilePath(filePath: string): string | undefined {
@ -505,8 +511,8 @@ export class Program {
return this._sourceFileList.filter((s) => isUserCode(s)).length;
}
getTracked(): SourceFileInfo[] {
return this._sourceFileList.filter((s) => s.isTracked);
getUserFiles(): SourceFileInfo[] {
return this._sourceFileList.filter((s) => isUserCode(s));
}
getOpened(): SourceFileInfo[] {
@ -572,13 +578,13 @@ export class Program {
return this._sourceFileMap.get(normalizePathCase(this._fs, filePath));
}
getBoundSourceFileInfo(filePath: string): SourceFileInfo | undefined {
getBoundSourceFileInfo(filePath: string, content?: string, force?: boolean): SourceFileInfo | undefined {
const sourceFileInfo = this.getSourceFileInfo(filePath);
if (!sourceFileInfo) {
return undefined;
}
this._bindFile(sourceFileInfo);
this._bindFile(sourceFileInfo, content, force);
return sourceFileInfo;
}
@ -896,8 +902,7 @@ export class Program {
let shadowFileInfo = this.getSourceFileInfo(shadowImplPath);
if (!shadowFileInfo) {
shadowFileInfo = this._createInterimFileInfo(shadowImplPath);
this._addToSourceFileListAndMap(shadowFileInfo);
shadowFileInfo = this.addInterimFile(shadowImplPath);
}
if (!shadowFileInfo.shadows.includes(stubFile)) {
@ -969,8 +974,8 @@ export class Program {
return this._evaluator;
}
private _parseFile(fileToParse: SourceFileInfo, content?: string) {
if (!this._isFileNeeded(fileToParse) || !fileToParse.sourceFile.isParseRequired()) {
private _parseFile(fileToParse: SourceFileInfo, content?: string, force?: boolean) {
if (!force && (!this._isFileNeeded(fileToParse) || !fileToParse.sourceFile.isParseRequired())) {
return;
}
@ -994,12 +999,12 @@ export class Program {
// Binds the specified file and all of its dependencies, recursively. If
// it runs out of time, it returns true. If it completes, it returns false.
private _bindFile(fileToAnalyze: SourceFileInfo, content?: string): void {
if (!this._isFileNeeded(fileToAnalyze) || !fileToAnalyze.sourceFile.isBindingRequired()) {
private _bindFile(fileToAnalyze: SourceFileInfo, content?: string, force?: boolean): void {
if (!force && (!this._isFileNeeded(fileToAnalyze) || !fileToAnalyze.sourceFile.isBindingRequired())) {
return;
}
this._parseFile(fileToAnalyze, content);
this._parseFile(fileToAnalyze, content, force);
const getScopeIfAvailable = (fileInfo: SourceFileInfo | undefined) => {
if (!fileInfo || fileInfo === fileToAnalyze) {
@ -2021,26 +2026,6 @@ export class Program {
}
moveSymbolAtPosition(
filePath: string,
newFilePath: string,
position: Position,
options: { importFormat: ImportFormat },
token?: CancellationToken
): FileEditActions | undefined {
if (CancellationToken.is(options)) {
return this._moveSymbolAtPosition(
filePath,
newFilePath,
position,
{ importFormat: ImportFormat.Absolute },
options
);
}
return this._moveSymbolAtPosition(filePath, newFilePath, position, options, token!);
}
private _moveSymbolAtPosition(
filePath: string,
newFilePath: string,
position: Position,
@ -2083,7 +2068,7 @@ export class Program {
}
// If this isn't a name node, there are no references to be found.
if (node.nodeType !== ParseNodeType.Name) {
if (node.nodeType !== ParseNodeType.Name || !RenameModuleProvider.canMoveSymbol(this._evaluator!, node)) {
return undefined;
}
@ -2120,17 +2105,15 @@ export class Program {
return undefined;
}
const symbolRange = RenameModuleProvider.getSymbolTextRange(parseResults, sourceDecl);
const importAdder = new ImportAdder(this._configOptions, this._importResolver, this._evaluator!);
const collectedimports = importAdder.collectImportsForSymbolsUsed(parseResults, sourceDecl.node, token);
const collectedimports = importAdder.collectImportsForSymbolsUsed(parseResults, symbolRange, token);
let insertionPoint: number | undefined = 0;
let insertionIndentation = 0;
const newFileParseResults = newFileInfo?.sourceFile.getParseResults();
if (newFileParseResults) {
// TODO: Add "insertAfter" option to make sure we insert symbol after that point.
// For example, if collectedImports has symbols from the destination file, we should
// insert after those symbols are defined.
const insertBefore = renameModuleProvider.tryGetFirstSymbolUsage(newFileParseResults);
insertionPoint = getInsertionPointForSymbolUnderModule(
this._evaluator!,
@ -2146,17 +2129,22 @@ export class Program {
return undefined;
}
insertionIndentation = getNewlineIndentation(newFileParseResults, insertionPoint);
insertionIndentation = getModuleStatementIndentation(newFileParseResults);
}
const fileOperations: FileOperations[] = [];
const reindentResult = reindentSpan(parseResults, symbolRange, insertionIndentation);
const fullRange = RenameModuleProvider.getSymbolFullStatementTextRange(parseResults, sourceDecl);
renameModuleProvider.textEditTracker.addEdit(
filePath,
getFullStatementRange(sourceDecl.node, parseResults, { includeTrailingBlankLines: true }),
convertTextRangeToRange(
TextRange.combine([reindentResult.originalSpan, fullRange])!,
parseResults.tokenizerOutput.lines
),
''
);
let codeSnippetToInsert = reindentSpan(parseResults, sourceDecl.node, insertionIndentation);
const fileOperations: FileOperations[] = [];
let codeSnippetToInsert = reindentResult.text;
if (newFileParseResults) {
// TODO: We need to "add import" statement for symbols defined in the destination file
// if we couldn't find insertion point where all constraints are met.
@ -2193,6 +2181,7 @@ export class Program {
const insertAddEdits = importAdder.applyImports(
collectedimports,
newFilePath,
tempParseResults,
insertionPoint,
options.importFormat,
@ -2562,6 +2551,7 @@ export class Program {
this._createNewEvaluator();
this._discardCachedParseResults();
this._parsedFileCount = 0;
Extensions.getProgramExtensions(this.rootPath).forEach((e) => (e.clearCache ? e.clearCache() : null));
}
test_createSourceMapper(execEnv: ExecutionEnvironment, from?: SourceFileInfo) {
@ -2872,8 +2862,7 @@ export class Program {
// Special case for import statement.
// ex) import X.Y
// SourceFile for X might not be in memory since import `X.Y` only brings in Y
stubFileInfo = this._createInterimFileInfo(stubFilePath);
this._addToSourceFileListAndMap(stubFileInfo);
stubFileInfo = this.addInterimFile(stubFilePath);
}
this._addShadowedFile(stubFileInfo, implFilePath);
@ -2885,8 +2874,7 @@ export class Program {
// Special case for import statement.
// ex) import X.Y
// SourceFile for X might not be in memory since import `X.Y` only brings in Y
fileInfo = this._createInterimFileInfo(f);
this._addToSourceFileListAndMap(fileInfo);
fileInfo = this.addInterimFile(f);
// Even though this file is not referenced by anything, make sure
// we have parse tree for doc string.

View File

@ -56,7 +56,7 @@ import {
} from '../common/pathUtils';
import { DocumentRange, Position, Range } from '../common/textRange';
import { timingStats } from '../common/timing';
import { AutoImportOptions } from '../languageService/autoImporter';
import { AutoImportOptions, ImportFormat } from '../languageService/autoImporter';
import { AbbreviationMap, CompletionOptions, CompletionResultsList } from '../languageService/completionProvider';
import { DefinitionFilter } from '../languageService/definitionProvider';
import { WorkspaceSymbolCallback } from '../languageService/documentSymbolProvider';
@ -175,7 +175,7 @@ export class AnalyzerService {
// Create the extensions tied to this program. This is where the mutating 'addTrackedFile' will actually
// mutate the local program and the BG thread one.
Extensions.createProgramExtensions(this._program, { addTrackedFile: this.addTrackedFile.bind(this) });
Extensions.createProgramExtensions(this._program, { addInterimFile: this.addInterimFile.bind(this) });
}
clone(
@ -192,7 +192,7 @@ export class AnalyzerService {
});
// Cloned service will use whatever user files the service currently has.
const userFiles = this.backgroundAnalysisProgram.program.getTracked().map((i) => i.sourceFile.getFilePath());
const userFiles = this.getUserFiles();
service.backgroundAnalysisProgram.setTrackedFiles(userFiles);
service.backgroundAnalysisProgram.markAllFilesDirty(true);
@ -243,7 +243,7 @@ export class AnalyzerService {
return this._options.importResolverFactory!;
}
private get _cancellationProvider() {
get cancellationProvider() {
return this._options.cancellationProvider!;
}
@ -293,6 +293,10 @@ export class AnalyzerService {
return this._program.owns(filePath);
}
getUserFiles() {
return this._program.getUserFiles().map((i) => i.sourceFile.getFilePath());
}
setFileOpened(
path: string,
version: number | null,
@ -344,8 +348,8 @@ export class AnalyzerService {
this._scheduleReanalysis(/* requireTrackedFileUpdate */ false);
}
addTrackedFile(path: string, isThirdPartyImport: boolean) {
this._backgroundAnalysisProgram.addTrackedFile(path, isThirdPartyImport);
addInterimFile(path: string) {
this._backgroundAnalysisProgram.addInterimFile(path);
}
getParseResult(path: string) {
@ -478,6 +482,16 @@ export class AnalyzerService {
return this._program.performQuickAction(filePath, command, args, token);
}
moveSymbolAtPosition(
filePath: string,
newFilePath: string,
position: Position,
options: { importFormat: ImportFormat },
token: CancellationToken
): FileEditActions | undefined {
return this._program.moveSymbolAtPosition(filePath, newFilePath, position, options, token);
}
renameModule(filePath: string, newFilePath: string, token: CancellationToken): FileEditActions | undefined {
return this._program.renameModule(filePath, newFilePath, token);
}
@ -1208,6 +1222,8 @@ export class AnalyzerService {
this._console.info(`Searching for source files`);
fileList = this._getFileNamesFromFileSpecs();
// getFileNamesFromFileSpecs might have updated configOptions, resync options.
this._backgroundAnalysisProgram.setConfigOptions(this._configOptions);
this._backgroundAnalysisProgram.setTrackedFiles(fileList);
this._backgroundAnalysisProgram.markAllFilesDirty(markFilesDirtyUnconditionally);
@ -1257,6 +1273,11 @@ export class AnalyzerService {
if (this._configOptions.autoExcludeVenv) {
if (envMarkers.some((f) => this.fs.existsSync(combinePaths(absolutePath, ...f)))) {
// Save auto exclude paths in the configOptions once we found them.
if (!FileSpec.isInPath(absolutePath, exclude)) {
exclude.push(getFileSpec(this.fs, this._configOptions.projectRoot, `${absolutePath}/**`));
}
this._console.info(`Auto-excluding ${absolutePath}`);
return;
}
@ -1766,7 +1787,7 @@ export class AnalyzerService {
}
// This creates a cancellation source only if it actually gets used.
this._backgroundAnalysisCancellationSource = this._cancellationProvider.createCancellationTokenSource();
this._backgroundAnalysisCancellationSource = this.cancellationProvider.createCancellationTokenSource();
const moreToAnalyze = this._backgroundAnalysisProgram.startAnalysis(
this._backgroundAnalysisCancellationSource.token
);

View File

@ -32,6 +32,7 @@ import { convertLevelToCategory, Diagnostic, DiagnosticCategory } from '../commo
import { DiagnosticRule } from '../common/diagnosticRules';
import { DiagnosticSink, TextRangeDiagnosticSink } from '../common/diagnosticSink';
import { TextEditAction } from '../common/editAction';
import { Extensions } from '../common/extensibility';
import { FileSystem } from '../common/fileSystem';
import { LogTracker } from '../common/logTracker';
import { fromLSPAny } from '../common/lspUtils';
@ -679,6 +680,8 @@ export class SourceFile {
this._indexingNeeded = indexingNeeded;
this._moduleSymbolTable = undefined;
this._cachedIndexResults = undefined;
const filePath = this.getFilePath();
Extensions.getProgramExtensions(filePath).forEach((e) => (e.fileDirty ? e.fileDirty(filePath) : null));
}
markReanalysisRequired(forceRebinding: boolean): void {

View File

@ -21,18 +21,18 @@ import {
LogData,
run,
} from './backgroundThreadBase';
import { OperationCanceledException, throwIfCancellationRequested } from './common/cancellationUtils';
import {
getCancellationTokenId,
OperationCanceledException,
throwIfCancellationRequested,
} from './common/cancellationUtils';
import { ConfigOptions } from './common/configOptions';
import { ConsoleInterface, log, LogLevel } from './common/console';
import * as debug from './common/debug';
import { Diagnostic } from './common/diagnostic';
import { FileDiagnostics } from './common/diagnosticSink';
import { Extensions } from './common/extensibility';
import {
disposeCancellationToken,
getCancellationTokenFromId,
getCancellationTokenId,
} from './common/fileBasedCancellationUtils';
import { disposeCancellationToken, getCancellationTokenFromId } from './common/fileBasedCancellationUtils';
import { FileSystem } from './common/fileSystem';
import { Host, HostKind } from './common/host';
import { LogTracker } from './common/logTracker';
@ -127,8 +127,8 @@ export class BackgroundAnalysisBase {
this.enqueueRequest({ requestType: 'setFileClosed', data: { filePath, isTracked } });
}
addTrackedFile(filePath: string, isThirdPartyImport: boolean) {
this.enqueueRequest({ requestType: 'addTrackedFile', data: { filePath, isThirdPartyImport } });
addInterimFile(filePath: string) {
this.enqueueRequest({ requestType: 'addInterimFile', data: { filePath } });
}
markAllFilesDirty(evenIfContentsAreSame: boolean, indexingNeeded: boolean) {
@ -313,8 +313,7 @@ export abstract class BackgroundAnalysisRunnerBase extends BackgroundThreadBase
// Create the extensions bound to the program for this background thread
Extensions.createProgramExtensions(this._program, {
addTrackedFile: (filePath: string, isThirdPartyImport: boolean) =>
this._program.addTrackedFile(filePath, isThirdPartyImport),
addInterimFile: (filePath: string) => this._program.addInterimFile(filePath),
});
}
@ -448,9 +447,9 @@ export abstract class BackgroundAnalysisRunnerBase extends BackgroundThreadBase
break;
}
case 'addTrackedFile': {
const { filePath, isThirdPartyImport } = msg.data;
this.program.addTrackedFile(filePath, isThirdPartyImport);
case 'addInterimFile': {
const { filePath } = msg.data;
this.program.addInterimFile(filePath);
break;
}
@ -640,7 +639,7 @@ export interface AnalysisRequest {
| 'setImportResolver'
| 'getInlayHints'
| 'shutdown'
| 'addTrackedFile';
| 'addInterimFile';
data: any;
port?: MessagePort | undefined;

View File

@ -60,6 +60,7 @@ export class CommandController implements ServerCommand {
isLongRunningCommand(command: string): boolean {
switch (command) {
case Commands.createTypeStub:
case Commands.restartServer:
return true;
default:

View File

@ -6,7 +6,7 @@
* Helper methods relating to cancellation.
*/
import { AbstractCancellationTokenSource, CancellationTokenSource } from 'vscode-jsonrpc';
import { AbstractCancellationTokenSource, CancellationTokenSource, Emitter, Event } from 'vscode-jsonrpc';
import { CancellationToken, Disposable, LSPErrorCodes, ResponseError } from 'vscode-languageserver';
import { isDebugMode } from './core';
@ -43,8 +43,8 @@ export function throwIfCancellationRequested(token: CancellationToken) {
}
}
export function CancelAfter(...tokens: CancellationToken[]) {
const source = new CancellationTokenSource();
export function CancelAfter(provider: CancellationProvider, ...tokens: CancellationToken[]) {
const source = provider.createCancellationTokenSource();
const disposables: Disposable[] = [];
for (const token of tokens) {
@ -69,3 +69,91 @@ export class DefaultCancellationProvider implements CancellationProvider {
return new CancellationTokenSource();
}
}
export function getCancellationTokenId(token: CancellationToken) {
return token instanceof FileBasedToken ? token.cancellationFilePath : undefined;
}
export class FileBasedToken implements CancellationToken {
protected isCancelled = false;
private _emitter: Emitter<any> | undefined;
constructor(readonly cancellationFilePath: string, private _fs: { statSync(filePath: string): void }) {
// empty
}
cancel() {
if (!this.isCancelled) {
this.isCancelled = true;
if (this._emitter) {
this._emitter.fire(undefined);
this._disposeEmitter();
}
}
}
get isCancellationRequested(): boolean {
if (this.isCancelled) {
return true;
}
if (CancellationThrottle.shouldCheck() && this._pipeExists()) {
// The first time it encounters the cancellation file, it will
// cancel itself and raise a cancellation event.
// In this mode, cancel() might not be called explicitly by
// jsonrpc layer.
this.cancel();
}
return this.isCancelled;
}
get onCancellationRequested(): Event<any> {
if (!this._emitter) {
this._emitter = new Emitter<any>();
}
return this._emitter.event;
}
dispose(): void {
this._disposeEmitter();
}
private _disposeEmitter() {
if (this._emitter) {
this._emitter.dispose();
this._emitter = undefined;
}
}
private _pipeExists(): boolean {
try {
this._fs.statSync(this.cancellationFilePath);
return true;
} catch (e: any) {
return false;
}
}
}
class CancellationThrottle {
private static _lastCheckTimestamp = 0;
static shouldCheck() {
// Throttle cancellation checks to one every 5ms. This value
// was selected through empirical testing. If we call the
// file system more often than this, type analysis performance
// is affected. If we call it less often, performance doesn't
// improve much, but responsiveness suffers.
const minTimeBetweenChecksInMs = 5;
const curTimestamp = Date.now().valueOf();
const timeSinceLastCheck = curTimestamp - this._lastCheckTimestamp;
if (timeSinceLastCheck >= minTimeBetweenChecksInMs) {
this._lastCheckTimestamp = curTimestamp;
return true;
}
return false;
}
}

View File

@ -32,7 +32,8 @@ export interface ProgramExtension {
readonly declarationProviderExtension?: DeclarationProviderExtension;
readonly typeProviderExtension?: TypeProviderExtension;
readonly codeActionExtension?: CodeActionExtension;
sourceFileChanged?: (sourceFileInfo: SourceFileInfo) => void;
fileDirty?: (filePath: string) => void;
clearCache?: () => void;
}
// Readonly wrapper around a Program. Makes sure it doesn't mutate the program.
@ -43,12 +44,12 @@ export interface ProgramView {
console: ConsoleInterface;
getConfigOptions(): ConfigOptions;
owns(file: string): boolean;
getBoundSourceFileInfo(file: string): SourceFileInfo | undefined;
getBoundSourceFileInfo(file: string, content?: string, force?: boolean): SourceFileInfo | undefined;
}
// Mutable wrapper around a program. Allows the FG thread to forward this request to the BG thread
export interface ProgramMutator {
addTrackedFile(file: string, isThirdPartyImport: boolean): void;
addInterimFile(file: string): void;
}
export interface ExtensionFactory {

View File

@ -16,99 +16,20 @@ import {
CancellationSenderStrategy,
CancellationStrategy,
CancellationToken,
Emitter,
Event,
} from 'vscode-languageserver';
import { CancellationProvider, getCancellationFolderName, setCancellationFolderName } from './cancellationUtils';
class CancellationThrottle {
private static _lastCheckTimestamp = 0;
static shouldCheck() {
// Throttle cancellation checks to one every 5ms. This value
// was selected through empirical testing. If we call the
// file system more often than this, type analysis performance
// is affected. If we call it less often, performance doesn't
// improve much, but responsiveness suffers.
const minTimeBetweenChecksInMs = 5;
const curTimestamp = Date.now().valueOf();
const timeSinceLastCheck = curTimestamp - this._lastCheckTimestamp;
if (timeSinceLastCheck >= minTimeBetweenChecksInMs) {
this._lastCheckTimestamp = curTimestamp;
return true;
}
return false;
}
}
class FileBasedToken implements CancellationToken {
protected isCancelled = false;
private _emitter: Emitter<any> | undefined;
constructor(readonly cancellationFilePath: string) {}
cancel() {
if (!this.isCancelled) {
this.isCancelled = true;
if (this._emitter) {
this._emitter.fire(undefined);
this._disposeEmitter();
}
}
}
get isCancellationRequested(): boolean {
if (this.isCancelled) {
return true;
}
if (CancellationThrottle.shouldCheck() && this._pipeExists()) {
// The first time it encounters the cancellation file, it will
// cancel itself and raise a cancellation event.
// In this mode, cancel() might not be called explicitly by
// jsonrpc layer.
this.cancel();
}
return this.isCancelled;
}
get onCancellationRequested(): Event<any> {
if (!this._emitter) {
this._emitter = new Emitter<any>();
}
return this._emitter.event;
}
dispose(): void {
this._disposeEmitter();
}
private _disposeEmitter() {
if (this._emitter) {
this._emitter.dispose();
this._emitter = undefined;
}
}
private _pipeExists(): boolean {
try {
fs.statSync(this.cancellationFilePath);
return true;
} catch (e: any) {
return false;
}
}
}
import {
CancellationProvider,
FileBasedToken,
getCancellationFolderName,
setCancellationFolderName,
} from './cancellationUtils';
class OwningFileToken extends FileBasedToken {
private _disposed = false;
constructor(cancellationFilePath: string) {
super(cancellationFilePath);
super(cancellationFilePath, fs);
}
override cancel() {
@ -157,7 +78,7 @@ class FileBasedCancellationTokenSource implements AbstractCancellationTokenSourc
// Be lazy and create the token only when actually needed.
this._token = this._ownFile
? new OwningFileToken(this._cancellationFilePath)
: new FileBasedToken(this._cancellationFilePath);
: new FileBasedToken(this._cancellationFilePath, fs);
}
return this._token;
}
@ -245,11 +166,7 @@ export function getCancellationTokenFromId(cancellationId: string) {
return CancellationToken.None;
}
return new FileBasedToken(cancellationId);
}
export function getCancellationTokenId(token: CancellationToken) {
return token instanceof FileBasedToken ? token.cancellationFilePath : undefined;
return new FileBasedToken(cancellationId, fs);
}
let cancellationSourceId = 0;

View File

@ -56,6 +56,28 @@ export function convertWorkspaceDocumentEdits(
changeAnnotations: changeAnnotations,
};
// Ordering of documentChanges are important.
// Make sure create operaiton happens before edits
for (const operation of editActions.fileOperations) {
switch (operation.kind) {
case 'create':
workspaceEdit.documentChanges!.push(
CreateFile.create(
convertPathToUri(fs, operation.filePath),
/* options */ undefined,
defaultAnnotationId
)
);
break;
case 'rename':
case 'delete':
break;
default:
assertNever(operation);
}
}
// Text edit's file path must refer to original file paths unless it is a new file just created.
const mapPerFile = createMapFromItems(editActions.edits, (e) => e.filePath);
for (const [key, value] of mapPerFile) {
workspaceEdit.documentChanges!.push(
@ -72,13 +94,6 @@ export function convertWorkspaceDocumentEdits(
for (const operation of editActions.fileOperations) {
switch (operation.kind) {
case 'create':
workspaceEdit.documentChanges!.push(
CreateFile.create(
convertPathToUri(fs, operation.filePath),
/* options */ undefined,
defaultAnnotationId
)
);
break;
case 'rename':
workspaceEdit.documentChanges!.push(

View File

@ -12,13 +12,13 @@
import './common/extensions';
import {
AbstractCancellationTokenSource,
CallHierarchyIncomingCallsParams,
CallHierarchyItem,
CallHierarchyOutgoingCall,
CallHierarchyOutgoingCallsParams,
CallHierarchyPrepareParams,
CancellationToken,
CancellationTokenSource,
CodeAction,
CodeActionParams,
Command,
@ -367,10 +367,10 @@ export abstract class LanguageServerBase implements LanguageServerInterface {
protected _cacheManager: CacheManager;
// We support running only one "find all reference" at a time.
private _pendingFindAllRefsCancellationSource: CancellationTokenSource | undefined;
private _pendingFindAllRefsCancellationSource: AbstractCancellationTokenSource | undefined;
// We support running only one command at a time.
private _pendingCommandCancellationSource: CancellationTokenSource | undefined;
private _pendingCommandCancellationSource: AbstractCancellationTokenSource | undefined;
private _progressReporter: ProgressReporter;
@ -1646,7 +1646,7 @@ export abstract class LanguageServerBase implements LanguageServerInterface {
// created by the LSP library. If it's the latter, we'll create a server-initiated
// progress reporter.
if (reporter.constructor !== nullProgressReporter.constructor) {
return { reporter: reporter, source: CancelAfter(token) };
return { reporter: reporter, source: CancelAfter(this._serverOptions.cancellationProvider, token) };
}
const serverInitiatedReporter = await this._connection.window.createWorkDoneProgress();
@ -1659,7 +1659,7 @@ export abstract class LanguageServerBase implements LanguageServerInterface {
return {
reporter: serverInitiatedReporter,
source: CancelAfter(token, serverInitiatedReporter.token),
source: CancelAfter(this._serverOptions.cancellationProvider, token, serverInitiatedReporter.token),
};
}

View File

@ -82,6 +82,7 @@ export class ImportAdder {
applyImports(
result: ImportData,
filePath: string,
parseResults: ParseResults,
insertionPosition: number,
importFormat: ImportFormat,
@ -89,7 +90,6 @@ export class ImportAdder {
): TextEditAction[] {
throwIfCancellationRequested(token);
const filePath = getFileInfo(parseResults.parseTree).filePath;
const importStatements = getTopLevelImports(parseResults.parseTree);
const importNameInfo = this._getImportNameWithModuleInfo(filePath, result, importFormat);

View File

@ -139,7 +139,18 @@ export function reindentSpan(
previousInfo = info;
}
return texts.join('');
return {
originalSpan: TextRange.combine(tokenInfo)!,
text: texts.join(''),
};
}
export function getModuleStatementIndentation(parseResults: ParseResults) {
if (parseResults.parseTree.statements.length === 0) {
return getNewlineIndentation(parseResults, parseResults.parseTree.length, /* preferDedent */ true);
}
return getNewlineIndentation(parseResults, parseResults.parseTree.statements[0].start, /* preferDedent */ true);
}
function _getIndentation(

View File

@ -31,6 +31,7 @@ import {
import {
getDottedNameWithGivenNodeAsLastName,
getFirstAncestorOrSelfOfKind,
getFullStatementRange,
isFromImportAlias,
isFromImportModuleName,
isFromImportName,
@ -39,8 +40,11 @@ import {
isLastNameOfModuleName,
} from '../analyzer/parseTreeUtils';
import { ParseTreeWalker } from '../analyzer/parseTreeWalker';
import { ScopeType } from '../analyzer/scope';
import { isStubFile } from '../analyzer/sourceMapper';
import { isPrivateName } from '../analyzer/symbolNameUtils';
import { TypeEvaluator } from '../analyzer/typeEvaluatorTypes';
import { TypeCategory } from '../analyzer/types';
import { getOrAdd } from '../common/collectionUtils';
import { ConfigOptions } from '../common/configOptions';
import { assert, assertNever } from '../common/debug';
@ -57,6 +61,7 @@ import {
resolvePaths,
stripFileExtension,
} from '../common/pathUtils';
import { convertRangeToTextRange } from '../common/positionUtils';
import { TextEditTracker } from '../common/textEditUtils';
import { TextRange } from '../common/textRange';
import {
@ -158,6 +163,81 @@ export class RenameModuleProvider {
);
}
static canMoveSymbol(evaluator: TypeEvaluator, node: NameNode): boolean {
if (isPrivateName(node.value)) {
return false;
}
const lookUpResult = evaluator.lookUpSymbolRecursive(node, node.value, /* honorCodeFlow */ false);
if (lookUpResult === undefined || lookUpResult.scope.type !== ScopeType.Module) {
// We only allow moving a symbol at the module level.
return false;
}
// For now, we only supports module level variable, function and class.
const declarations = lookUpResult.symbol.getDeclarations();
if (declarations.length === 0) {
return false;
}
return declarations.every((d) => {
if (!TextRange.containsRange(d.node, node)) {
return false;
}
if (isFunctionDeclaration(d) || isClassDeclaration(d)) {
return true;
}
if (isVariableDeclaration(d)) {
// We only support simple variable assignment.
// ex) a = 1
if (d.typeAliasAnnotation) {
return false;
}
if (d.inferredTypeSource && isExpressionNode(d.inferredTypeSource)) {
const type = evaluator.getType(d.inferredTypeSource);
if (type?.category === TypeCategory.TypeVar) {
return false;
}
}
// This make sure we are not one of these
// ex) a = b = 1
// a, b = 1, 2
if (
d.node.parent?.nodeType !== ParseNodeType.Assignment ||
d.node.parent?.parent?.nodeType !== ParseNodeType.StatementList
) {
return false;
}
if (d.node.start !== d.node.parent.start) {
return false;
}
return true;
}
return false;
});
}
static getSymbolTextRange(parseResults: ParseResults, decl: Declaration): TextRange {
if (isVariableDeclaration(decl)) {
const range = getFullStatementRange(decl.node, parseResults);
return convertRangeToTextRange(range, parseResults.tokenizerOutput.lines) ?? decl.node;
}
return decl.node;
}
static getSymbolFullStatementTextRange(parseResults: ParseResults, decl: Declaration): TextRange {
const range = getFullStatementRange(decl.node, parseResults, { includeTrailingBlankLines: true });
return convertRangeToTextRange(range, parseResults.tokenizerOutput.lines) ?? decl.node;
}
static getRenameModulePath(declarations: Declaration[]) {
// If we have a decl with no node, we will prefer that decl over others.
// The decl with no node is a synthesized alias decl created only for IDE case
@ -307,10 +387,11 @@ export class RenameModuleProvider {
}
}
tryGetFirstSymbolUsage(parseResults: ParseResults) {
tryGetFirstSymbolUsage(parseResults: ParseResults, symbol?: { name: string; decls: Declaration[] }) {
const name = symbol?.name ?? getNameFromDeclaration(this.declarations[0]) ?? '';
const collector = new DocumentSymbolCollector(
[getNameFromDeclaration(this.declarations[0]) || ''],
this.declarations,
[name],
symbol?.decls ?? this.declarations,
this._evaluator!,
this._token,
parseResults.parseTree,
@ -612,19 +693,11 @@ export class RenameModuleProvider {
/* isLastPartImportName */ false
);
const hasAlias = !!importFromAs.alias;
if (isDestination && !hasAlias) {
if (fromNode.imports.length === 1) {
// If we have import statement for the symbol in the destination file,
// we need to remove it.
this._textEditTracker.deleteImportName(parseResults, importFromAs);
return;
}
if (isDestination) {
// If we have import statement for the symbol in the destination file,
// we need to remove it.
// ex) "from module import symbol, another_symbol" to
// "from module import another_symbol" and "from module.changed import symbol"
// Delete the existing import name including alias.
// "from module import another_symbol"
this._textEditTracker.deleteImportName(parseResults, importFromAs);
return;
}

View File

@ -1242,13 +1242,25 @@ export class TestState {
const expected = map[name].definitions;
// If we're going to def from a file, act like it's open.
if (!this.program.getSourceFileInfo(fileName)) {
const file = this.testData.files.find((v) => v.fileName === fileName);
if (file) {
this.program.setFileOpened(fileName, file.version, [{ text: file.content }]);
}
}
const position = this.convertOffsetToPosition(fileName, marker.position);
const actual = this.program.getDefinitionsForPosition(fileName, position, filter, CancellationToken.None);
assert.equal(actual?.length ?? 0, expected.length);
assert.equal(actual?.length ?? 0, expected.length, `No definitions found for marker "${name}"`);
for (const r of expected) {
assert.equal(actual?.filter((d) => this._deepEqual(d, r)).length, 1);
assert.equal(
actual?.filter((d) => this._deepEqual(d, r)).length,
1,
`No match found for ${JSON.stringify(r)} from marker ${name}`
);
}
}
}
@ -1667,7 +1679,7 @@ export class TestState {
}
});
return new Map<string, typeof results[0][1]>(results);
return new Map<string, (typeof results)[0][1]>(results);
}
private _createAnalysisService(

View File

@ -1395,6 +1395,7 @@ function testImportMove(code: string, importFormat = ImportFormat.Absolute) {
const edits = importMover.applyImports(
importData,
dest.fileName,
state.program.getBoundSourceFile(dest.fileName)!.getParseResults()!,
dest.position,
importFormat,

View File

@ -397,7 +397,7 @@ function testIndentation(code: string, indentation: number, expected: string, in
TextRange.fromBounds(range.pos, range.end),
indentation,
indentFirstToken
);
).text;
assert.strictEqual(actual, expected);
}

View File

@ -145,6 +145,66 @@ test('symbol from destination file used', () => {
testFromCode(code);
});
test('insert after all symbols references', () => {
const code = `
// @filename: test.py
//// from moved import MyType
////
//// [|{|"r":""|}def [|/*marker*/foo|](a: MyType) -> None:
//// c: Mapping[str, MyType] = { 'hello', a }|]
// @filename: moved.py
//// [|{|"r":""|}from test import foo
//// |]
//// class MyType:
//// pass[|{|"r":"!n!!n!!n!def foo(a: MyType) -> None:!n! c: Mapping[str, MyType] = { 'hello', a }", "name": "dest"|}|]
////
//// foo()
`;
testFromCode(code);
});
test('insert after all symbols references 2', () => {
const code = `
// @filename: test.py
//// from moved import MyType
////
//// [|{|"r":""|}def [|/*marker*/foo|](a: MyType) -> None:
//// c: Mapping[str, MyType] = { 'hello', a }|]
// @filename: moved.py
//// def __privateFoo():
//// pass
////
//// class MyType:
//// pass[|{|"r":"!n!!n!!n!def foo(a: MyType) -> None:!n! c: Mapping[str, MyType] = { 'hello', a }", "name": "dest"|}|]
`;
testFromCode(code);
});
test('symbol used before all symbol references', () => {
const code = `
// @filename: test.py
//// from moved import MyType
////
//// [|{|"r":""|}def [|/*marker*/foo|](a: MyType) -> None:
//// c: Mapping[str, MyType] = { 'hello', a }|]
// @filename: moved.py
//// [|{|"r":""|}from test import foo[|{|"r":"!n!!n!!n!def foo(a: MyType) -> None:!n! c: Mapping[str, MyType] = { 'hello', a }", "name": "dest"|}|]
//// |]
//// foo()
////
//// class MyType:
//// pass
`;
testFromCode(code);
});
function testFromCode(code: string) {
const state = parseAndGetTestState(code).state;

View File

@ -315,8 +315,8 @@ test('insert to a file with same symbol imported with alias', () => {
//// pass|]
// @filename: moved.py
//// from [|{|"r":"moved"|}test|] import foo as aliasFoo
////
//// [|{|"r":""|}from test import foo as aliasFoo
//// |]
//// aliasFoo()[|{|"r":"!n!!n!def foo():!n! pass", "name": "dest"|}|]
`;
@ -406,6 +406,49 @@ test('original file has import for the symbol with alias', () => {
testFromCode(code);
});
test('move after class', () => {
const code = `
// @filename: test.py
//// [|{|"r":""|}def [|/*marker*/foo|]():
//// pass
//// |]
// @filename: moved.py
//// class A:
//// def foo(self):
//// pass[|{|"r":"!n!!n!!n!def foo():!n! pass", "name": "dest"|}|]
`;
testFromCode(code);
});
test('move variable', () => {
const code = `
// @filename: test.py
//// [|{|"r":""|}[|/*marker*/A|] = 1|]
// @filename: moved.py
//// [|{|"r":"A = 1", "name": "dest"|}|]
`;
testFromCode(code);
});
test('move variable with doc string', () => {
const code = `
// @filename: test.py
//// [|{|"r":""|}[|/*marker*/A|] = 1
//// '''
//// doc string
//// '''|]
// @filename: moved.py
//// [|{|"r":"A = 1!n!'''!n! doc string!n!'''", "name": "dest"|}|]
`;
testFromCode(code);
});
function testFromCode(code: string) {
const state = parseAndGetTestState(code).state;

View File

@ -21,16 +21,7 @@ test('source and destnation file must have same ext', () => {
//// [|/*dest*/|]
`;
const state = parseAndGetTestState(code).state;
const actions = state.program.moveSymbolAtPosition(
state.getMarkerByName('marker').fileName,
state.getMarkerByName('dest').fileName,
state.getPositionRange('marker').start,
{ importFormat: ImportFormat.Absolute },
CancellationToken.None
);
assert(!actions);
testNoMoveFromCode(code);
});
test('source and destnation file can not be same', () => {
@ -39,6 +30,134 @@ test('source and destnation file can not be same', () => {
//// [|/*dest*/|]def [|/*marker*/foo|](): pass
`;
testNoMoveFromCode(code);
});
test('Symbol must be module level symbol', () => {
const code = `
// @filename: test.py
//// class A:
//// def [|/*marker*/foo|](self): pass
// @filename: moved.py
//// [|/*dest*/|]
`;
testNoMoveFromCode(code);
});
test('Import alias can not be moved', () => {
const code = `
// @filename: test.py
//// import [|/*marker*/sys|]
// @filename: moved.py
//// [|/*dest*/|]
`;
testNoMoveFromCode(code);
});
test('Type alias can not be moved', () => {
const code = `
// @filename: test.py
//// from typing import TypeAlias
//// [|/*marker*/TA|]: TypeAlias = int
// @filename: moved.py
//// [|/*dest*/|]
`;
testNoMoveFromCode(code);
});
test('TypeVar can not be moved', () => {
const code = `
// @filename: test.py
//// from typing import TypeVar
//// [|/*marker*/T1|] = TypeVar("T1")
// @filename: moved.py
//// [|/*dest*/|]
`;
testNoMoveFromCode(code);
});
test('tuple unpacking not supported', () => {
const code = `
// @filename: test.py
//// [|/*marker*/a|], b = 1, 2
// @filename: moved.py
//// [|/*dest*/|]
`;
testNoMoveFromCode(code);
});
test('tuple unpacking not supported 2', () => {
const code = `
// @filename: test.py
//// a, [|/*marker*/b|] = 1, 2
// @filename: moved.py
//// [|/*dest*/|]
`;
testNoMoveFromCode(code);
});
test('chained assignment not supported', () => {
const code = `
// @filename: test.py
//// [|/*marker*/a|] = b = 1
// @filename: moved.py
//// [|/*dest*/|]
`;
testNoMoveFromCode(code);
});
test('chained assignment not supported 2', () => {
const code = `
// @filename: test.py
//// a = [|/*marker*/b|] = 1
// @filename: moved.py
//// [|/*dest*/|]
`;
testNoMoveFromCode(code);
});
test('augmented assignment', () => {
const code = `
// @filename: test.py
//// [|/*marker*/a|] += 1
// @filename: moved.py
//// [|/*dest*/|]
`;
testNoMoveFromCode(code);
});
test('augmented assignment 2', () => {
const code = `
// @filename: test.py
//// a = 1
//// [|/*marker*/a|] += 1
// @filename: moved.py
//// [|/*dest*/|]
`;
testNoMoveFromCode(code);
});
function testNoMoveFromCode(code: string) {
const state = parseAndGetTestState(code).state;
const actions = state.program.moveSymbolAtPosition(
@ -49,4 +168,4 @@ test('source and destnation file can not be same', () => {
CancellationToken.None
);
assert(!actions);
});
}

File diff suppressed because it is too large Load Diff

View File

@ -26,7 +26,7 @@
"@types/copy-webpack-plugin": "^10.1.0",
"@types/node": "^17.0.45",
"copy-webpack-plugin": "^10.2.4",
"esbuild-loader": "^2.20.0",
"esbuild-loader": "^3.0.1",
"shx": "^0.3.4",
"ts-loader": "^9.4.2",
"typescript": "~4.4.4",