Change all file paths to use a new URI class. (#6519)

* Preliminary idea

* First commit

* Refactor all uri based path functions to uriUtils

* Most of real code building

* More tests working

* More progress on the tests

* Rest of typecheck errors

* Move URIs even further to prevent accidental issues

* More tests

* Relative path fixes

* More test fixes and changes to 'startsWith'

* Uri tests all passing

* remove uri tests from path utils tests

* Fix relative paths for partial stub remapping

* Make isEmpty look empty and fix empty check for resolved paths

* Fix module name retrieval issue

* Use map instead of set for URI list

* Another path is empty fix.

* Fix importresolver finding source files for stubs

* Fixup actual defs to match expected format

* Relative path differences again

* Fix some comparisons

* More comparison fixes

* Config of ('.') was using current directory to find module names

* Shortcut empty URI for existence

* Add back ignoring case

* Fix regex matching

* All tests passing

* Fix build after merge

* Fix failing tests

* Fix rootDirectory and python path from interpreter

* Some perf improvements

* Don't bother reparsing in file system

* More tests and some logging output

* Extra data for analyzing slowness

* Fix build problems

* Refactor to not use vscode.URI except for initial parse

* Try without vscode uri validation

* Rework to new perf idea

* Split out class and interface

* All uri tests passing

* Fix other tests

* Cache results for faster perf

* Add memoization decorator

* combinePath remove one layer and make key gen faster

* Move caches local

* Generalize the memoization and optimize zero arg funcs

* Cache more stuff

* slight speedup for cache saving

* slight improvement by just returning the cached item

* Small optimization to skip loop if possible

* Further speedup for similar calls of combinePath

* Make it possible to switch between profiled and not

* Another small optimization

* Cache files in directory

* Small opt for isNativeModuleFileName

* Speed up getting pytyped info

* Fix command line parsing to expand ~
Slight perf improvement for exists cached by not splitting

* Split out instrumenting memoization

* Remove instrumented memoizationin

* Fix comment

* Fix build on ubuntu/mac

* Fix windows specific problems with tests
Fix auto import to work with windows file paths

* Fix command line output

* Try fixing CI path for failing test

* fix output paths in other spots

* More file output locations

* Output more data for failing test

* Put validation back and fix test

* Fix import cycles message to not show URI path

* Remove printing of config options

* More review feedback

* Missed a spot in the review feedback

* Fix build error

* Update packages/pyright-internal/src/analyzer/importResolver.ts

Co-authored-by: Erik De Bonte <erikd@microsoft.com>

* Update packages/pyright-internal/src/analyzer/analyzerFileInfo.ts

Co-authored-by: Erik De Bonte <erikd@microsoft.com>

* Update packages/pyright-internal/src/analyzer/backgroundAnalysisProgram.ts

Co-authored-by: Erik De Bonte <erikd@microsoft.com>

* Review feedback from Erik

* Revert changes to launch.json

* Fixup config options to use same roots as before

* Fix casing for cwd test on windows

* Fix spack differences

* Add back in the instrumentation until figure import resolve cache

* Fixup after merge

* Fix another equals comparison

* Merge error

* Fix after merge

* Fixup after merge

* Keep track of case sensitivity in the URIs

* Fix test failures on windows/mac

* Switch to the faster version

* Remove memoization

* Review feedback for some renames

* Review feedback

* Feedback from Erik, put back memoization for simple stuff

* fix prettier

* Fix typo and cache URI creations

* Add a comment

* Review feedback from Eric

* Fix crash in config options

* Missed a piece of feedback

---------

Co-authored-by: Erik De Bonte <erikd@microsoft.com>
This commit is contained in:
Rich Chiodo 2023-12-07 09:15:39 -08:00 committed by GitHub
parent 07568b047d
commit a5b352d929
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
133 changed files with 5698 additions and 4107 deletions

View File

@ -8,8 +8,8 @@
"editor.formatOnSave": true
},
"editor.codeActionsOnSave": {
"source.fixAll.eslint": true,
"source.organizeImports": true
"source.fixAll.eslint": "explicit",
"source.organizeImports": "explicit"
},
"typescript.tsdk": "node_modules/typescript/lib",
}

View File

@ -16,7 +16,8 @@
"check:eslint": "eslint .",
"fix:eslint": "eslint --fix .",
"check:prettier": "prettier -c .",
"fix:prettier": "prettier --write ."
"fix:prettier": "prettier --write .",
"typecheck": "npx lerna exec --stream --no-bail --ignore=pyright -- tsc --noEmit"
},
"devDependencies": {
"@types/glob": "^7.2.0",

View File

@ -23,7 +23,7 @@
"vscode-languageserver": "8.1.0",
"vscode-languageserver-textdocument": "1.0.10",
"vscode-languageserver-types": "3.17.3",
"vscode-uri": "^3.0.7"
"vscode-uri": "^3.0.8"
},
"devDependencies": {
"@types/command-line-args": "^5.2.0",
@ -3887,9 +3887,9 @@
"integrity": "sha512-SYU4z1dL0PyIMd4Vj8YOqFvHu7Hz/enbWtpfnVbJHU4Nd1YNYx8u0ennumc6h48GQNeOLxmwySmnADouT/AuZA=="
},
"node_modules/vscode-uri": {
"version": "3.0.7",
"resolved": "https://registry.npmjs.org/vscode-uri/-/vscode-uri-3.0.7.tgz",
"integrity": "sha512-eOpPHogvorZRobNqJGhapa0JdwaxpjVvyBp0QIUMRMSf8ZAlqOdEquKuRmw9Qwu0qXtJIWqFtMkmvJjUZmMjVA=="
"version": "3.0.8",
"resolved": "https://registry.npmjs.org/vscode-uri/-/vscode-uri-3.0.8.tgz",
"integrity": "sha512-AyFQ0EVmsOZOlAnxoFOGOq1SQDWAB7C6aqMGS23svWAllfOaxbuFvcT8D1i8z3Gyn8fraVeZNNmN6e9bxxXkKw=="
},
"node_modules/walker": {
"version": "1.0.8",
@ -6943,9 +6943,9 @@
"integrity": "sha512-SYU4z1dL0PyIMd4Vj8YOqFvHu7Hz/enbWtpfnVbJHU4Nd1YNYx8u0ennumc6h48GQNeOLxmwySmnADouT/AuZA=="
},
"vscode-uri": {
"version": "3.0.7",
"resolved": "https://registry.npmjs.org/vscode-uri/-/vscode-uri-3.0.7.tgz",
"integrity": "sha512-eOpPHogvorZRobNqJGhapa0JdwaxpjVvyBp0QIUMRMSf8ZAlqOdEquKuRmw9Qwu0qXtJIWqFtMkmvJjUZmMjVA=="
"version": "3.0.8",
"resolved": "https://registry.npmjs.org/vscode-uri/-/vscode-uri-3.0.8.tgz",
"integrity": "sha512-AyFQ0EVmsOZOlAnxoFOGOq1SQDWAB7C6aqMGS23svWAllfOaxbuFvcT8D1i8z3Gyn8fraVeZNNmN6e9bxxXkKw=="
},
"walker": {
"version": "1.0.8",

View File

@ -30,7 +30,7 @@
"vscode-languageserver": "8.1.0",
"vscode-languageserver-textdocument": "1.0.10",
"vscode-languageserver-types": "3.17.3",
"vscode-uri": "^3.0.7"
"vscode-uri": "^3.0.8"
},
"devDependencies": {
"@types/command-line-args": "^5.2.0",

View File

@ -13,13 +13,14 @@ import { TextRangeDiagnosticSink } from '../common/diagnosticSink';
import { PythonVersion } from '../common/pythonVersion';
import { TextRange } from '../common/textRange';
import { TextRangeCollection } from '../common/textRangeCollection';
import { Uri } from '../common/uri/uri';
import { Scope } from './scope';
import { IPythonMode } from './sourceFile';
import { SymbolTable } from './symbol';
// Maps import paths to the symbol table for the imported module.
export interface AbsoluteModuleDescriptor {
importingFilePath: string;
importingFileUri: Uri;
nameParts: string[];
}
@ -29,7 +30,7 @@ export interface LookupImportOptions {
}
export type ImportLookup = (
filePathOrModule: string | AbsoluteModuleDescriptor,
fileUriOrModule: Uri | AbsoluteModuleDescriptor,
options?: LookupImportOptions
) => ImportLookupResult | undefined;
@ -52,7 +53,7 @@ export interface AnalyzerFileInfo {
lines: TextRangeCollection<TextRange>;
typingSymbolAliases: Map<string, string>;
definedConstants: Map<string, boolean | string>;
filePath: string;
fileUri: Uri;
moduleName: string;
isStubFile: boolean;
isTypingStubFile: boolean;

View File

@ -13,12 +13,13 @@ import { BackgroundAnalysisBase } from '../backgroundAnalysisBase';
import { ConfigOptions, ExecutionEnvironment } from '../common/configOptions';
import { Diagnostic } from '../common/diagnostic';
import { FileDiagnostics } from '../common/diagnosticSink';
import { ServiceProvider } from '../common/serviceProvider';
import '../common/serviceProviderExtensions';
import { Range } from '../common/textRange';
import { Uri } from '../common/uri/uri';
import { AnalysisCompleteCallback, analyzeProgram } from './analysis';
import { ImportResolver } from './importResolver';
import { MaxAnalysisTime, OpenFileOptions, Program } from './program';
import { ServiceProvider } from '../common/serviceProvider';
import '../common/serviceProviderExtensions';
export enum InvalidatedReason {
Reanalyzed,
@ -72,8 +73,8 @@ export class BackgroundAnalysisProgram {
return this._backgroundAnalysis;
}
hasSourceFile(filePath: string): boolean {
return !!this._program.getSourceFile(filePath);
hasSourceFile(fileUri: Uri): boolean {
return !!this._program.getSourceFile(fileUri);
}
setConfigOptions(configOptions: ConfigOptions) {
@ -90,9 +91,9 @@ export class BackgroundAnalysisProgram {
this.configOptions.getExecutionEnvironments().forEach((e) => this._ensurePartialStubPackages(e));
}
setTrackedFiles(filePaths: string[]) {
this._backgroundAnalysis?.setTrackedFiles(filePaths);
const diagnostics = this._program.setTrackedFiles(filePaths);
setTrackedFiles(fileUris: Uri[]) {
this._backgroundAnalysis?.setTrackedFiles(fileUris);
const diagnostics = this._program.setTrackedFiles(fileUris);
this._reportDiagnosticsForRemovedFiles(diagnostics);
}
@ -101,35 +102,35 @@ export class BackgroundAnalysisProgram {
this._program.setAllowedThirdPartyImports(importNames);
}
setFileOpened(filePath: string, version: number | null, contents: string, options: OpenFileOptions) {
this._backgroundAnalysis?.setFileOpened(filePath, version, contents, options);
this._program.setFileOpened(filePath, version, contents, options);
setFileOpened(fileUri: Uri, version: number | null, contents: string, options: OpenFileOptions) {
this._backgroundAnalysis?.setFileOpened(fileUri, version, contents, options);
this._program.setFileOpened(fileUri, version, contents, options);
}
getChainedFilePath(filePath: string): string | undefined {
return this._program.getChainedFilePath(filePath);
getChainedUri(fileUri: Uri): Uri | undefined {
return this._program.getChainedUri(fileUri);
}
updateChainedFilePath(filePath: string, chainedFilePath: string | undefined) {
this._backgroundAnalysis?.updateChainedFilePath(filePath, chainedFilePath);
this._program.updateChainedFilePath(filePath, chainedFilePath);
updateChainedUri(fileUri: Uri, chainedUri: Uri | undefined) {
this._backgroundAnalysis?.updateChainedUri(fileUri, chainedUri);
this._program.updateChainedUri(fileUri, chainedUri);
}
updateOpenFileContents(path: string, version: number | null, contents: string, options: OpenFileOptions) {
this._backgroundAnalysis?.setFileOpened(path, version, contents, options);
this._program.setFileOpened(path, version, contents, options);
this.markFilesDirty([path], /* evenIfContentsAreSame */ true);
updateOpenFileContents(uri: Uri, version: number | null, contents: string, options: OpenFileOptions) {
this._backgroundAnalysis?.setFileOpened(uri, version, contents, options);
this._program.setFileOpened(uri, version, contents, options);
this.markFilesDirty([uri], /* evenIfContentsAreSame */ true);
}
setFileClosed(filePath: string, isTracked?: boolean) {
this._backgroundAnalysis?.setFileClosed(filePath, isTracked);
const diagnostics = this._program.setFileClosed(filePath, isTracked);
setFileClosed(fileUri: Uri, isTracked?: boolean) {
this._backgroundAnalysis?.setFileClosed(fileUri, isTracked);
const diagnostics = this._program.setFileClosed(fileUri, isTracked);
this._reportDiagnosticsForRemovedFiles(diagnostics);
}
addInterimFile(filePath: string) {
this._backgroundAnalysis?.addInterimFile(filePath);
this._program.addInterimFile(filePath);
addInterimFile(fileUri: Uri) {
this._backgroundAnalysis?.addInterimFile(fileUri);
this._program.addInterimFile(fileUri);
}
markAllFilesDirty(evenIfContentsAreSame: boolean) {
@ -137,9 +138,9 @@ export class BackgroundAnalysisProgram {
this._program.markAllFilesDirty(evenIfContentsAreSame);
}
markFilesDirty(filePaths: string[], evenIfContentsAreSame: boolean) {
this._backgroundAnalysis?.markFilesDirty(filePaths, evenIfContentsAreSame);
this._program.markFilesDirty(filePaths, evenIfContentsAreSame);
markFilesDirty(fileUris: Uri[], evenIfContentsAreSame: boolean) {
this._backgroundAnalysis?.markFilesDirty(fileUris, evenIfContentsAreSame);
this._program.markFilesDirty(fileUris, evenIfContentsAreSame);
}
setCompletionCallback(callback?: AnalysisCompleteCallback) {
@ -163,34 +164,34 @@ export class BackgroundAnalysisProgram {
);
}
async analyzeFile(filePath: string, token: CancellationToken): Promise<boolean> {
async analyzeFile(fileUri: Uri, token: CancellationToken): Promise<boolean> {
if (this._backgroundAnalysis) {
return this._backgroundAnalysis.analyzeFile(filePath, token);
return this._backgroundAnalysis.analyzeFile(fileUri, token);
}
return this._program.analyzeFile(filePath, token);
return this._program.analyzeFile(fileUri, token);
}
libraryUpdated() {
// empty
}
async getDiagnosticsForRange(filePath: string, range: Range, token: CancellationToken): Promise<Diagnostic[]> {
async getDiagnosticsForRange(fileUri: Uri, range: Range, token: CancellationToken): Promise<Diagnostic[]> {
if (this._backgroundAnalysis) {
return this._backgroundAnalysis.getDiagnosticsForRange(filePath, range, token);
return this._backgroundAnalysis.getDiagnosticsForRange(fileUri, range, token);
}
return this._program.getDiagnosticsForRange(filePath, range);
return this._program.getDiagnosticsForRange(fileUri, range);
}
async writeTypeStub(
targetImportPath: string,
targetImportUri: Uri,
targetIsSingleFile: boolean,
stubPath: string,
stubUri: Uri,
token: CancellationToken
): Promise<any> {
if (this._backgroundAnalysis) {
return this._backgroundAnalysis.writeTypeStub(targetImportPath, targetIsSingleFile, stubPath, token);
return this._backgroundAnalysis.writeTypeStub(targetImportUri, targetIsSingleFile, stubUri, token);
}
analyzeProgram(
@ -201,7 +202,7 @@ export class BackgroundAnalysisProgram {
this._serviceProvider.console(),
token
);
return this._program.writeTypeStub(targetImportPath, targetIsSingleFile, stubPath, token);
return this._program.writeTypeStub(targetImportUri, targetIsSingleFile, stubUri, token);
}
invalidateAndForceReanalysis(reason: InvalidatedReason) {
@ -245,7 +246,7 @@ export class BackgroundAnalysisProgram {
}
private _ensurePartialStubPackages(execEnv: ExecutionEnvironment) {
this._backgroundAnalysis?.ensurePartialStubPackages(execEnv.root);
this._backgroundAnalysis?.ensurePartialStubPackages(execEnv.root?.toString());
return this._importResolver.ensurePartialStubPackages(execEnv);
}

View File

@ -22,9 +22,10 @@ import { DiagnosticLevel } from '../common/configOptions';
import { assert, assertNever, fail } from '../common/debug';
import { CreateTypeStubFileAction, Diagnostic } from '../common/diagnostic';
import { DiagnosticRule } from '../common/diagnosticRules';
import { getFileName, stripFileExtension } from '../common/pathUtils';
import { stripFileExtension } from '../common/pathUtils';
import { convertTextRangeToRange } from '../common/positionUtils';
import { TextRange, getEmptyRange } from '../common/textRange';
import { Uri } from '../common/uri/uri';
import { Localizer } from '../localization/localize';
import {
ArgumentCategory,
@ -407,7 +408,7 @@ export class Binder extends ParseTreeWalker {
const classDeclaration: ClassDeclaration = {
type: DeclarationType.Class,
node,
path: this._fileInfo.filePath,
uri: this._fileInfo.fileUri,
range: convertTextRangeToRange(node.name, this._fileInfo.lines),
moduleName: this._fileInfo.moduleName,
isInExceptSuite: this._isInExceptSuite,
@ -465,7 +466,7 @@ export class Binder extends ParseTreeWalker {
node,
isMethod: !!containingClassNode,
isGenerator: false,
path: this._fileInfo.filePath,
uri: this._fileInfo.fileUri,
range: convertTextRangeToRange(node.name, this._fileInfo.lines),
moduleName: this._fileInfo.moduleName,
isInExceptSuite: this._isInExceptSuite,
@ -539,7 +540,7 @@ export class Binder extends ParseTreeWalker {
const paramDeclaration: ParameterDeclaration = {
type: DeclarationType.Parameter,
node: paramNode,
path: this._fileInfo.filePath,
uri: this._fileInfo.fileUri,
range: convertTextRangeToRange(paramNode, this._fileInfo.lines),
moduleName: this._fileInfo.moduleName,
isInExceptSuite: this._isInExceptSuite,
@ -613,7 +614,7 @@ export class Binder extends ParseTreeWalker {
const paramDeclaration: ParameterDeclaration = {
type: DeclarationType.Parameter,
node: paramNode,
path: this._fileInfo.filePath,
uri: this._fileInfo.fileUri,
range: convertTextRangeToRange(paramNode, this._fileInfo.lines),
moduleName: this._fileInfo.moduleName,
isInExceptSuite: this._isInExceptSuite,
@ -765,7 +766,7 @@ export class Binder extends ParseTreeWalker {
const paramDeclaration: TypeParameterDeclaration = {
type: DeclarationType.TypeParameter,
node: param,
path: this._fileInfo.filePath,
uri: this._fileInfo.fileUri,
range: convertTextRangeToRange(node, this._fileInfo.lines),
moduleName: this._fileInfo.moduleName,
isInExceptSuite: this._isInExceptSuite,
@ -806,7 +807,7 @@ export class Binder extends ParseTreeWalker {
const typeAliasDeclaration: TypeAliasDeclaration = {
type: DeclarationType.TypeAlias,
node,
path: this._fileInfo.filePath,
uri: this._fileInfo.fileUri,
range: convertTextRangeToRange(node.name, this._fileInfo.lines),
moduleName: this._fileInfo.moduleName,
isInExceptSuite: this._isInExceptSuite,
@ -1414,7 +1415,7 @@ export class Binder extends ParseTreeWalker {
node: node.name,
isConstant: isConstantName(node.name.value),
inferredTypeSource: node,
path: this._fileInfo.filePath,
uri: this._fileInfo.fileUri,
range: convertTextRangeToRange(node.name, this._fileInfo.lines),
moduleName: this._fileInfo.moduleName,
isInExceptSuite: this._isInExceptSuite,
@ -1745,9 +1746,9 @@ export class Binder extends ParseTreeWalker {
AnalyzerNodeInfo.setFlowNode(node, this._currentFlowNode!);
let resolvedPath = '';
let resolvedPath = Uri.empty();
if (importInfo && importInfo.isImportFound && !importInfo.isNativeLib) {
resolvedPath = importInfo.resolvedPaths[importInfo.resolvedPaths.length - 1];
resolvedPath = importInfo.resolvedUris[importInfo.resolvedUris.length - 1];
}
// If this file is a module __init__.py(i), relative imports of submodules
@ -1756,7 +1757,7 @@ export class Binder extends ParseTreeWalker {
// symbols below) in case one of the imported symbols is the same name as the
// submodule. In that case, we want to the symbol to appear later in the
// declaration list because it should "win" when resolving the alias.
const fileName = stripFileExtension(getFileName(this._fileInfo.filePath));
const fileName = stripFileExtension(this._fileInfo.fileUri.fileName);
const isModuleInitFile =
fileName === '__init__' && node.module.leadingDots === 1 && node.module.nameParts.length === 1;
@ -1814,7 +1815,7 @@ export class Binder extends ParseTreeWalker {
const aliasDecl: AliasDeclaration = {
type: DeclarationType.Alias,
node,
path: resolvedPath,
uri: resolvedPath,
loadSymbolsFromPath: true,
range: getEmptyRange(), // Range is unknown for wildcard name import.
usesLocalName: false,
@ -1834,7 +1835,7 @@ export class Binder extends ParseTreeWalker {
const submoduleFallback: AliasDeclaration = {
type: DeclarationType.Alias,
node,
path: implicitImport.path,
uri: implicitImport.uri,
loadSymbolsFromPath: true,
range: getEmptyRange(),
usesLocalName: false,
@ -1845,7 +1846,7 @@ export class Binder extends ParseTreeWalker {
const aliasDecl: AliasDeclaration = {
type: DeclarationType.Alias,
node,
path: resolvedPath,
uri: resolvedPath,
loadSymbolsFromPath: true,
usesLocalName: false,
symbolName: name,
@ -1926,7 +1927,7 @@ export class Binder extends ParseTreeWalker {
submoduleFallback = {
type: DeclarationType.Alias,
node: importSymbolNode,
path: implicitImport.path,
uri: implicitImport.uri,
loadSymbolsFromPath: true,
range: getEmptyRange(),
usesLocalName: false,
@ -1942,7 +1943,7 @@ export class Binder extends ParseTreeWalker {
if (fileName === '__init__') {
if (node.module.leadingDots === 1 && node.module.nameParts.length === 0) {
loadSymbolsFromPath = false;
} else if (resolvedPath === this._fileInfo.filePath) {
} else if (resolvedPath.equals(this._fileInfo.fileUri)) {
loadSymbolsFromPath = false;
}
}
@ -1951,7 +1952,7 @@ export class Binder extends ParseTreeWalker {
const aliasDecl: AliasDeclaration = {
type: DeclarationType.Alias,
node: importSymbolNode,
path: resolvedPath,
uri: resolvedPath,
loadSymbolsFromPath,
usesLocalName: !!importSymbolNode.alias,
symbolName: importedName,
@ -2304,7 +2305,7 @@ export class Binder extends ParseTreeWalker {
node: node.target,
isConstant: isConstantName(node.target.value),
inferredTypeSource: node,
path: this._fileInfo.filePath,
uri: this._fileInfo.fileUri,
range: convertTextRangeToRange(node.target, this._fileInfo.lines),
moduleName: this._fileInfo.moduleName,
isInExceptSuite: this._isInExceptSuite,
@ -2389,7 +2390,7 @@ export class Binder extends ParseTreeWalker {
node: slotNameNode,
isConstant: isConstantName(slotName),
isDefinedBySlots: true,
path: this._fileInfo.filePath,
uri: this._fileInfo.fileUri,
range: convertTextRangeToRange(slotNameNode, this._fileInfo.lines),
moduleName: this._fileInfo.moduleName,
isInExceptSuite: this._isInExceptSuite,
@ -2438,7 +2439,7 @@ export class Binder extends ParseTreeWalker {
node: target,
isConstant: isConstantName(target.value),
inferredTypeSource: target.parent,
path: this._fileInfo.filePath,
uri: this._fileInfo.fileUri,
range: convertTextRangeToRange(target, this._fileInfo.lines),
moduleName: this._fileInfo.moduleName,
isInExceptSuite: this._isInExceptSuite,
@ -2466,23 +2467,25 @@ export class Binder extends ParseTreeWalker {
const aliasDecl = varSymbol.getDeclarations().find((decl) => decl.type === DeclarationType.Alias) as
| AliasDeclaration
| undefined;
const resolvedPath =
aliasDecl?.path && aliasDecl.loadSymbolsFromPath
? aliasDecl.path
: aliasDecl?.submoduleFallback?.path && aliasDecl.submoduleFallback.loadSymbolsFromPath
? aliasDecl.submoduleFallback.path
const resolvedUri =
aliasDecl?.uri && !aliasDecl.uri.isEmpty() && aliasDecl.loadSymbolsFromPath
? aliasDecl.uri
: aliasDecl?.submoduleFallback?.uri &&
!aliasDecl.submoduleFallback.uri.isEmpty() &&
aliasDecl.submoduleFallback.loadSymbolsFromPath
? aliasDecl.submoduleFallback.uri
: undefined;
if (!resolvedPath) {
if (!resolvedUri) {
return undefined;
}
let lookupInfo = this._fileInfo.importLookup(resolvedPath);
let lookupInfo = this._fileInfo.importLookup(resolvedUri);
if (lookupInfo?.dunderAllNames) {
return lookupInfo.dunderAllNames;
}
if (aliasDecl?.submoduleFallback?.path) {
lookupInfo = this._fileInfo.importLookup(aliasDecl.submoduleFallback.path);
if (aliasDecl?.submoduleFallback?.uri && !aliasDecl.submoduleFallback.uri.isEmpty()) {
lookupInfo = this._fileInfo.importLookup(aliasDecl.submoduleFallback.uri);
return lookupInfo?.dunderAllNames;
}
@ -2520,15 +2523,15 @@ export class Binder extends ParseTreeWalker {
.getDeclarations()
.find((decl) => decl.type === DeclarationType.Alias && decl.firstNamePart === firstNamePartValue);
let newDecl: AliasDeclaration;
let pathOfLastSubmodule: string;
if (importInfo && importInfo.isImportFound && !importInfo.isNativeLib && importInfo.resolvedPaths.length > 0) {
pathOfLastSubmodule = importInfo.resolvedPaths[importInfo.resolvedPaths.length - 1];
let uriOfLastSubmodule: Uri;
if (importInfo && importInfo.isImportFound && !importInfo.isNativeLib && importInfo.resolvedUris.length > 0) {
uriOfLastSubmodule = importInfo.resolvedUris[importInfo.resolvedUris.length - 1];
} else {
pathOfLastSubmodule = UnresolvedModuleMarker;
uriOfLastSubmodule = UnresolvedModuleMarker;
}
const isResolved =
importInfo && importInfo.isImportFound && !importInfo.isNativeLib && importInfo.resolvedPaths.length > 0;
importInfo && importInfo.isImportFound && !importInfo.isNativeLib && importInfo.resolvedUris.length > 0;
if (existingDecl) {
newDecl = existingDecl as AliasDeclaration;
@ -2536,7 +2539,7 @@ export class Binder extends ParseTreeWalker {
newDecl = {
type: DeclarationType.Alias,
node,
path: pathOfLastSubmodule,
uri: uriOfLastSubmodule,
loadSymbolsFromPath: false,
range: getEmptyRange(),
usesLocalName: !!importAlias,
@ -2551,7 +2554,7 @@ export class Binder extends ParseTreeWalker {
newDecl = {
type: DeclarationType.Alias,
node,
path: pathOfLastSubmodule,
uri: uriOfLastSubmodule,
loadSymbolsFromPath: true,
range: getEmptyRange(),
usesLocalName: !!importAlias,
@ -2565,8 +2568,8 @@ export class Binder extends ParseTreeWalker {
// See if there is import info for this part of the path. This allows us
// to implicitly import all of the modules in a multi-part module name.
const implicitImportInfo = AnalyzerNodeInfo.getImportInfo(node.module.nameParts[0]);
if (implicitImportInfo && implicitImportInfo.resolvedPaths.length) {
newDecl.path = implicitImportInfo.resolvedPaths[0];
if (implicitImportInfo && implicitImportInfo.resolvedUris.length) {
newDecl.uri = implicitImportInfo.resolvedUris[0];
newDecl.loadSymbolsFromPath = true;
this._addImplicitImportsToLoaderActions(implicitImportInfo, newDecl);
}
@ -2574,7 +2577,7 @@ export class Binder extends ParseTreeWalker {
// Add the implicit imports for this module if it's the last
// name part we're resolving.
if (importAlias || node.module.nameParts.length === 1) {
newDecl.path = pathOfLastSubmodule;
newDecl.uri = uriOfLastSubmodule;
newDecl.loadSymbolsFromPath = true;
newDecl.isUnresolved = false;
@ -2594,13 +2597,13 @@ export class Binder extends ParseTreeWalker {
: undefined;
if (!loaderActions) {
const loaderActionPath =
importInfo && i < importInfo.resolvedPaths.length
? importInfo.resolvedPaths[i]
importInfo && i < importInfo.resolvedUris.length
? importInfo.resolvedUris[i]
: UnresolvedModuleMarker;
// Allocate a new loader action.
loaderActions = {
path: loaderActionPath,
uri: loaderActionPath,
loadSymbolsFromPath: false,
implicitImports: new Map<string, ModuleLoaderActions>(),
isUnresolved: !isResolved,
@ -2614,8 +2617,8 @@ export class Binder extends ParseTreeWalker {
if (i === node.module.nameParts.length - 1) {
// If this is the last name part we're resolving, add in the
// implicit imports as well.
if (importInfo && i < importInfo.resolvedPaths.length) {
loaderActions.path = importInfo.resolvedPaths[i];
if (importInfo && i < importInfo.resolvedUris.length) {
loaderActions.uri = importInfo.resolvedUris[i];
loaderActions.loadSymbolsFromPath = true;
this._addImplicitImportsToLoaderActions(importInfo, loaderActions);
}
@ -2625,8 +2628,8 @@ export class Binder extends ParseTreeWalker {
// import all of the modules in a multi-part module name (e.g. "import a.b.c"
// imports "a" and "a.b" and "a.b.c").
const implicitImportInfo = AnalyzerNodeInfo.getImportInfo(node.module.nameParts[i]);
if (implicitImportInfo && implicitImportInfo.resolvedPaths.length) {
loaderActions.path = implicitImportInfo.resolvedPaths[i];
if (implicitImportInfo && implicitImportInfo.resolvedUris.length) {
loaderActions.uri = implicitImportInfo.resolvedUris[i];
loaderActions.loadSymbolsFromPath = true;
this._addImplicitImportsToLoaderActions(implicitImportInfo, loaderActions);
}
@ -3486,7 +3489,7 @@ export class Binder extends ParseTreeWalker {
type: DeclarationType.Intrinsic,
node,
intrinsicType: type,
path: this._fileInfo.filePath,
uri: this._fileInfo.fileUri,
range: getEmptyRange(),
moduleName: this._fileInfo.moduleName,
isInExceptSuite: this._isInExceptSuite,
@ -3561,7 +3564,7 @@ export class Binder extends ParseTreeWalker {
inferredTypeSource: source,
isInferenceAllowedInPyTyped: this._isInferenceAllowedInPyTyped(name.value),
typeAliasName: isPossibleTypeAlias ? target : undefined,
path: this._fileInfo.filePath,
uri: this._fileInfo.fileUri,
range: convertTextRangeToRange(name, this._fileInfo.lines),
moduleName: this._fileInfo.moduleName,
isInExceptSuite: this._isInExceptSuite,
@ -3609,7 +3612,7 @@ export class Binder extends ParseTreeWalker {
isConstant: isConstantName(name.value),
inferredTypeSource: source,
isDefinedByMemberAccess: true,
path: this._fileInfo.filePath,
uri: this._fileInfo.fileUri,
range: convertTextRangeToRange(target.memberName, this._fileInfo.lines),
moduleName: this._fileInfo.moduleName,
isInExceptSuite: this._isInExceptSuite,
@ -3701,7 +3704,7 @@ export class Binder extends ParseTreeWalker {
isConstant: isConstantName(name.value),
isFinal: finalInfo.isFinal,
typeAliasName: target,
path: this._fileInfo.filePath,
uri: this._fileInfo.fileUri,
typeAnnotationNode,
range: convertTextRangeToRange(name, this._fileInfo.lines),
moduleName: this._fileInfo.moduleName,
@ -3774,7 +3777,7 @@ export class Binder extends ParseTreeWalker {
isConstant: isConstantName(name.value),
isDefinedByMemberAccess: true,
isFinal: finalInfo.isFinal,
path: this._fileInfo.filePath,
uri: this._fileInfo.fileUri,
typeAnnotationNode: finalInfo.isFinal && !finalInfo.finalTypeNode ? undefined : typeAnnotation,
range: convertTextRangeToRange(target.memberName, this._fileInfo.lines),
moduleName: this._fileInfo.moduleName,
@ -4021,14 +4024,14 @@ export class Binder extends ParseTreeWalker {
? loaderActions.implicitImports.get(implicitImport.name)
: undefined;
if (existingLoaderAction) {
existingLoaderAction.path = implicitImport.path;
existingLoaderAction.uri = implicitImport.uri;
existingLoaderAction.loadSymbolsFromPath = true;
} else {
if (!loaderActions.implicitImports) {
loaderActions.implicitImports = new Map<string, ModuleLoaderActions>();
}
loaderActions.implicitImports.set(implicitImport.name, {
path: implicitImport.path,
uri: implicitImport.uri,
loadSymbolsFromPath: true,
implicitImports: new Map<string, ModuleLoaderActions>(),
});
@ -4095,7 +4098,7 @@ export class Binder extends ParseTreeWalker {
symbol.addDeclaration({
type: DeclarationType.SpecialBuiltInClass,
node: annotationNode,
path: this._fileInfo.filePath,
uri: this._fileInfo.fileUri,
range: convertTextRangeToRange(annotationNode, this._fileInfo.lines),
moduleName: this._fileInfo.moduleName,
isInExceptSuite: this._isInExceptSuite,

View File

@ -20,9 +20,9 @@ import { DiagnosticLevel } from '../common/configOptions';
import { assert, assertNever } from '../common/debug';
import { ActionKind, Diagnostic, DiagnosticAddendum, RenameShadowedFileAction } from '../common/diagnostic';
import { DiagnosticRule } from '../common/diagnosticRules';
import { getFileExtension } from '../common/pathUtils';
import { PythonVersion, versionToString } from '../common/pythonVersion';
import { TextRange } from '../common/textRange';
import { Uri } from '../common/uri/uri';
import { DefinitionProvider } from '../languageService/definitionProvider';
import { Localizer } from '../localization/localize';
import {
@ -233,7 +233,9 @@ export class Checker extends ParseTreeWalker {
const codeComplexity = AnalyzerNodeInfo.getCodeFlowComplexity(this._moduleNode);
if (isPrintCodeComplexityEnabled) {
console.log(`Code complexity of module ${this._fileInfo.filePath} is ${codeComplexity.toString()}`);
console.log(
`Code complexity of module ${this._fileInfo.fileUri.toUserVisibleString()} is ${codeComplexity.toString()}`
);
}
if (codeComplexity > maxCodeComplexity) {
@ -1566,11 +1568,12 @@ export class Checker extends ParseTreeWalker {
}
const resolvedAlias = this._evaluator.resolveAliasDeclaration(decl, /* resolveLocalNames */ true);
if (!resolvedAlias?.path || !isStubFile(resolvedAlias.path)) {
const resolvedAliasUri = resolvedAlias?.uri;
if (!resolvedAliasUri || !isStubFile(resolvedAliasUri)) {
continue;
}
const importResult = this._getImportResult(node, resolvedAlias.path);
const importResult = this._getImportResult(node, resolvedAliasUri);
if (!importResult) {
continue;
}
@ -1667,22 +1670,22 @@ export class Checker extends ParseTreeWalker {
return false;
}
private _getImportResult(node: ImportFromAsNode, filePath: string) {
const execEnv = this._importResolver.getConfigOptions().findExecEnvironment(filePath);
private _getImportResult(node: ImportFromAsNode, uri: Uri) {
const execEnv = this._importResolver.getConfigOptions().findExecEnvironment(uri);
const moduleNameNode = (node.parent as ImportFromNode).module;
// Handle both absolute and relative imports.
const moduleName =
moduleNameNode.leadingDots === 0
? this._importResolver.getModuleNameForImport(filePath, execEnv).moduleName
: getRelativeModuleName(this._importResolver.fileSystem, this._fileInfo.filePath, filePath);
? this._importResolver.getModuleNameForImport(uri, execEnv).moduleName
: getRelativeModuleName(this._importResolver.fileSystem, this._fileInfo.fileUri, uri);
if (!moduleName) {
return undefined;
}
return this._importResolver.resolveImport(
this._fileInfo.filePath,
this._fileInfo.fileUri,
execEnv,
createImportedModuleDescriptor(moduleName)
);
@ -3087,7 +3090,7 @@ export class Checker extends ParseTreeWalker {
if (diagnostic && overload.details.declaration) {
diagnostic.addRelatedInfo(
Localizer.DiagnosticAddendum.overloadSignature(),
overload.details.declaration?.path ?? primaryDecl.path,
overload.details.declaration?.uri ?? primaryDecl.uri,
overload.details.declaration?.range ?? primaryDecl.range
);
}
@ -3280,7 +3283,7 @@ export class Checker extends ParseTreeWalker {
}
if (primaryDeclNode) {
diag.addRelatedInfo(primaryDeclInfo, primaryDecl.path, primaryDecl.range);
diag.addRelatedInfo(primaryDeclInfo, primaryDecl.uri, primaryDecl.range);
}
}
};
@ -4177,7 +4180,7 @@ export class Checker extends ParseTreeWalker {
if (
stdlibPath &&
this._importResolver.isStdlibModule(desc, this._fileInfo.executionEnvironment) &&
this._sourceMapper.isUserCode(this._fileInfo.filePath)
this._sourceMapper.isUserCode(this._fileInfo.fileUri)
) {
// This means the user has a module that is overwriting the stdlib module.
const diag = this._evaluator.addDiagnosticForTextRange(
@ -4186,7 +4189,7 @@ export class Checker extends ParseTreeWalker {
DiagnosticRule.reportShadowedImports,
Localizer.Diagnostic.stdlibModuleOverridden().format({
name: moduleName,
path: this._fileInfo.filePath,
path: this._fileInfo.fileUri.toUserVisibleString(),
}),
this._moduleNode
);
@ -4195,8 +4198,8 @@ export class Checker extends ParseTreeWalker {
if (diag) {
const renameAction: RenameShadowedFileAction = {
action: ActionKind.RenameShadowedFileAction,
oldFile: this._fileInfo.filePath,
newFile: this._sourceMapper.getNextFileName(this._fileInfo.filePath),
oldUri: this._fileInfo.fileUri,
newUri: this._sourceMapper.getNextFileName(this._fileInfo.fileUri),
};
diag.addAction(renameAction);
}
@ -4245,7 +4248,7 @@ export class Checker extends ParseTreeWalker {
namePartNodes[namePartNodes.length - 1].start,
CancellationToken.None
);
const paths = definitions ? definitions.map((d) => d.path) : [];
const paths = definitions ? definitions.map((d) => d.uri) : [];
paths.forEach((p) => {
if (!p.startsWith(stdlibPath) && !isStubFile(p) && this._sourceMapper.isUserCode(p)) {
// This means the user has a module that is overwriting the stdlib module.
@ -4254,7 +4257,7 @@ export class Checker extends ParseTreeWalker {
DiagnosticRule.reportShadowedImports,
Localizer.Diagnostic.stdlibModuleOverridden().format({
name: nameParts.join('.'),
path: p,
path: p.toUserVisibleString(),
}),
node
);
@ -4262,8 +4265,8 @@ export class Checker extends ParseTreeWalker {
if (diag) {
const renameAction: RenameShadowedFileAction = {
action: ActionKind.RenameShadowedFileAction,
oldFile: p,
newFile: this._sourceMapper.getNextFileName(p),
oldUri: p,
newUri: this._sourceMapper.getNextFileName(p),
};
diag.addAction(renameAction);
}
@ -4755,7 +4758,7 @@ export class Checker extends ParseTreeWalker {
decl.type !== DeclarationType.Function || ParseTreeUtils.isSuiteEmpty(decl.node.suite)
)
) {
if (getFileExtension(decls[0].path).toLowerCase() !== '.pyi') {
if (!decls[0].uri.hasExtension('.pyi')) {
if (!isSymbolImplemented(name)) {
diagAddendum.addMessage(
Localizer.DiagnosticAddendum.missingProtocolMember().format({
@ -4881,7 +4884,7 @@ export class Checker extends ParseTreeWalker {
if (fieldDecls.length > 0) {
diagnostic.addRelatedInfo(
Localizer.DiagnosticAddendum.dataClassFieldLocation(),
fieldDecls[0].path,
fieldDecls[0].uri,
fieldDecls[0].range
);
}
@ -5103,7 +5106,16 @@ export class Checker extends ParseTreeWalker {
const updatedClassType = ClassType.cloneWithNewTypeParameters(classType, updatedTypeParams);
const objectObject = ClassType.cloneAsInstance(objectType);
const dummyTypeObject = ClassType.createInstantiable('__varianceDummy', '', '', '', 0, 0, undefined, undefined);
const dummyTypeObject = ClassType.createInstantiable(
'__varianceDummy',
'',
'',
Uri.empty(),
0,
0,
undefined,
undefined
);
updatedTypeParams.forEach((param, paramIndex) => {
// Skip variadics and ParamSpecs.
@ -5382,7 +5394,7 @@ export class Checker extends ParseTreeWalker {
)
),
}),
secondaryDecl.path,
secondaryDecl.uri,
secondaryDecl.range
);
}
@ -5760,7 +5772,7 @@ export class Checker extends ParseTreeWalker {
baseClass: this._evaluator.printType(convertToInstance(overriddenClassAndSymbol.classType)),
type: this._evaluator.printType(overriddenType),
}),
overriddenDecl.path,
overriddenDecl.uri,
overriddenDecl.range
);
@ -5769,7 +5781,7 @@ export class Checker extends ParseTreeWalker {
baseClass: this._evaluator.printType(convertToInstance(overrideClassAndSymbol.classType)),
type: this._evaluator.printType(overrideType),
}),
overrideDecl.path,
overrideDecl.uri,
overrideDecl.range
);
}
@ -6005,7 +6017,7 @@ export class Checker extends ParseTreeWalker {
if (diag && origDecl) {
diag.addRelatedInfo(
Localizer.DiagnosticAddendum.overriddenMethod(),
origDecl.path,
origDecl.uri,
origDecl.range
);
}
@ -6030,7 +6042,7 @@ export class Checker extends ParseTreeWalker {
if (diag && origDecl) {
diag.addRelatedInfo(
Localizer.DiagnosticAddendum.finalMethod(),
origDecl.path,
origDecl.uri,
origDecl.range
);
}
@ -6060,7 +6072,7 @@ export class Checker extends ParseTreeWalker {
if (diag && origDecl) {
diag.addRelatedInfo(
Localizer.DiagnosticAddendum.overriddenMethod(),
origDecl.path,
origDecl.uri,
origDecl.range
);
}
@ -6123,7 +6135,7 @@ export class Checker extends ParseTreeWalker {
if (diag && origDecl) {
diag.addRelatedInfo(
Localizer.DiagnosticAddendum.overriddenMethod(),
origDecl.path,
origDecl.uri,
origDecl.range
);
}
@ -6160,7 +6172,7 @@ export class Checker extends ParseTreeWalker {
if (diag && origDecl) {
diag.addRelatedInfo(
Localizer.DiagnosticAddendum.overriddenMethod(),
origDecl.path,
origDecl.uri,
origDecl.range
);
}
@ -6247,7 +6259,7 @@ export class Checker extends ParseTreeWalker {
if (diag && origDecl) {
diag.addRelatedInfo(
Localizer.DiagnosticAddendum.overriddenSymbol(),
origDecl.path,
origDecl.uri,
origDecl.range
);
}
@ -6306,7 +6318,7 @@ export class Checker extends ParseTreeWalker {
if (diag) {
diag.addRelatedInfo(
Localizer.DiagnosticAddendum.overriddenSymbol(),
overrideFinalVarDecl.path,
overrideFinalVarDecl.uri,
overrideFinalVarDecl.range
);
}
@ -6353,7 +6365,7 @@ export class Checker extends ParseTreeWalker {
if (diag && origDecl) {
diag.addRelatedInfo(
Localizer.DiagnosticAddendum.overriddenSymbol(),
origDecl.path,
origDecl.uri,
origDecl.range
);
}

View File

@ -10,10 +10,12 @@
* by picking the alphabetically-first module in the cycle.
*/
export class CircularDependency {
private _paths: string[] = [];
import { Uri } from '../common/uri/uri';
appendPath(path: string) {
export class CircularDependency {
private _paths: Uri[] = [];
appendPath(path: Uri) {
this._paths.push(path);
}

View File

@ -867,7 +867,7 @@ function getDescriptorForConverterField(
descriptorName,
getClassFullName(converterNode, fileInfo.moduleName, descriptorName),
fileInfo.moduleName,
fileInfo.filePath,
fileInfo.fileUri,
ClassTypeFlags.None,
getTypeSourceId(converterNode),
/* declaredMetaclass */ undefined,

View File

@ -10,6 +10,7 @@
*/
import { Range } from '../common/textRange';
import { Uri } from '../common/uri/uri';
import {
ClassNode,
ExpressionNode,
@ -31,7 +32,7 @@ import {
YieldNode,
} from '../parser/parseNodes';
export const UnresolvedModuleMarker = '*** unresolved ***';
export const UnresolvedModuleMarker = Uri.parse('unresolved-module-marker://**/*', /* isCaseSensitive */ true);
export const enum DeclarationType {
Intrinsic,
@ -57,9 +58,9 @@ export interface DeclarationBase {
node: ParseNode;
// The file and range within that file that
// contains the declaration. Unless this is an alias, then path refers to the
// contains the declaration. Unless this is an alias, then uri refers to the
// file the alias is referring to.
path: string;
uri: Uri;
range: Range;
// The dot-separated import name for the file that
@ -222,10 +223,10 @@ export interface AliasDeclaration extends DeclarationBase {
// This interface represents a set of actions that the python loader
// performs when a module import is encountered.
export interface ModuleLoaderActions {
// The resolved path of the implicit import. This can be empty
// if the resolved path doesn't reference a module (e.g. it's
// The resolved uri of the implicit import. This can be empty
// if the resolved uri doesn't reference a module (e.g. it's
// a directory).
path: string;
uri: Uri;
// Is this a dummy entry for an unresolved import?
isUnresolved?: boolean;
@ -285,5 +286,5 @@ export function isIntrinsicDeclaration(decl: Declaration): decl is IntrinsicDecl
}
export function isUnresolvedAliasDeclaration(decl: Declaration): boolean {
return isAliasDeclaration(decl) && decl.path === UnresolvedModuleMarker;
return isAliasDeclaration(decl) && decl.uri === UnresolvedModuleMarker;
}

View File

@ -8,6 +8,7 @@
*/
import { getEmptyRange } from '../common/textRange';
import { Uri } from '../common/uri/uri';
import { NameNode, ParseNodeType } from '../parser/parseNodes';
import { ImportLookup, ImportLookupResult } from './analyzerFileInfo';
import { AliasDeclaration, Declaration, DeclarationType, ModuleLoaderActions, isAliasDeclaration } from './declaration';
@ -78,7 +79,7 @@ export function areDeclarationsSame(
return false;
}
if (decl1.path !== decl2.path) {
if (!decl1.uri.equals(decl2.uri)) {
return false;
}
@ -174,16 +175,16 @@ export function getNameNodeForDeclaration(declaration: Declaration): NameNode |
throw new Error(`Shouldn't reach here`);
}
export function isDefinedInFile(decl: Declaration, filePath: string) {
export function isDefinedInFile(decl: Declaration, fileUri: Uri) {
if (isAliasDeclaration(decl)) {
// Alias decl's path points to the original symbol
// the alias is pointing to. So, we need to get the
// filepath in that the alias is defined from the node.
return getFileInfoFromNode(decl.node)?.filePath === filePath;
return getFileInfoFromNode(decl.node)?.fileUri.equals(fileUri);
}
// Other decls, the path points to the file the symbol is defined in.
return decl.path === filePath;
return decl.uri.equals(fileUri);
}
export function getDeclarationsWithUsesLocalNameRemoved(decls: Declaration[]) {
@ -199,13 +200,13 @@ export function getDeclarationsWithUsesLocalNameRemoved(decls: Declaration[]) {
});
}
export function createSynthesizedAliasDeclaration(path: string): AliasDeclaration {
export function createSynthesizedAliasDeclaration(uri: Uri): AliasDeclaration {
// The only time this decl is used is for IDE services such as
// the find all references, hover provider and etc.
return {
type: DeclarationType.Alias,
node: undefined!,
path,
uri,
loadSymbolsFromPath: false,
range: getEmptyRange(),
implicitImports: new Map<string, ModuleLoaderActions>(),
@ -267,8 +268,10 @@ export function resolveAliasDeclaration(
}
let lookupResult: ImportLookupResult | undefined;
if (curDeclaration.path && curDeclaration.loadSymbolsFromPath) {
lookupResult = importLookup(curDeclaration.path, { skipFileNeededCheck: options.skipFileNeededCheck });
if (!curDeclaration.uri.isEmpty() && curDeclaration.loadSymbolsFromPath) {
lookupResult = importLookup(curDeclaration.uri, {
skipFileNeededCheck: options.skipFileNeededCheck,
});
}
const symbol: Symbol | undefined = lookupResult
@ -284,11 +287,11 @@ export function resolveAliasDeclaration(
// when useLibraryCodeForTypes is disabled), b should be evaluated as Unknown,
// not as a module.
if (
curDeclaration.path &&
!curDeclaration.uri.isEmpty() &&
curDeclaration.submoduleFallback.type === DeclarationType.Alias &&
curDeclaration.submoduleFallback.path
!curDeclaration.submoduleFallback.uri.isEmpty()
) {
const lookupResult = importLookup(curDeclaration.submoduleFallback.path, {
const lookupResult = importLookup(curDeclaration.submoduleFallback.uri, {
skipFileNeededCheck: options.skipFileNeededCheck,
skipParsing: true,
});
@ -393,7 +396,7 @@ export function resolveAliasDeclaration(
// the module is foo, and the foo.__init__.py file contains the statement
// "from foo import bar", we want to import the foo/bar.py submodule.
if (
curDeclaration.path === declaration.path &&
curDeclaration.uri.equals(declaration.uri) &&
curDeclaration.type === DeclarationType.Alias &&
curDeclaration.submoduleFallback
) {

View File

@ -81,7 +81,7 @@ export function createEnumType(
className,
getClassFullName(errorNode, fileInfo.moduleName, className),
fileInfo.moduleName,
fileInfo.filePath,
fileInfo.fileUri,
ClassTypeFlags.EnumClass,
getTypeSourceId(errorNode),
/* declaredMetaclass */ undefined,

File diff suppressed because it is too large Load Diff

View File

@ -7,6 +7,7 @@
* Interface that describes the output of the import resolver.
*/
import { Uri } from '../common/uri/uri';
import { PyTypedInfo } from './pyTypedUtils';
export const enum ImportType {
@ -19,7 +20,7 @@ export interface ImplicitImport {
isStubFile: boolean;
isNativeLib: boolean;
name: string;
path: string;
uri: Uri;
pyTypedInfo?: PyTypedInfo | undefined;
}
@ -62,11 +63,11 @@ export interface ImportResult {
// The resolved absolute paths for each of the files in the module name.
// Parts that have no files (e.g. directories within a namespace
// package) have empty strings for a resolvedPath.
resolvedPaths: string[];
resolvedUris: Uri[];
// For absolute imports, the search path that was used to resolve
// (or partially resolve) the module.
searchPath?: string;
searchPath?: Uri;
// True if resolved file is a type hint (.pyi) file rather than
// a python (.py) file.
@ -103,5 +104,5 @@ export interface ImportResult {
pyTypedInfo?: PyTypedInfo | undefined;
// The directory of the package, if found.
packageDirectory?: string | undefined;
packageDirectory?: Uri | undefined;
}

View File

@ -14,16 +14,11 @@ import { throwIfCancellationRequested } from '../common/cancellationUtils';
import { addIfUnique, appendArray, createMapFromItems } from '../common/collectionUtils';
import { TextEditAction } from '../common/editAction';
import { ReadOnlyFileSystem } from '../common/fileSystem';
import {
getDirectoryPath,
getFileName,
getRelativePathComponentsFromDirectory,
isFile,
stripFileExtension,
} from '../common/pathUtils';
import { convertOffsetToPosition, convertPositionToOffset } from '../common/positionUtils';
import { compareStringsCaseSensitive } from '../common/stringUtils';
import { Position, Range, TextRange } from '../common/textRange';
import { Uri } from '../common/uri/uri';
import { isFile } from '../common/uri/uriUtils';
import {
ImportAsNode,
ImportFromAsNode,
@ -35,18 +30,18 @@ import {
ParseNodeType,
} from '../parser/parseNodes';
import { ParseResults } from '../parser/parser';
import { TokenType } from '../parser/tokenizerTypes';
import * as AnalyzerNodeInfo from './analyzerNodeInfo';
import { ModuleNameAndType } from './importResolver';
import { ImportResult, ImportType } from './importResult';
import * as SymbolNameUtils from './symbolNameUtils';
import { findTokenAfter, getTokenAt } from './parseTreeUtils';
import { TokenType } from '../parser/tokenizerTypes';
import * as SymbolNameUtils from './symbolNameUtils';
export interface ImportStatement {
node: ImportNode | ImportFromNode;
subnode?: ImportAsNode;
importResult: ImportResult | undefined;
resolvedPath: string | undefined;
resolvedPath: Uri | undefined;
moduleName: string;
followsNonImportStatement: boolean;
}
@ -625,10 +620,10 @@ function _getInsertionEditForAutoImportInsertion(
function _processImportNode(node: ImportNode, localImports: ImportStatements, followsNonImportStatement: boolean) {
node.list.forEach((importAsNode) => {
const importResult = AnalyzerNodeInfo.getImportInfo(importAsNode.module);
let resolvedPath: string | undefined;
let resolvedPath: Uri | undefined;
if (importResult && importResult.isImportFound) {
resolvedPath = importResult.resolvedPaths[importResult.resolvedPaths.length - 1];
resolvedPath = importResult.resolvedUris[importResult.resolvedUris.length - 1];
}
const localImport: ImportStatement = {
@ -647,8 +642,8 @@ function _processImportNode(node: ImportNode, localImports: ImportStatements, fo
// Don't overwrite existing import or import from statements
// because we always want to prefer 'import from' over 'import'
// in the map.
if (!localImports.mapByFilePath.has(resolvedPath)) {
localImports.mapByFilePath.set(resolvedPath, localImport);
if (!localImports.mapByFilePath.has(resolvedPath.key)) {
localImports.mapByFilePath.set(resolvedPath.key, localImport);
}
}
});
@ -661,10 +656,10 @@ function _processImportFromNode(
includeImplicitImports: boolean
) {
const importResult = AnalyzerNodeInfo.getImportInfo(node.module);
let resolvedPath: string | undefined;
let resolvedPath: Uri | undefined;
if (importResult && importResult.isImportFound) {
resolvedPath = importResult.resolvedPaths[importResult.resolvedPaths.length - 1];
resolvedPath = importResult.resolvedUris[importResult.resolvedUris.length - 1];
}
if (includeImplicitImports && importResult) {
@ -673,7 +668,7 @@ function _processImportFromNode(
for (const implicitImport of importResult.implicitImports.values()) {
const importFromAs = node.imports.find((i) => i.name.value === implicitImport.name);
if (importFromAs) {
localImports.implicitImports.set(implicitImport.path, importFromAs);
localImports.implicitImports.set(implicitImport.uri.key, importFromAs);
}
}
}
@ -690,7 +685,7 @@ function _processImportFromNode(
// Add it to the map.
if (resolvedPath) {
const prevEntry = localImports.mapByFilePath.get(resolvedPath);
const prevEntry = localImports.mapByFilePath.get(resolvedPath.key);
// Overwrite existing import statements because we always want to prefer
// 'import from' over 'import'. Also, overwrite existing 'import from' if
// the module name is shorter.
@ -699,7 +694,7 @@ function _processImportFromNode(
prevEntry.node.nodeType === ParseNodeType.Import ||
prevEntry.moduleName.length > localImport.moduleName.length
) {
localImports.mapByFilePath.set(resolvedPath, localImport);
localImports.mapByFilePath.set(resolvedPath.key, localImport);
}
}
}
@ -850,23 +845,23 @@ function getConsecutiveNumberPairs(indices: number[]) {
export function getRelativeModuleName(
fs: ReadOnlyFileSystem,
sourcePath: string,
targetPath: string,
sourcePath: Uri,
targetPath: Uri,
ignoreFolderStructure = false,
sourceIsFile?: boolean
) {
let srcPath = sourcePath;
sourceIsFile = sourceIsFile !== undefined ? sourceIsFile : isFile(fs, sourcePath);
if (sourceIsFile) {
srcPath = getDirectoryPath(sourcePath);
srcPath = sourcePath.getDirectory();
}
let symbolName: string | undefined;
let destPath = targetPath;
if (sourceIsFile) {
destPath = getDirectoryPath(targetPath);
destPath = targetPath.getDirectory();
const fileName = stripFileExtension(getFileName(targetPath));
const fileName = targetPath.stripAllExtensions().fileName;
if (fileName !== '__init__') {
// ex) src: a.py, dest: b.py -> ".b" will be returned.
symbolName = fileName;
@ -875,18 +870,18 @@ export function getRelativeModuleName(
// like how it would return for sibling folder.
//
// if folder structure is not ignored, ".." will be returned
symbolName = getFileName(destPath);
destPath = getDirectoryPath(destPath);
symbolName = destPath.fileName;
destPath = destPath.getDirectory();
}
}
const relativePaths = getRelativePathComponentsFromDirectory(srcPath, destPath, (f) => fs.realCasePath(f));
const relativePaths = srcPath.getRelativePathComponents(destPath);
// This assumes both file paths are under the same importing root.
// So this doesn't handle paths pointing to 2 different import roots.
// ex) user file A to library file B
let currentPaths = '.';
for (let i = 1; i < relativePaths.length; i++) {
for (let i = 0; i < relativePaths.length; i++) {
const relativePath = relativePaths[i];
if (relativePath === '..') {
currentPaths += '.';
@ -907,25 +902,25 @@ export function getRelativeModuleName(
return currentPaths;
}
export function getDirectoryLeadingDotsPointsTo(fromDirectory: string, leadingDots: number) {
export function getDirectoryLeadingDotsPointsTo(fromDirectory: Uri, leadingDots: number) {
let currentDirectory = fromDirectory;
for (let i = 1; i < leadingDots; i++) {
if (currentDirectory === '') {
if (currentDirectory.isRoot()) {
return undefined;
}
currentDirectory = getDirectoryPath(currentDirectory);
currentDirectory = currentDirectory.getDirectory();
}
return currentDirectory;
}
export function getResolvedFilePath(importResult: ImportResult | undefined) {
if (!importResult || !importResult.isImportFound || importResult.resolvedPaths.length === 0) {
if (!importResult || !importResult.isImportFound || importResult.resolvedUris.length === 0) {
return undefined;
}
if (importResult.resolvedPaths.length === 1 && importResult.resolvedPaths[0] === '') {
if (importResult.resolvedUris.length === 1 && importResult.resolvedUris[0].equals(Uri.empty())) {
// Import is resolved to namespace package folder.
if (importResult.packageDirectory) {
return importResult.packageDirectory;
@ -940,7 +935,7 @@ export function getResolvedFilePath(importResult: ImportResult | undefined) {
}
// Regular case.
return importResult.resolvedPaths[importResult.resolvedPaths.length - 1];
return importResult.resolvedUris[importResult.resolvedUris.length - 1];
}
export function haveSameParentModule(module1: string[], module2: string[]) {

View File

@ -114,7 +114,7 @@ export function createNamedTupleType(
className,
ParseTreeUtils.getClassFullName(errorNode, fileInfo.moduleName, className),
fileInfo.moduleName,
fileInfo.filePath,
fileInfo.fileUri,
ClassTypeFlags.ReadOnlyInstanceVariables,
ParseTreeUtils.getTypeSourceId(errorNode),
/* declaredMetaclass */ undefined,
@ -207,7 +207,7 @@ export function createNamedTupleType(
type: DeclarationType.Variable,
node: stringNode as StringListNode,
isRuntimeTypeExpression: true,
path: fileInfo.filePath,
uri: fileInfo.fileUri,
range: convertOffsetsToRange(
stringNode.start,
TextRange.getEnd(stringNode),
@ -303,7 +303,7 @@ export function createNamedTupleType(
const declaration: VariableDeclaration = {
type: DeclarationType.Variable,
node: entryNameNode,
path: fileInfo.filePath,
uri: fileInfo.fileUri,
typeAnnotationNode: entryTypeNode,
range: convertOffsetsToRange(
entryNameNode.start,

View File

@ -10,6 +10,7 @@
*/
import { Diagnostic, DiagnosticWithinFile } from '../common/diagnostic';
import { Uri } from '../common/uri/uri';
import { ScopeType } from './scope';
export enum SymbolCategory {
@ -37,7 +38,7 @@ export interface SymbolInfo {
category: SymbolCategory;
name: string;
fullName: string;
filePath: string;
fileUri: Uri;
isExported: boolean;
typeKnownStatus: TypeKnownStatus;
referenceCount: number;
@ -47,7 +48,7 @@ export interface SymbolInfo {
export interface ModuleInfo {
name: string;
path: string;
uri: Uri;
isExported: boolean;
}
@ -57,10 +58,10 @@ export interface PackageTypeReport {
packageName: string;
moduleName: string;
ignoreExternal: boolean;
packageRootDirectory: string | undefined;
moduleRootDirectory: string | undefined;
packageRootDirectoryUri: Uri | undefined;
moduleRootDirectoryUri: Uri | undefined;
isModuleSingleFile: boolean;
pyTypedPath: string | undefined;
pyTypedPathUri: Uri | undefined;
missingFunctionDocStringCount: number;
missingClassDocStringCount: number;
missingDefaultParamCount: number;
@ -85,20 +86,20 @@ export interface PackageTypeReport {
export function getEmptyReport(
packageName: string,
packageRootDirectory: string,
packageRootUri: Uri,
moduleName: string,
moduleRootDirectory: string,
moduleRootUri: Uri,
isModuleSingleFile: boolean,
ignoreExternal: boolean
) {
const report: PackageTypeReport = {
packageName,
ignoreExternal,
packageRootDirectory,
packageRootDirectoryUri: packageRootUri,
moduleName,
moduleRootDirectory,
moduleRootDirectoryUri: moduleRootUri,
isModuleSingleFile,
pyTypedPath: undefined,
pyTypedPathUri: undefined,
missingFunctionDocStringCount: 0,
missingClassDocStringCount: 0,
missingDefaultParamCount: 0,

View File

@ -14,16 +14,11 @@ import { NullConsole } from '../common/console';
import { assert } from '../common/debug';
import { Diagnostic, DiagnosticAddendum, DiagnosticCategory } from '../common/diagnostic';
import { FullAccessHost } from '../common/fullAccessHost';
import {
combinePaths,
getDirectoryPath,
getFileExtension,
getFileName,
stripFileExtension,
tryStat,
} from '../common/pathUtils';
import { getFileExtension, stripFileExtension } from '../common/pathUtils';
import { ServiceProvider } from '../common/serviceProvider';
import { getEmptyRange, Range } from '../common/textRange';
import { Uri } from '../common/uri/uri';
import { tryStat } from '../common/uri/uriUtils';
import { DeclarationType, FunctionDeclaration, VariableDeclaration } from './declaration';
import { createImportedModuleDescriptor, ImportResolver } from './importResolver';
import {
@ -67,7 +62,7 @@ import {
type PublicSymbolSet = Set<string>;
interface ModuleDirectoryInfo {
moduleDirectory: string;
moduleDirectory: Uri;
isModuleSingleFile: boolean;
}
@ -83,8 +78,8 @@ export class PackageTypeVerifier {
private _packageName: string,
private _ignoreExternal = false
) {
const host = new FullAccessHost(_serviceProvider.fs());
this._configOptions = new ConfigOptions('');
const host = new FullAccessHost(_serviceProvider);
this._configOptions = new ConfigOptions(Uri.empty());
this._configOptions.defaultPythonPlatform = commandLineOptions.pythonPlatform;
this._configOptions.defaultPythonVersion = commandLineOptions.pythonVersion;
@ -99,11 +94,11 @@ export class PackageTypeVerifier {
this._configOptions.evaluateUnknownImportsAsAny = true;
}
this._execEnv = this._configOptions.findExecEnvironment('.');
this._execEnv = this._configOptions.findExecEnvironment(Uri.file('.', _serviceProvider.fs().isCaseSensitive));
this._importResolver = new ImportResolver(
this._serviceProvider,
this._configOptions,
new FullAccessHost(this._serviceProvider.fs())
new FullAccessHost(this._serviceProvider)
);
this._program = new Program(this._importResolver, this._configOptions, this._serviceProvider);
}
@ -117,9 +112,9 @@ export class PackageTypeVerifier {
const report = getEmptyReport(
moduleNameParts[0],
packageDirectoryInfo?.moduleDirectory ?? '',
packageDirectoryInfo?.moduleDirectory ?? Uri.empty(),
trimmedModuleName,
moduleDirectoryInfo?.moduleDirectory ?? '',
moduleDirectoryInfo?.moduleDirectory ?? Uri.empty(),
moduleDirectoryInfo?.isModuleSingleFile ?? false,
this._ignoreExternal
);
@ -134,7 +129,7 @@ export class PackageTypeVerifier {
getEmptyRange()
)
);
} else if (!report.moduleRootDirectory) {
} else if (!report.moduleRootDirectoryUri) {
commonDiagnostics.push(
new Diagnostic(
DiagnosticCategory.Error,
@ -144,14 +139,14 @@ export class PackageTypeVerifier {
);
} else {
let pyTypedInfo: PyTypedInfo | undefined;
if (report.moduleRootDirectory) {
pyTypedInfo = this._getDeepestPyTypedInfo(report.moduleRootDirectory, moduleNameParts);
if (report.moduleRootDirectoryUri) {
pyTypedInfo = this._getDeepestPyTypedInfo(report.moduleRootDirectoryUri, moduleNameParts);
}
// If we couldn't find any "py.typed" info in the module path, search again
// starting at the package root.
if (!pyTypedInfo && report.packageRootDirectory) {
pyTypedInfo = this._getDeepestPyTypedInfo(report.packageRootDirectory, moduleNameParts);
if (!pyTypedInfo && report.packageRootDirectoryUri) {
pyTypedInfo = this._getDeepestPyTypedInfo(report.packageRootDirectoryUri, moduleNameParts);
}
if (!pyTypedInfo) {
@ -159,10 +154,10 @@ export class PackageTypeVerifier {
new Diagnostic(DiagnosticCategory.Error, 'No py.typed file found', getEmptyRange())
);
} else {
report.pyTypedPath = pyTypedInfo.pyTypedPath;
report.pyTypedPathUri = pyTypedInfo.pyTypedPath;
const publicModules = this._getListOfPublicModules(
report.moduleRootDirectory,
report.moduleRootDirectoryUri,
report.isModuleSingleFile,
trimmedModuleName
);
@ -239,12 +234,12 @@ export class PackageTypeVerifier {
}
}
private _getDeepestPyTypedInfo(rootDirectory: string, packageNameParts: string[]) {
private _getDeepestPyTypedInfo(rootDirectory: Uri, packageNameParts: string[]) {
let subNameParts = Array.from(packageNameParts);
// Find the deepest py.typed file that corresponds to the requested submodule.
while (subNameParts.length >= 1) {
const packageSubdir = combinePaths(rootDirectory, ...subNameParts.slice(1));
const packageSubdir = rootDirectory.combinePaths(...subNameParts.slice(1));
const pyTypedInfo = getPyTypedInfo(this._serviceProvider.fs(), packageSubdir);
if (pyTypedInfo) {
return pyTypedInfo;
@ -257,7 +252,11 @@ export class PackageTypeVerifier {
}
private _resolveImport(moduleName: string) {
return this._importResolver.resolveImport('', this._execEnv, createImportedModuleDescriptor(moduleName));
return this._importResolver.resolveImport(
Uri.empty(),
this._execEnv,
createImportedModuleDescriptor(moduleName)
);
}
private _getPublicSymbolsForModule(
@ -268,7 +267,7 @@ export class PackageTypeVerifier {
const importResult = this._resolveImport(moduleName);
if (importResult.isImportFound) {
const modulePath = importResult.resolvedPaths[importResult.resolvedPaths.length - 1];
const modulePath = importResult.resolvedUris[importResult.resolvedUris.length - 1];
this._program.addTrackedFiles([modulePath], /* isThirdPartyImport */ true, /* isInPyTypedPackage */ true);
const sourceFile = this._program.getBoundSourceFile(modulePath);
@ -276,7 +275,7 @@ export class PackageTypeVerifier {
if (sourceFile) {
const module: ModuleInfo = {
name: moduleName,
path: modulePath,
uri: modulePath,
isExported: true,
};
@ -379,15 +378,15 @@ export class PackageTypeVerifier {
)
);
} else {
const modulePath = importResult.resolvedPaths[importResult.resolvedPaths.length - 1];
const modulePath = importResult.resolvedUris[importResult.resolvedUris.length - 1];
const module: ModuleInfo = {
name: moduleName,
path: modulePath,
uri: modulePath,
isExported: true,
};
report.modules.set(modulePath, module);
report.modules.set(modulePath.key, module);
this._program.addTrackedFiles([modulePath], /* isThirdPartyImport */ true, /* isInPyTypedPackage */ true);
const sourceFile = this._program.getBoundSourceFile(modulePath);
@ -413,9 +412,9 @@ export class PackageTypeVerifier {
// Scans the directory structure for a list of public modules
// within the package.
private _getListOfPublicModules(moduleRootPath: string, isModuleSingleFile: boolean, moduleName: string): string[] {
private _getListOfPublicModules(moduleRoot: Uri, isModuleSingleFile: boolean, moduleName: string): string[] {
const publicModules: string[] = [];
this._addPublicModulesRecursive(moduleRootPath, isModuleSingleFile, moduleName, publicModules);
this._addPublicModulesRecursive(moduleRoot, isModuleSingleFile, moduleName, publicModules);
// Make sure modules are unique. There may be duplicates if a ".py" and ".pyi"
// exist for some modules.
@ -433,7 +432,7 @@ export class PackageTypeVerifier {
}
private _addPublicModulesRecursive(
dirPath: string,
dirPath: Uri,
isModuleSingleFile: boolean,
modulePath: string,
publicModules: string[]
@ -444,7 +443,7 @@ export class PackageTypeVerifier {
let isFile = entry.isFile();
let isDirectory = entry.isDirectory();
if (entry.isSymbolicLink()) {
const stat = tryStat(this._serviceProvider.fs(), combinePaths(dirPath, entry.name));
const stat = tryStat(this._serviceProvider.fs(), dirPath.combinePaths(entry.name));
if (stat) {
isFile = stat.isFile();
isDirectory = stat.isDirectory();
@ -477,7 +476,7 @@ export class PackageTypeVerifier {
} else if (isDirectory) {
if (!isPrivateOrProtectedName(entry.name) && this._isLegalModulePartName(entry.name)) {
this._addPublicModulesRecursive(
combinePaths(dirPath, entry.name),
dirPath.combinePaths(entry.name),
isModuleSingleFile,
`${modulePath}.${entry.name}`,
publicModules
@ -572,7 +571,7 @@ export class PackageTypeVerifier {
const decls = symbol.getDeclarations();
const primaryDecl = decls.length > 0 ? decls[decls.length - 1] : undefined;
const declRange = primaryDecl?.range || getEmptyRange();
const declPath = primaryDecl?.path || '';
const declPath = primaryDecl?.uri || Uri.empty();
const symbolCategory = this._getSymbolCategory(symbol, symbolType);
const isExported = publicSymbols.has(fullName);
@ -590,7 +589,7 @@ export class PackageTypeVerifier {
category: symbolCategory,
name,
fullName,
filePath: module.path,
fileUri: Uri.file(module.path, this._serviceProvider.fs().isCaseSensitive),
isExported,
typeKnownStatus: TypeKnownStatus.Known,
referenceCount: 1,
@ -616,7 +615,7 @@ export class PackageTypeVerifier {
const decls = symbol.getDeclarations();
const primaryDecl = decls.length > 0 ? decls[decls.length - 1] : undefined;
const declRange = primaryDecl?.range || getEmptyRange();
const declPath = primaryDecl?.path || '';
const declPath = primaryDecl?.uri || Uri.empty();
const extraInfo = new DiagnosticAddendum();
if (baseSymbolType) {
@ -664,7 +663,7 @@ export class PackageTypeVerifier {
symbolInfo: SymbolInfo,
type: Type,
declRange: Range,
declFilePath: string,
declFileUri: Uri,
publicSymbols: PublicSymbolSet,
skipDocStringCheck = false
): TypeKnownStatus {
@ -677,7 +676,7 @@ export class PackageTypeVerifier {
symbolInfo,
`Type argument ${index + 1} for type alias "${type.typeAliasInfo!.name}" has unknown type`,
declRange,
declFilePath
declFileUri
);
knownStatus = TypeKnownStatus.Unknown;
} else if (isPartlyUnknown(typeArg)) {
@ -687,7 +686,7 @@ export class PackageTypeVerifier {
type.typeAliasInfo!.name
}" has partially unknown type`,
declRange,
declFilePath
declFileUri
);
knownStatus = TypeKnownStatus.PartiallyUnknown;
}
@ -702,7 +701,7 @@ export class PackageTypeVerifier {
'Type is missing type annotation and could be inferred differently by type checkers' +
ambiguousDiag.getString(),
declRange,
declFilePath
declFileUri
);
knownStatus = this._updateKnownStatusIfWorse(knownStatus, TypeKnownStatus.Ambiguous);
}
@ -721,7 +720,7 @@ export class PackageTypeVerifier {
symbolInfo.fullName
}"`,
declRange,
declFilePath
declFileUri
);
knownStatus = this._updateKnownStatusIfWorse(knownStatus, TypeKnownStatus.Unknown);
break;
@ -736,7 +735,7 @@ export class PackageTypeVerifier {
symbolInfo,
subtype,
declRange,
declFilePath,
declFileUri,
publicSymbols
)
);
@ -753,7 +752,7 @@ export class PackageTypeVerifier {
symbolInfo,
overload,
declRange,
declFilePath,
declFileUri,
publicSymbols
)
);
@ -771,7 +770,7 @@ export class PackageTypeVerifier {
publicSymbols,
symbolInfo,
declRange,
declFilePath,
declFileUri,
undefined /* diag */,
skipDocStringCheck
)
@ -821,7 +820,7 @@ export class PackageTypeVerifier {
symbolInfo,
accessType,
getEmptyRange(),
'',
Uri.empty(),
publicSymbols,
skipDocStringCheck
)
@ -847,7 +846,7 @@ export class PackageTypeVerifier {
symbolInfo,
`Type argument ${index + 1} for class "${type.details.name}" has unknown type`,
declRange,
declFilePath
declFileUri
);
knownStatus = this._updateKnownStatusIfWorse(knownStatus, TypeKnownStatus.Unknown);
} else if (isPartlyUnknown(typeArg)) {
@ -859,7 +858,7 @@ export class PackageTypeVerifier {
type.details.name
}" has partially unknown type` + diag.getString(),
declRange,
declFilePath
declFileUri
);
knownStatus = this._updateKnownStatusIfWorse(knownStatus, TypeKnownStatus.PartiallyUnknown);
}
@ -877,7 +876,7 @@ export class PackageTypeVerifier {
symbolInfo,
`Module "${moduleSymbol.fullName}" is partially unknown`,
declRange,
declFilePath
declFileUri
);
knownStatus = this._updateKnownStatusIfWorse(knownStatus, moduleSymbol.typeKnownStatus);
}
@ -899,15 +898,15 @@ export class PackageTypeVerifier {
publicSymbols: PublicSymbolSet,
symbolInfo?: SymbolInfo,
declRange?: Range,
declFilePath?: string,
declFileUri?: Uri,
diag?: DiagnosticAddendum,
skipDocStringCheck = false
): TypeKnownStatus {
let knownStatus = TypeKnownStatus.Known;
// If the file path wasn't provided, try to get it from the type.
if (type.details.declaration && !declFilePath) {
declFilePath = type.details.declaration.path;
if (type.details.declaration && !declFileUri) {
declFileUri = type.details.declaration.uri;
}
type.details.parameters.forEach((param, index) => {
@ -929,7 +928,7 @@ export class PackageTypeVerifier {
symbolInfo,
`Type annotation for parameter "${param.name}" is missing`,
declRange ?? getEmptyRange(),
declFilePath ?? ''
declFileUri ?? Uri.empty()
);
}
diag?.createAddendum().addMessage(`Type annotation for parameter "${param.name}" is missing`);
@ -941,7 +940,7 @@ export class PackageTypeVerifier {
symbolInfo,
`Type of parameter "${param.name}" is unknown`,
declRange ?? getEmptyRange(),
declFilePath ?? ''
declFileUri ?? Uri.empty()
);
diag?.createAddendum().addMessage(`Type of parameter "${param.name}" is unknown`);
}
@ -963,7 +962,7 @@ export class PackageTypeVerifier {
symbolInfo,
`Type of parameter "${param.name}" is partially unknown` + extraInfo.getString(),
declRange ?? getEmptyRange(),
declFilePath ?? ''
declFileUri ?? Uri.empty()
);
}
@ -986,7 +985,7 @@ export class PackageTypeVerifier {
symbolInfo,
`Return type is unknown`,
declRange ?? getEmptyRange(),
declFilePath ?? ''
declFileUri ?? Uri.empty()
);
}
knownStatus = this._updateKnownStatusIfWorse(knownStatus, TypeKnownStatus.Unknown);
@ -1009,7 +1008,7 @@ export class PackageTypeVerifier {
symbolInfo,
`Return type is partially unknown` + extraInfo.getString(),
declRange ?? getEmptyRange(),
declFilePath ?? ''
declFileUri ?? Uri.empty()
);
}
@ -1030,7 +1029,7 @@ export class PackageTypeVerifier {
symbolInfo,
`Return type annotation is missing`,
declRange ?? getEmptyRange(),
declFilePath ?? ''
declFileUri ?? Uri.empty()
);
}
diag?.createAddendum().addMessage(`Return type annotation is missing`);
@ -1060,7 +1059,7 @@ export class PackageTypeVerifier {
symbolInfo,
`No docstring found for function "${symbolInfo.fullName}"`,
declRange ?? getEmptyRange(),
declFilePath ?? ''
declFileUri ?? Uri.empty()
);
}
@ -1074,7 +1073,7 @@ export class PackageTypeVerifier {
symbolInfo,
`One or more default values in function "${symbolInfo.fullName}" is specified as "..."`,
declRange ?? getEmptyRange(),
declFilePath ?? ''
declFileUri ?? Uri.empty()
);
}
@ -1100,7 +1099,7 @@ export class PackageTypeVerifier {
category: SymbolCategory.Class,
name: type.details.name,
fullName: type.details.fullName,
filePath: type.details.filePath,
fileUri: type.details.fileUri,
isExported: publicSymbols.has(type.details.fullName),
typeKnownStatus: TypeKnownStatus.Known,
referenceCount: 1,
@ -1116,7 +1115,7 @@ export class PackageTypeVerifier {
symbolInfo,
`No docstring found for class "${type.details.fullName}"`,
getEmptyRange(),
''
Uri.empty()
);
report.missingClassDocStringCount++;
@ -1155,7 +1154,7 @@ export class PackageTypeVerifier {
// Add information for the metaclass.
if (type.details.effectiveMetaclass) {
if (!isInstantiableClass(type.details.effectiveMetaclass)) {
this._addSymbolError(symbolInfo, `Type of metaclass unknown`, getEmptyRange(), '');
this._addSymbolError(symbolInfo, `Type of metaclass unknown`, getEmptyRange(), Uri.empty());
symbolInfo.typeKnownStatus = this._updateKnownStatusIfWorse(
symbolInfo.typeKnownStatus,
TypeKnownStatus.PartiallyUnknown
@ -1175,7 +1174,7 @@ export class PackageTypeVerifier {
`Type of metaclass "${type.details.effectiveMetaclass}" is partially unknown` +
diag.getString(),
getEmptyRange(),
''
Uri.empty()
);
symbolInfo.typeKnownStatus = this._updateKnownStatusIfWorse(
symbolInfo.typeKnownStatus,
@ -1188,7 +1187,7 @@ export class PackageTypeVerifier {
// Add information for base classes.
type.details.baseClasses.forEach((baseClass) => {
if (!isInstantiableClass(baseClass)) {
this._addSymbolError(symbolInfo, `Type of base class unknown`, getEmptyRange(), '');
this._addSymbolError(symbolInfo, `Type of base class unknown`, getEmptyRange(), Uri.empty());
symbolInfo.typeKnownStatus = this._updateKnownStatusIfWorse(
symbolInfo.typeKnownStatus,
TypeKnownStatus.PartiallyUnknown
@ -1208,7 +1207,7 @@ export class PackageTypeVerifier {
symbolInfo,
`Type of base class "${baseClass.details.fullName}" is partially unknown` + diag.getString(),
getEmptyRange(),
''
Uri.empty()
);
symbolInfo.typeKnownStatus = this._updateKnownStatusIfWorse(
@ -1238,7 +1237,7 @@ export class PackageTypeVerifier {
category: SymbolCategory.Module,
name: type.moduleName,
fullName: type.moduleName,
filePath: type.filePath,
fileUri: type.fileUri,
isExported: publicSymbols.has(type.moduleName),
typeKnownStatus: TypeKnownStatus.Known,
referenceCount: 1,
@ -1448,19 +1447,21 @@ export class PackageTypeVerifier {
private _getDirectoryInfoForModule(moduleName: string): ModuleDirectoryInfo | undefined {
const importResult = this._importResolver.resolveImport(
'',
Uri.empty(),
this._execEnv,
createImportedModuleDescriptor(moduleName)
);
if (importResult.isImportFound) {
const resolvedPath = importResult.resolvedPaths[importResult.resolvedPaths.length - 1];
const resolvedPath = importResult.resolvedUris[importResult.resolvedUris.length - 1];
// If it's a namespace package with no __init__.py(i), use the package
// directory instead.
const moduleDirectory = resolvedPath ? getDirectoryPath(resolvedPath) : importResult.packageDirectory ?? '';
const moduleDirectory = resolvedPath
? resolvedPath.getDirectory()
: importResult.packageDirectory ?? Uri.empty();
let isModuleSingleFile = false;
if (resolvedPath && stripFileExtension(getFileName(resolvedPath)) !== '__init__') {
if (resolvedPath && stripFileExtension(resolvedPath.fileName) !== '__init__') {
isModuleSingleFile = true;
}
@ -1508,17 +1509,17 @@ export class PackageTypeVerifier {
report.symbols.set(symbolInfo.fullName, symbolInfo);
}
private _addSymbolError(symbolInfo: SymbolInfo, message: string, declRange: Range, declFilePath: string) {
private _addSymbolError(symbolInfo: SymbolInfo, message: string, declRange: Range, declUri: Uri) {
symbolInfo.diagnostics.push({
diagnostic: new Diagnostic(DiagnosticCategory.Error, message, declRange),
filePath: declFilePath,
uri: declUri,
});
}
private _addSymbolWarning(symbolInfo: SymbolInfo, message: string, declRange: Range, declFilePath: string) {
private _addSymbolWarning(symbolInfo: SymbolInfo, message: string, declRange: Range, declUri: Uri) {
symbolInfo.diagnostics.push({
diagnostic: new Diagnostic(DiagnosticCategory.Warning, message, declRange),
filePath: declFilePath,
uri: declUri,
});
}

View File

@ -9,46 +9,46 @@
import { getOrAdd } from '../common/collectionUtils';
import { FileSystem } from '../common/fileSystem';
import { ensureTrailingDirectorySeparator, normalizePath, realCasePath } from '../common/pathUtils';
import { Uri } from '../common/uri/uri';
import { ImportResult } from './importResult';
export type ImportPath = { importPath: string | undefined };
export type ImportPath = { importPath: Uri | undefined };
type CacheEntry = { importResult: ImportResult; path: string; importName: string };
type CacheEntry = { importResult: ImportResult; path: Uri; importName: string };
export class ParentDirectoryCache {
private readonly _importChecked = new Map<string, Map<string, ImportPath>>();
private readonly _cachedResults = new Map<string, Map<string, ImportResult>>();
private _libPathCache: string[] | undefined = undefined;
private _libPathCache: Uri[] | undefined = undefined;
constructor(private _importRootGetter: () => string[]) {
constructor(private _importRootGetter: () => Uri[]) {
// empty
}
getImportResult(path: string, importName: string, importResult: ImportResult): ImportResult | undefined {
const result = this._cachedResults.get(importName)?.get(path);
getImportResult(path: Uri, importName: string, importResult: ImportResult): ImportResult | undefined {
const result = this._cachedResults.get(importName)?.get(path.key);
if (result) {
// We already checked for the importName at the path.
// Return the result if succeeded otherwise, return regular import result given.
return result ?? importResult;
}
const checked = this._importChecked.get(importName)?.get(path);
const checked = this._importChecked.get(importName)?.get(path.key);
if (checked) {
// We already checked for the importName at the path.
if (!checked.importPath) {
return importResult;
}
return this._cachedResults.get(importName)?.get(checked.importPath) ?? importResult;
return this._cachedResults.get(importName)?.get(checked.importPath.key) ?? importResult;
}
return undefined;
}
checkValidPath(fs: FileSystem, sourceFilePath: string, root: string): boolean {
if (!sourceFilePath.startsWith(root)) {
checkValidPath(fs: FileSystem, sourceFileUri: Uri, root: Uri): boolean {
if (!sourceFileUri.startsWith(root)) {
// We don't search containing folders for libs.
return false;
}
@ -56,11 +56,11 @@ export class ParentDirectoryCache {
this._libPathCache =
this._libPathCache ??
this._importRootGetter()
.map((r) => ensureTrailingDirectorySeparator(realCasePath(normalizePath(r), fs)))
.map((r) => fs.realCasePath(r))
.filter((r) => r !== root)
.filter((r) => r.startsWith(root));
if (this._libPathCache.some((p) => sourceFilePath.startsWith(p))) {
if (this._libPathCache.some((p) => sourceFileUri.startsWith(p))) {
// Make sure it is not lib folders under user code root.
// ex) .venv folder
return false;
@ -69,13 +69,13 @@ export class ParentDirectoryCache {
return true;
}
checked(path: string, importName: string, importPath: ImportPath) {
getOrAdd(this._importChecked, importName, () => new Map<string, ImportPath>()).set(path, importPath);
checked(path: Uri, importName: string, importPath: ImportPath) {
getOrAdd(this._importChecked, importName, () => new Map<string, ImportPath>()).set(path.key, importPath);
}
add(result: CacheEntry) {
getOrAdd(this._cachedResults, result.importName, () => new Map<string, ImportResult>()).set(
result.path,
result.path.key,
result.importResult
);
}

View File

@ -2718,7 +2718,7 @@ export function getScopeIdForNode(node: ParseNode): string {
}
const fileInfo = AnalyzerNodeInfo.getFileInfo(node);
return `${fileInfo.filePath}.${node.start.toString()}-${name}`;
return `${fileInfo.fileUri.key}.${node.start.toString()}-${name}`;
}
// Walks up the parse tree and finds all scopes that can provide

View File

@ -21,21 +21,14 @@ import { FileDiagnostics } from '../common/diagnosticSink';
import { FileEditAction } from '../common/editAction';
import { EditableProgram, ProgramView } from '../common/extensibility';
import { LogTracker } from '../common/logTracker';
import {
combinePaths,
getDirectoryPath,
getFileName,
getRelativePath,
makeDirectories,
normalizePath,
stripFileExtension,
} from '../common/pathUtils';
import { convertRangeToTextRange } from '../common/positionUtils';
import { ServiceProvider } from '../common/serviceProvider';
import '../common/serviceProviderExtensions';
import { ServiceKeys } from '../common/serviceProviderExtensions';
import { Range, doRangesIntersect } from '../common/textRange';
import { Duration, timingStats } from '../common/timing';
import { Uri } from '../common/uri/uri';
import { makeDirectories } from '../common/uri/uriUtils';
import { ParseResults } from '../parser/parser';
import { AbsoluteModuleDescriptor, ImportLookupResult, LookupImportOptions } from './analyzerFileInfo';
import * as AnalyzerNodeInfo from './analyzerNodeInfo';
@ -73,7 +66,7 @@ export interface MaxAnalysisTime {
}
interface UpdateImportInfo {
path: string;
path: Uri;
isTypeshedFile: boolean;
isThirdPartyImport: boolean;
isPyTypedPresent: boolean;
@ -84,14 +77,13 @@ export type PreCheckCallback = (parseResults: ParseResults, evaluator: TypeEvalu
export interface ISourceFileFactory {
createSourceFile(
serviceProvider: ServiceProvider,
filePath: string,
fileUri: Uri,
moduleName: string,
isThirdPartyImport: boolean,
isThirdPartyPyTypedPresent: boolean,
editMode: SourceFileEditMode,
console?: ConsoleInterface,
logTracker?: LogTracker,
realFilePath?: string,
ipythonMode?: IPythonMode
): SourceFile;
}
@ -105,8 +97,7 @@ export namespace ISourceFileFactory {
export interface OpenFileOptions {
isTracked: boolean;
ipythonMode: IPythonMode;
chainedFilePath: string | undefined;
realFilePath: string | undefined;
chainedFileUri: Uri | undefined;
}
// Track edit mode related information.
@ -195,7 +186,7 @@ export class Program {
return this._console;
}
get rootPath(): string {
get rootPath(): Uri {
return this._configOptions.projectRoot;
}
@ -241,7 +232,7 @@ export class Program {
if (newContents) {
// Create a text document so we can compute the edits.
const textDocument = TextDocument.create(
fileInfo.sourceFile.getFilePath(),
fileInfo.sourceFile.getUri().toString(),
'python',
1,
fileInfo.sourceFile.getFileContent() || ''
@ -249,7 +240,7 @@ export class Program {
// Add an edit action to the list.
edits.push({
filePath: fileInfo.sourceFile.getFilePath(),
fileUri: fileInfo.sourceFile.getUri(),
range: {
start: { line: 0, character: 0 },
end: { line: textDocument.lineCount, character: 0 },
@ -268,7 +259,7 @@ export class Program {
// We don't need to care about file diagnostics since in edit mode
// checker won't run.
v.sourceFile.prepareForClose();
this._removeSourceFileFromListAndMap(v.sourceFile.getFilePath(), i);
this._removeSourceFileFromListAndMap(v.sourceFile.getUri(), i);
}
}
}
@ -299,26 +290,26 @@ export class Program {
}
// Sets the list of tracked files that make up the program.
setTrackedFiles(filePaths: string[]): FileDiagnostics[] {
setTrackedFiles(fileUris: Uri[]): FileDiagnostics[] {
if (this._sourceFileList.length > 0) {
// We need to determine which files to remove from the existing file list.
const newFileMap = new Map<string, string>();
filePaths.forEach((path) => {
newFileMap.set(path, path);
const newFileMap = new Map<string, Uri>();
fileUris.forEach((path) => {
newFileMap.set(path.key, path);
});
// Files that are not in the tracked file list are
// marked as no longer tracked.
this._sourceFileList.forEach((oldFile) => {
const filePath = oldFile.sourceFile.getFilePath();
if (!newFileMap.has(filePath)) {
const fileUri = oldFile.sourceFile.getUri();
if (!newFileMap.has(fileUri.key)) {
oldFile.isTracked = false;
}
});
}
// Add the new files. Only the new items will be added.
this.addTrackedFiles(filePaths);
this.addTrackedFiles(fileUris);
return this._removeUnneededFiles();
}
@ -338,25 +329,25 @@ export class Program {
this._allowedThirdPartyImports = importNames;
}
addTrackedFiles(filePaths: string[], isThirdPartyImport = false, isInPyTypedPackage = false) {
filePaths.forEach((filePath) => {
this.addTrackedFile(filePath, isThirdPartyImport, isInPyTypedPackage);
addTrackedFiles(fileUris: Uri[], isThirdPartyImport = false, isInPyTypedPackage = false) {
fileUris.forEach((fileUri) => {
this.addTrackedFile(fileUri, isThirdPartyImport, isInPyTypedPackage);
});
}
addInterimFile(filePath: string): SourceFileInfo {
addInterimFile(fileUri: Uri): SourceFileInfo {
// Double check not already there.
let fileInfo = this.getSourceFileInfo(filePath);
let fileInfo = this.getSourceFileInfo(fileUri);
if (!fileInfo) {
fileInfo = this._createInterimFileInfo(filePath);
fileInfo = this._createInterimFileInfo(fileUri);
this._addToSourceFileListAndMap(fileInfo);
}
return fileInfo;
}
addTrackedFile(filePath: string, isThirdPartyImport = false, isInPyTypedPackage = false): SourceFile {
let sourceFileInfo = this.getSourceFileInfo(filePath);
const moduleImportInfo = this._getModuleImportInfoForFile(filePath);
addTrackedFile(fileUri: Uri, isThirdPartyImport = false, isInPyTypedPackage = false): SourceFile {
let sourceFileInfo = this.getSourceFileInfo(fileUri);
const moduleImportInfo = this._getModuleImportInfoForFile(fileUri);
const importName = moduleImportInfo.moduleName;
if (sourceFileInfo) {
@ -369,7 +360,7 @@ export class Program {
const sourceFile = this._sourceFileFactory.createSourceFile(
this.serviceProvider,
filePath,
fileUri,
importName,
isThirdPartyImport,
isInPyTypedPackage,
@ -391,23 +382,22 @@ export class Program {
return sourceFile;
}
setFileOpened(filePath: string, version: number | null, contents: string, options?: OpenFileOptions) {
let sourceFileInfo = this.getSourceFileInfo(filePath);
setFileOpened(fileUri: Uri, version: number | null, contents: string, options?: OpenFileOptions) {
let sourceFileInfo = this.getSourceFileInfo(fileUri);
if (!sourceFileInfo) {
const moduleImportInfo = this._getModuleImportInfoForFile(filePath);
const moduleImportInfo = this._getModuleImportInfoForFile(fileUri);
const sourceFile = this._sourceFileFactory.createSourceFile(
this.serviceProvider,
filePath,
fileUri,
moduleImportInfo.moduleName,
/* isThirdPartyImport */ false,
moduleImportInfo.isThirdPartyPyTypedPresent,
this._editModeTracker,
this._console,
this._logTracker,
options?.realFilePath,
options?.ipythonMode ?? IPythonMode.None
);
const chainedFilePath = options?.chainedFilePath;
const chainedFilePath = options?.chainedFileUri;
sourceFileInfo = new SourceFileInfo(
sourceFile,
/* isTypeshedFile */ false,
@ -435,26 +425,26 @@ export class Program {
sourceFileInfo.sourceFile.setClientVersion(version, contents);
}
getChainedFilePath(filePath: string): string | undefined {
const sourceFileInfo = this.getSourceFileInfo(filePath);
return sourceFileInfo?.chainedSourceFile?.sourceFile.getFilePath();
getChainedUri(fileUri: Uri): Uri | undefined {
const sourceFileInfo = this.getSourceFileInfo(fileUri);
return sourceFileInfo?.chainedSourceFile?.sourceFile.getUri();
}
updateChainedFilePath(filePath: string, chainedFilePath: string | undefined) {
const sourceFileInfo = this.getSourceFileInfo(filePath);
updateChainedUri(fileUri: Uri, chainedFileUri: Uri | undefined) {
const sourceFileInfo = this.getSourceFileInfo(fileUri);
if (!sourceFileInfo) {
return;
}
sourceFileInfo.chainedSourceFile = chainedFilePath ? this.getSourceFileInfo(chainedFilePath) : undefined;
sourceFileInfo.chainedSourceFile = chainedFileUri ? this.getSourceFileInfo(chainedFileUri) : undefined;
sourceFileInfo.sourceFile.markDirty();
this._markFileDirtyRecursive(sourceFileInfo, new Set<string>());
verifyNoCyclesInChainedFiles(this, sourceFileInfo);
}
setFileClosed(filePath: string, isTracked?: boolean): FileDiagnostics[] {
const sourceFileInfo = this.getSourceFileInfo(filePath);
setFileClosed(fileUri: Uri, isTracked?: boolean): FileDiagnostics[] {
const sourceFileInfo = this.getSourceFileInfo(fileUri);
if (sourceFileInfo) {
sourceFileInfo.isOpenByClient = false;
sourceFileInfo.isTracked = isTracked ?? sourceFileInfo.isTracked;
@ -493,12 +483,12 @@ export class Program {
}
}
markFilesDirty(filePaths: string[], evenIfContentsAreSame: boolean) {
markFilesDirty(fileUris: Uri[], evenIfContentsAreSame: boolean) {
const markDirtySet = new Set<string>();
filePaths.forEach((filePath) => {
const sourceFileInfo = this.getSourceFileInfo(filePath);
fileUris.forEach((fileUri) => {
const sourceFileInfo = this.getSourceFileInfo(fileUri);
if (sourceFileInfo) {
const fileName = getFileName(filePath);
const fileName = fileUri.fileName;
// Handle builtins and __builtins__ specially. They are implicitly
// included by all source files.
@ -576,9 +566,9 @@ export class Program {
return this._configOptions.functionSignatureDisplay;
}
containsSourceFileIn(folder: string): boolean {
for (const normalizedSourceFilePath of this._sourceFileMap.keys()) {
if (normalizedSourceFilePath.startsWith(folder)) {
containsSourceFileIn(folder: Uri): boolean {
for (const normalizedSourceFilePath of this._sourceFileMap.values()) {
if (normalizedSourceFilePath.sourceFile.getUri().startsWith(folder)) {
return true;
}
}
@ -586,19 +576,19 @@ export class Program {
return false;
}
owns(filePath: string) {
const fileInfo = this.getSourceFileInfo(filePath);
owns(uri: Uri) {
const fileInfo = this.getSourceFileInfo(uri);
if (fileInfo) {
// If we already determined whether the file is tracked or not, don't do it again.
// This will make sure we have consistent look at the state once it is loaded to the memory.
return fileInfo.isTracked;
}
return matchFileSpecs(this._configOptions, filePath);
return matchFileSpecs(this._configOptions, uri);
}
getSourceFile(filePath: string): SourceFile | undefined {
const sourceFileInfo = this.getSourceFileInfo(filePath);
getSourceFile(uri: Uri): SourceFile | undefined {
const sourceFileInfo = this.getSourceFileInfo(uri);
if (!sourceFileInfo) {
return undefined;
}
@ -606,20 +596,23 @@ export class Program {
return sourceFileInfo.sourceFile;
}
getBoundSourceFile(filePath: string): SourceFile | undefined {
return this.getBoundSourceFileInfo(filePath)?.sourceFile;
getBoundSourceFile(uri: Uri): SourceFile | undefined {
return this.getBoundSourceFileInfo(uri)?.sourceFile;
}
getSourceFileInfoList(): readonly SourceFileInfo[] {
return this._sourceFileList;
}
getSourceFileInfo(filePath: string): SourceFileInfo | undefined {
return this._sourceFileMap.get(filePath);
getSourceFileInfo(uri: Uri): SourceFileInfo | undefined {
if (!uri.isEmpty()) {
return this._sourceFileMap.get(uri.key);
}
return undefined;
}
getBoundSourceFileInfo(filePath: string, content?: string, force?: boolean): SourceFileInfo | undefined {
const sourceFileInfo = this.getSourceFileInfo(filePath);
getBoundSourceFileInfo(uri: Uri, content?: string, force?: boolean): SourceFileInfo | undefined {
const sourceFileInfo = this.getSourceFileInfo(uri);
if (!sourceFileInfo) {
return undefined;
}
@ -685,9 +678,9 @@ export class Program {
// Performs parsing and analysis of a single file in the program. If the file is not part of
// the program returns false to indicate analysis was not performed.
analyzeFile(filePath: string, token: CancellationToken = CancellationToken.None): boolean {
analyzeFile(fileUri: Uri, token: CancellationToken = CancellationToken.None): boolean {
return this._runEvaluatorWithCancellationToken(token, () => {
const sourceFileInfo = this.getSourceFileInfo(filePath);
const sourceFileInfo = this.getSourceFileInfo(fileUri);
if (sourceFileInfo && this._checkTypes(sourceFileInfo, token)) {
return true;
}
@ -710,19 +703,19 @@ export class Program {
}
getSourceMapper(
filePath: string,
fileUri: Uri,
token: CancellationToken,
mapCompiled?: boolean,
preferStubs?: boolean
): SourceMapper {
const sourceFileInfo = this.getSourceFileInfo(filePath);
const execEnv = this._configOptions.findExecEnvironment(filePath);
const sourceFileInfo = this.getSourceFileInfo(fileUri);
const execEnv = this._configOptions.findExecEnvironment(fileUri);
return this._createSourceMapper(execEnv, token, sourceFileInfo, mapCompiled, preferStubs);
}
getParseResults(filePath: string): ParseResults | undefined {
getParseResults(fileUri: Uri): ParseResults | undefined {
return this.getBoundSourceFileInfo(
filePath,
fileUri,
/* content */ undefined,
/* force */ true
)?.sourceFile.getParseResults();
@ -746,41 +739,39 @@ export class Program {
sortedFiles.forEach((sfInfo) => {
const checkTimeInMs = sfInfo.sourceFile.getCheckTime()!;
this._console.info(`${checkTimeInMs}ms: ${sfInfo.sourceFile.getFilePath()}`);
this._console.info(`${checkTimeInMs}ms: ${sfInfo.sourceFile.getUri()}`);
});
}
// Prints import dependency information for each of the files in
// the program, skipping any typeshed files.
printDependencies(projectRootDir: string, verbose: boolean) {
printDependencies(projectRootDir: Uri, verbose: boolean) {
const fs = this._importResolver.fileSystem;
const sortedFiles = this._sourceFileList
.filter((s) => !s.isTypeshedFile)
.sort((a, b) => {
return fs.getOriginalFilePath(a.sourceFile.getFilePath()) <
fs.getOriginalFilePath(b.sourceFile.getFilePath())
? 1
: -1;
return fs.getOriginalUri(a.sourceFile.getUri()) < fs.getOriginalUri(b.sourceFile.getUri()) ? 1 : -1;
});
const zeroImportFiles: SourceFile[] = [];
sortedFiles.forEach((sfInfo) => {
this._console.info('');
let filePath = fs.getOriginalFilePath(sfInfo.sourceFile.getFilePath());
const relPath = getRelativePath(filePath, projectRootDir);
const fileUri = fs.getOriginalUri(sfInfo.sourceFile.getUri());
let fileString = fileUri.toString();
const relPath = projectRootDir.getRelativePathComponents(fileUri);
if (relPath) {
filePath = relPath;
fileString = relPath.join('/');
}
this._console.info(`${filePath}`);
this._console.info(`${fileString}`);
this._console.info(
` Imports ${sfInfo.imports.length} ` + `file${sfInfo.imports.length === 1 ? '' : 's'}`
);
if (verbose) {
sfInfo.imports.forEach((importInfo) => {
this._console.info(` ${fs.getOriginalFilePath(importInfo.sourceFile.getFilePath())}`);
this._console.info(` ${fs.getOriginalUri(importInfo.sourceFile.getUri())}`);
});
}
@ -789,7 +780,7 @@ export class Program {
);
if (verbose) {
sfInfo.importedBy.forEach((importInfo) => {
this._console.info(` ${fs.getOriginalFilePath(importInfo.sourceFile.getFilePath())}`);
this._console.info(` ${fs.getOriginalUri(importInfo.sourceFile.getUri())}`);
});
}
@ -804,33 +795,33 @@ export class Program {
`${zeroImportFiles.length} file${zeroImportFiles.length === 1 ? '' : 's'}` + ` not explicitly imported`
);
zeroImportFiles.forEach((importFile) => {
this._console.info(` ${fs.getOriginalFilePath(importFile.getFilePath())}`);
this._console.info(` ${fs.getOriginalUri(importFile.getUri())}`);
});
}
}
writeTypeStub(targetImportPath: string, targetIsSingleFile: boolean, stubPath: string, token: CancellationToken) {
writeTypeStub(targetImportPath: Uri, targetIsSingleFile: boolean, stubPath: Uri, token: CancellationToken) {
for (const sourceFileInfo of this._sourceFileList) {
throwIfCancellationRequested(token);
const filePath = sourceFileInfo.sourceFile.getFilePath();
const fileUri = sourceFileInfo.sourceFile.getUri();
// Generate type stubs only for the files within the target path,
// not any files that the target module happened to import.
const relativePath = getRelativePath(filePath, targetImportPath);
const relativePath = targetImportPath.getRelativePath(fileUri);
if (relativePath !== undefined) {
let typeStubPath = normalizePath(combinePaths(stubPath, relativePath));
let typeStubPath = stubPath.combinePaths(relativePath);
// If the target is a single file implementation, as opposed to
// a package in a directory, transform the name of the type stub
// to __init__.pyi because we're placing it in a directory.
if (targetIsSingleFile) {
typeStubPath = combinePaths(getDirectoryPath(typeStubPath), '__init__.pyi');
typeStubPath = typeStubPath.getDirectory().combinePaths('__init__.pyi');
} else {
typeStubPath = stripFileExtension(typeStubPath) + '.pyi';
typeStubPath = typeStubPath.replaceExtension('.pyi');
}
const typeStubDir = getDirectoryPath(typeStubPath);
const typeStubDir = typeStubPath.getDirectory();
try {
makeDirectories(this.fileSystem, typeStubDir, stubPath);
@ -867,8 +858,8 @@ export class Program {
return evaluator.printType(type, options);
}
getTextOnRange(filePath: string, range: Range, token: CancellationToken): string | undefined {
const sourceFileInfo = this.getSourceFileInfo(filePath);
getTextOnRange(fileUri: Uri, range: Range, token: CancellationToken): string | undefined {
const sourceFileInfo = this.getSourceFileInfo(fileUri);
if (!sourceFileInfo) {
return undefined;
}
@ -904,7 +895,7 @@ export class Program {
);
if (diagnostics !== undefined) {
fileDiagnostics.push({
filePath: sourceFileInfo.sourceFile.getFilePath(),
fileUri: sourceFileInfo.sourceFile.getUri(),
version: sourceFileInfo.sourceFile.getClientVersion(),
diagnostics,
});
@ -921,7 +912,7 @@ export class Program {
// This condition occurs when the user switches from workspace to
// "open files only" mode. Clear all diagnostics for this file.
fileDiagnostics.push({
filePath: sourceFileInfo.sourceFile.getFilePath(),
fileUri: sourceFileInfo.sourceFile.getUri(),
version: sourceFileInfo.sourceFile.getClientVersion(),
diagnostics: [],
});
@ -932,8 +923,8 @@ export class Program {
return fileDiagnostics;
}
getDiagnosticsForRange(filePath: string, range: Range): Diagnostic[] {
const sourceFile = this.getSourceFile(filePath);
getDiagnosticsForRange(fileUri: Uri, range: Range): Diagnostic[] {
const sourceFile = this.getSourceFile(fileUri);
if (!sourceFile) {
return [];
}
@ -958,7 +949,7 @@ export class Program {
// Cloned program will use whatever user files the program currently has.
const userFiles = this.getUserFiles();
program.setTrackedFiles(userFiles.map((i) => i.sourceFile.getFilePath()));
program.setTrackedFiles(userFiles.map((i) => i.sourceFile.getUri()));
program.markAllFilesDirty(/* evenIfContentsAreSame */ true);
// Make sure we keep editor content (open file) which could be different than one in the file system.
@ -969,14 +960,13 @@ export class Program {
}
program.setFileOpened(
fileInfo.sourceFile.getFilePath(),
fileInfo.sourceFile.getUri(),
version,
fileInfo.sourceFile.getOpenFileContents() ?? '',
{
chainedFilePath: fileInfo.chainedSourceFile?.sourceFile.getFilePath(),
chainedFileUri: fileInfo.chainedSourceFile?.sourceFile.getUri(),
ipythonMode: fileInfo.sourceFile.getIPythonMode(),
isTracked: fileInfo.isTracked,
realFilePath: fileInfo.sourceFile.getRealFilePath(),
}
);
}
@ -1071,14 +1061,14 @@ export class Program {
// Clear only if there are any errors for this file.
if (fileInfo.diagnosticsVersion !== undefined) {
fileDiagnostics.push({
filePath: fileInfo.sourceFile.getFilePath(),
fileUri: fileInfo.sourceFile.getUri(),
version: fileInfo.sourceFile.getClientVersion(),
diagnostics: [],
});
}
fileInfo.sourceFile.prepareForClose();
this._removeSourceFileFromListAndMap(fileInfo.sourceFile.getFilePath(), i);
this._removeSourceFileFromListAndMap(fileInfo.sourceFile.getUri(), i);
// Unlink any imports and remove them from the list if
// they are no longer referenced.
@ -1099,14 +1089,14 @@ export class Program {
// Clear if there are any errors for this import.
if (importedFile.diagnosticsVersion !== undefined) {
fileDiagnostics.push({
filePath: importedFile.sourceFile.getFilePath(),
fileUri: importedFile.sourceFile.getUri(),
version: importedFile.sourceFile.getClientVersion(),
diagnostics: [],
});
}
importedFile.sourceFile.prepareForClose();
this._removeSourceFileFromListAndMap(importedFile.sourceFile.getFilePath(), indexToRemove);
this._removeSourceFileFromListAndMap(importedFile.sourceFile.getUri(), indexToRemove);
i--;
}
}
@ -1122,7 +1112,7 @@ export class Program {
// out the errors for the now-closed file.
if (!this._shouldCheckFile(fileInfo) && fileInfo.diagnosticsVersion !== undefined) {
fileDiagnostics.push({
filePath: fileInfo.sourceFile.getFilePath(),
fileUri: fileInfo.sourceFile.getUri(),
version: fileInfo.sourceFile.getClientVersion(),
diagnostics: [],
});
@ -1165,14 +1155,14 @@ export class Program {
return true;
}
const filePath = fileInfo.sourceFile.getFilePath();
const fileUri = fileInfo.sourceFile.getUri();
// Avoid infinite recursion.
if (recursionSet.has(filePath)) {
if (recursionSet.has(fileUri.key)) {
return false;
}
recursionSet.add(filePath);
recursionSet.add(fileUri.key);
for (const importerInfo of fileInfo.importedBy) {
if (this._isImportNeededRecursive(importerInfo, recursionSet)) {
@ -1194,16 +1184,16 @@ export class Program {
this._importResolver,
execEnv,
this._evaluator!,
(stubFilePath: string, implFilePath: string) => {
let stubFileInfo = this.getSourceFileInfo(stubFilePath);
(stubFileUri: Uri, implFileUri: Uri) => {
let stubFileInfo = this.getSourceFileInfo(stubFileUri);
if (!stubFileInfo) {
// Special case for import statement like "import X.Y". The SourceFile
// for X might not be in memory since import `X.Y` only brings in Y.
stubFileInfo = this.addInterimFile(stubFilePath);
stubFileInfo = this.addInterimFile(stubFileUri);
}
this._addShadowedFile(stubFileInfo, implFilePath);
return this.getBoundSourceFile(implFilePath);
this._addShadowedFile(stubFileInfo, implFileUri);
return this.getBoundSourceFile(implFileUri);
},
(f) => {
let fileInfo = this.getBoundSourceFileInfo(f);
@ -1296,6 +1286,10 @@ export class Program {
return true;
}
private _getSourceFileInfoFromKey(key: string) {
return this._sourceFileMap.get(key);
}
private _updateSourceFileImports(sourceFileInfo: SourceFileInfo, options: ConfigOptions): SourceFileInfo[] {
const filesAdded: SourceFileInfo[] = [];
@ -1338,9 +1332,9 @@ export class Program {
if (sourceFileInfo.chainedSourceFile.sourceFile.isFileDeleted()) {
sourceFileInfo.chainedSourceFile = undefined;
} else {
const filePath = sourceFileInfo.chainedSourceFile.sourceFile.getFilePath();
newImportPathMap.set(filePath, {
path: filePath,
const fileUri = sourceFileInfo.chainedSourceFile.sourceFile.getUri();
newImportPathMap.set(fileUri.key, {
path: fileUri,
isTypeshedFile: false,
isThirdPartyImport: false,
isPyTypedPresent: false,
@ -1351,12 +1345,12 @@ export class Program {
imports.forEach((importResult) => {
if (importResult.isImportFound) {
if (this._isImportAllowed(sourceFileInfo, importResult, importResult.isStubFile)) {
if (importResult.resolvedPaths.length > 0) {
const filePath = importResult.resolvedPaths[importResult.resolvedPaths.length - 1];
if (filePath) {
if (importResult.resolvedUris.length > 0) {
const fileUri = importResult.resolvedUris[importResult.resolvedUris.length - 1];
if (!fileUri.isEmpty()) {
const thirdPartyTypeInfo = getThirdPartyImportInfo(importResult);
newImportPathMap.set(filePath, {
path: filePath,
newImportPathMap.set(fileUri.key, {
path: fileUri,
isTypeshedFile:
!!importResult.isStdlibTypeshedFile || !!importResult.isThirdPartyTypeshedFile,
isThirdPartyImport: thirdPartyTypeInfo.isThirdPartyImport,
@ -1370,8 +1364,8 @@ export class Program {
if (this._isImportAllowed(sourceFileInfo, importResult, implicitImport.isStubFile)) {
if (!implicitImport.isNativeLib) {
const thirdPartyTypeInfo = getThirdPartyImportInfo(importResult);
newImportPathMap.set(implicitImport.path, {
path: implicitImport.path,
newImportPathMap.set(implicitImport.uri.key, {
path: implicitImport.uri,
isTypeshedFile:
!!importResult.isStdlibTypeshedFile || !!importResult.isThirdPartyTypeshedFile,
isThirdPartyImport: thirdPartyTypeInfo.isThirdPartyImport,
@ -1391,7 +1385,7 @@ export class Program {
if (options.verboseOutput) {
this._console.info(
`Could not resolve source for '${importResult.importName}' ` +
`in file '${sourceFileInfo.sourceFile.getFilePath()}'`
`in file '${sourceFileInfo.sourceFile.getUri().toUserVisibleString()}'`
);
if (importResult.nonStubImportResult.importFailureInfo) {
@ -1405,7 +1399,7 @@ export class Program {
} else if (options.verboseOutput) {
this._console.info(
`Could not import '${importResult.importName}' ` +
`in file '${sourceFileInfo.sourceFile.getFilePath()}'`
`in file '${sourceFileInfo.sourceFile.getUri().toUserVisibleString()}'`
);
if (importResult.importFailureInfo) {
importResult.importFailureInfo.forEach((diag) => {
@ -1417,17 +1411,17 @@ export class Program {
const updatedImportMap = new Map<string, SourceFileInfo>();
sourceFileInfo.imports.forEach((importInfo) => {
const oldFilePath = importInfo.sourceFile.getFilePath();
const oldFilePath = importInfo.sourceFile.getUri();
// A previous import was removed.
if (!newImportPathMap.has(oldFilePath)) {
if (!newImportPathMap.has(oldFilePath.key)) {
importInfo.mutate((s) => {
s.importedBy = s.importedBy.filter(
(fi) => fi.sourceFile.getFilePath() !== sourceFileInfo.sourceFile.getFilePath()
(fi) => !fi.sourceFile.getUri().equals(sourceFileInfo.sourceFile.getUri())
);
});
} else {
updatedImportMap.set(oldFilePath, importInfo);
updatedImportMap.set(oldFilePath.key, importInfo);
}
});
@ -1469,9 +1463,9 @@ export class Program {
// Update the imports list. It should now map the set of imports
// specified by the source file.
sourceFileInfo.mutate((s) => (s.imports = []));
newImportPathMap.forEach((_, path) => {
if (this.getSourceFileInfo(path)) {
sourceFileInfo.mutate((s) => s.imports.push(this.getSourceFileInfo(path)!));
newImportPathMap.forEach((_, key) => {
if (this._getSourceFileInfoFromKey(key)) {
sourceFileInfo.mutate((s) => s.imports.push(this._getSourceFileInfoFromKey(key)!));
}
});
@ -1480,26 +1474,29 @@ export class Program {
sourceFileInfo.builtinsImport = undefined;
const builtinsImport = sourceFileInfo.sourceFile.getBuiltinsImport();
if (builtinsImport && builtinsImport.isImportFound) {
const resolvedBuiltinsPath = builtinsImport.resolvedPaths[builtinsImport.resolvedPaths.length - 1];
const resolvedBuiltinsPath = builtinsImport.resolvedUris[builtinsImport.resolvedUris.length - 1];
sourceFileInfo.builtinsImport = this.getSourceFileInfo(resolvedBuiltinsPath);
}
return filesAdded;
}
private _removeSourceFileFromListAndMap(filePath: string, indexToRemove: number) {
this._sourceFileMap.delete(filePath);
private _removeSourceFileFromListAndMap(fileUri: Uri, indexToRemove: number) {
this._sourceFileMap.delete(fileUri.key);
this._sourceFileList.splice(indexToRemove, 1);
}
private _addToSourceFileListAndMap(fileInfo: SourceFileInfo) {
const filePath = fileInfo.sourceFile.getFilePath();
const fileUri = fileInfo.sourceFile.getUri();
// We should never add a file with the same path twice.
assert(!this._sourceFileMap.has(filePath));
assert(!this._sourceFileMap.has(fileUri.key));
// We should never have an empty URI for a source file.
assert(!fileInfo.sourceFile.getUri().isEmpty());
this._sourceFileList.push(fileInfo);
this._sourceFileMap.set(filePath, fileInfo);
this._sourceFileMap.set(fileUri.key, fileInfo);
}
private static _getPrintTypeFlags(configOptions: ConfigOptions): PrintTypeFlags {
@ -1528,7 +1525,7 @@ export class Program {
return flags;
}
private _getModuleImportInfoForFile(filePath: string) {
private _getModuleImportInfoForFile(fileUri: Uri) {
// We allow illegal module names (e.g. names that include "-" in them)
// because we want a unique name for each module even if it cannot be
// imported through an "import" statement. It's important to have a
@ -1536,7 +1533,7 @@ export class Program {
// name. The type checker uses the fully-qualified (unique) module name
// to differentiate between such types.
const moduleNameAndType = this._importResolver.getModuleNameForImport(
filePath,
fileUri,
this._configOptions.getDefaultExecEnvironment(),
/* allowIllegalModuleName */ true,
/* detectPyTyped */ true
@ -1549,7 +1546,7 @@ export class Program {
// it "shadows" a type stub file for purposes of finding doc strings and definitions.
// We need to track the relationship so if the original type stub is removed from the
// program, we can remove the corresponding shadowed file and any files it imports.
private _addShadowedFile(stubFile: SourceFileInfo, shadowImplPath: string): SourceFile {
private _addShadowedFile(stubFile: SourceFileInfo, shadowImplPath: Uri): SourceFile {
let shadowFileInfo = this.getSourceFileInfo(shadowImplPath);
if (!shadowFileInfo) {
@ -1567,11 +1564,11 @@ export class Program {
return shadowFileInfo.sourceFile;
}
private _createInterimFileInfo(filePath: string) {
const moduleImportInfo = this._getModuleImportInfoForFile(filePath);
private _createInterimFileInfo(fileUri: Uri) {
const moduleImportInfo = this._getModuleImportInfoForFile(fileUri);
const sourceFile = this._sourceFileFactory.createSourceFile(
this.serviceProvider,
filePath,
fileUri,
moduleImportInfo.moduleName,
/* isThirdPartyImport */ false,
/* isInPyTypedPackage */ false,
@ -1673,8 +1670,8 @@ export class Program {
let nextImplicitImport = this._getImplicitImports(fileToAnalyze);
while (nextImplicitImport) {
const implicitPath = nextImplicitImport.sourceFile.getFilePath();
if (implicitSet.has(implicitPath)) {
const implicitPath = nextImplicitImport.sourceFile.getUri();
if (implicitSet.has(implicitPath.key)) {
// We've found a cycle. Break out of the loop.
debug.fail(
this.serviceProvider
@ -1683,7 +1680,7 @@ export class Program {
);
}
implicitSet.add(implicitPath);
implicitSet.add(implicitPath.key);
implicitImports.push(nextImplicitImport);
this._parseFile(nextImplicitImport, /* content */ undefined, skipFileNeededCheck);
@ -1773,27 +1770,27 @@ export class Program {
}
private _lookUpImport = (
filePathOrModule: string | AbsoluteModuleDescriptor,
fileUriOrModule: Uri | AbsoluteModuleDescriptor,
options?: LookupImportOptions
): ImportLookupResult | undefined => {
let sourceFileInfo: SourceFileInfo | undefined;
if (typeof filePathOrModule === 'string') {
sourceFileInfo = this.getSourceFileInfo(filePathOrModule);
if (Uri.isUri(fileUriOrModule)) {
sourceFileInfo = this.getSourceFileInfo(fileUriOrModule);
} else {
// Resolve the import.
const importResult = this._importResolver.resolveImport(
filePathOrModule.importingFilePath,
this._configOptions.findExecEnvironment(filePathOrModule.importingFilePath),
fileUriOrModule.importingFileUri,
this._configOptions.findExecEnvironment(fileUriOrModule.importingFileUri),
{
leadingDots: 0,
nameParts: filePathOrModule.nameParts,
nameParts: fileUriOrModule.nameParts,
importedSymbols: undefined,
}
);
if (importResult.isImportFound && !importResult.isNativeLib && importResult.resolvedPaths.length > 0) {
const resolvedPath = importResult.resolvedPaths[importResult.resolvedPaths.length - 1];
if (importResult.isImportFound && !importResult.isNativeLib && importResult.resolvedUris.length > 0) {
const resolvedPath = importResult.resolvedUris[importResult.resolvedUris.length - 1];
if (resolvedPath) {
// See if the source file already exists in the program.
sourceFileInfo = this.getSourceFileInfo(resolvedPath);
@ -1872,7 +1869,7 @@ export class Program {
}
private _checkTypes(fileToCheck: SourceFileInfo, token: CancellationToken, chainedByList?: SourceFileInfo[]) {
return this._logTracker.log(`analyzing: ${fileToCheck.sourceFile.getFilePath()}`, (logState) => {
return this._logTracker.log(`analyzing: ${fileToCheck.sourceFile.getUri()}`, (logState) => {
// If the file isn't needed because it was eliminated from the
// transitive closure or deleted, skip the file rather than wasting
// time on it.
@ -1911,7 +1908,7 @@ export class Program {
}
if (boundFile) {
const execEnv = this._configOptions.findExecEnvironment(fileToCheck.sourceFile.getFilePath());
const execEnv = this._configOptions.findExecEnvironment(fileToCheck.sourceFile.getUri());
fileToCheck.sourceFile.check(
this.configOptions,
this._importResolver,
@ -2028,8 +2025,8 @@ export class Program {
) {
// If the file is already in the closure map, we found a cyclical
// dependency. Don't recur further.
const filePath = file.sourceFile.getFilePath();
if (closureMap.has(filePath)) {
const fileUri = file.sourceFile.getUri();
if (closureMap.has(fileUri.key)) {
return;
}
@ -2041,7 +2038,7 @@ export class Program {
}
// Add the file to the closure map.
closureMap.set(filePath, file);
closureMap.set(fileUri.key, file);
// If this file hasn't already been parsed, parse it now. This will
// discover any files it imports. Skip this if the file is part
@ -2074,13 +2071,13 @@ export class Program {
return false;
}
const filePath = sourceFileInfo.sourceFile.getFilePath();
const fileUri = sourceFileInfo.sourceFile.getUri();
filesVisited.set(filePath, sourceFileInfo);
filesVisited.set(fileUri.key, sourceFileInfo);
let detectedCycle = false;
if (dependencyMap.has(filePath)) {
if (dependencyMap.has(fileUri.key)) {
// We detect a cycle (partial or full). A full cycle is one that is
// rooted in the file at the start of our dependency chain. A partial
// cycle loops back on some other file in the dependency chain. We
@ -2097,7 +2094,7 @@ export class Program {
} else {
// If we've already checked this dependency along
// some other path, we can skip it.
if (dependencyMap.has(filePath)) {
if (dependencyMap.has(fileUri.key)) {
return false;
}
@ -2105,7 +2102,7 @@ export class Program {
// (for ordering information). Set the dependency map
// entry to true to indicate that we're actively exploring
// that dependency.
dependencyMap.set(filePath, true);
dependencyMap.set(fileUri.key, true);
dependencyChain.push(sourceFileInfo);
for (const imp of sourceFileInfo.imports) {
@ -2116,7 +2113,7 @@ export class Program {
// Set the dependencyMap entry to false to indicate that we have
// already explored this file and don't need to explore it again.
dependencyMap.set(filePath, false);
dependencyMap.set(fileUri.key, false);
dependencyChain.pop();
}
@ -2126,7 +2123,7 @@ export class Program {
private _logImportCycle(dependencyChain: SourceFileInfo[]) {
const circDep = new CircularDependency();
dependencyChain.forEach((sourceFileInfo) => {
circDep.appendPath(sourceFileInfo.sourceFile.getFilePath());
circDep.appendPath(sourceFileInfo.sourceFile.getUri());
});
circDep.normalizeOrder();
@ -2137,15 +2134,15 @@ export class Program {
}
private _markFileDirtyRecursive(sourceFileInfo: SourceFileInfo, markSet: Set<string>, forceRebinding = false) {
const filePath = sourceFileInfo.sourceFile.getFilePath();
const fileUri = sourceFileInfo.sourceFile.getUri();
// Don't mark it again if it's already been visited.
if (markSet.has(filePath)) {
if (markSet.has(fileUri.key)) {
return;
}
sourceFileInfo.sourceFile.markReanalysisRequired(forceRebinding);
markSet.add(filePath);
markSet.add(fileUri.key);
sourceFileInfo.importedBy.forEach((dep) => {
// Changes on chained source file can change symbols in the symbol table and

View File

@ -70,7 +70,7 @@ export function createProperty(
decoratorType.details.name,
getClassFullName(decoratorNode, fileInfo.moduleName, `__property_${fget.details.name}`),
fileInfo.moduleName,
fileInfo.filePath,
fileInfo.fileUri,
ClassTypeFlags.PropertyClass | ClassTypeFlags.BuiltInClass,
typeSourceId,
/* declaredMetaclass */ undefined,
@ -171,7 +171,7 @@ export function clonePropertyWithSetter(
classType.details.name,
classType.details.fullName,
classType.details.moduleName,
getFileInfo(errorNode).filePath,
getFileInfo(errorNode).fileUri,
flagsToClone,
classType.details.typeSourceId,
classType.details.declaredMetaclass,
@ -228,7 +228,7 @@ export function clonePropertyWithDeleter(
classType.details.name,
classType.details.fullName,
classType.details.moduleName,
getFileInfo(errorNode).filePath,
getFileInfo(errorNode).fileUri,
classType.details.flags,
classType.details.typeSourceId,
classType.details.declaredMetaclass,

View File

@ -8,24 +8,23 @@
*/
import { FileSystem } from '../common/fileSystem';
import { combinePaths, isDirectory, isFile } from '../common/pathUtils';
import { Uri } from '../common/uri/uri';
import { isDirectory, isFile } from '../common/uri/uriUtils';
export interface PyTypedInfo {
pyTypedPath: string;
pyTypedPath: Uri;
isPartiallyTyped: boolean;
}
const _pyTypedFileName = 'py.typed';
export function getPyTypedInfo(fileSystem: FileSystem, dirPath: string): PyTypedInfo | undefined {
export function getPyTypedInfo(fileSystem: FileSystem, dirPath: Uri): PyTypedInfo | undefined {
if (!fileSystem.existsSync(dirPath) || !isDirectory(fileSystem, dirPath)) {
return undefined;
}
let isPartiallyTyped = false;
const pyTypedPath = combinePaths(dirPath, _pyTypedFileName);
const pyTypedPath = dirPath.pytypedUri;
if (!fileSystem.existsSync(dirPath) || !isFile(fileSystem, pyTypedPath)) {
if (!fileSystem.existsSync(pyTypedPath) || !isFile(fileSystem, pyTypedPath)) {
return undefined;
}

View File

@ -12,43 +12,32 @@ import { compareComparableValues } from '../common/core';
import { FileSystem } from '../common/fileSystem';
import { Host } from '../common/host';
import * as pathConsts from '../common/pathConsts';
import {
combinePaths,
containsPath,
ensureTrailingDirectorySeparator,
getDirectoryPath,
getFileSystemEntries,
isDirectory,
normalizePath,
tryStat,
} from '../common/pathUtils';
import { versionToString } from '../common/pythonVersion';
import { PythonVersion } from '../common/pythonVersion';
import { PythonVersion, versionToString } from '../common/pythonVersion';
import { Uri } from '../common/uri/uri';
import { getFileSystemEntries, isDirectory, tryStat } from '../common/uri/uriUtils';
export interface PythonPathResult {
paths: string[];
prefix: string;
paths: Uri[];
prefix: Uri | undefined;
}
export const stdLibFolderName = 'stdlib';
export const thirdPartyFolderName = 'stubs';
export function getTypeShedFallbackPath(fs: FileSystem) {
let moduleDirectory = fs.getModulePath();
if (!moduleDirectory) {
const moduleDirectory = fs.getModulePath();
if (!moduleDirectory || moduleDirectory.isEmpty()) {
return undefined;
}
moduleDirectory = getDirectoryPath(ensureTrailingDirectorySeparator(normalizePath(moduleDirectory)));
const typeshedPath = combinePaths(moduleDirectory, pathConsts.typeshedFallback);
const typeshedPath = moduleDirectory.combinePaths(pathConsts.typeshedFallback);
if (fs.existsSync(typeshedPath)) {
return fs.realCasePath(typeshedPath);
}
// In the debug version of Pyright, the code is one level
// deeper, so we need to look one level up for the typeshed fallback.
const debugTypeshedPath = combinePaths(getDirectoryPath(moduleDirectory), pathConsts.typeshedFallback);
const debugTypeshedPath = moduleDirectory.getDirectory().combinePaths(pathConsts.typeshedFallback);
if (fs.existsSync(debugTypeshedPath)) {
return fs.realCasePath(debugTypeshedPath);
}
@ -56,8 +45,8 @@ export function getTypeShedFallbackPath(fs: FileSystem) {
return undefined;
}
export function getTypeshedSubdirectory(typeshedPath: string, isStdLib: boolean) {
return combinePaths(typeshedPath, isStdLib ? stdLibFolderName : thirdPartyFolderName);
export function getTypeshedSubdirectory(typeshedPath: Uri, isStdLib: boolean) {
return typeshedPath.combinePaths(isStdLib ? stdLibFolderName : thirdPartyFolderName);
}
export function findPythonSearchPaths(
@ -66,21 +55,21 @@ export function findPythonSearchPaths(
host: Host,
importFailureInfo: string[],
includeWatchPathsOnly?: boolean | undefined,
workspaceRoot?: string | undefined
): string[] | undefined {
workspaceRoot?: Uri | undefined
): Uri[] | undefined {
importFailureInfo.push('Finding python search paths');
if (configOptions.venvPath !== undefined && configOptions.venv) {
const venvDir = configOptions.venv;
const venvPath = combinePaths(configOptions.venvPath, venvDir);
const venvPath = configOptions.venvPath.combinePaths(venvDir);
const foundPaths: string[] = [];
const sitePackagesPaths: string[] = [];
const foundPaths: Uri[] = [];
const sitePackagesPaths: Uri[] = [];
[pathConsts.lib, pathConsts.lib64, pathConsts.libAlternate].forEach((libPath) => {
const sitePackagesPath = findSitePackagesPath(
fs,
combinePaths(venvPath, libPath),
venvPath.combinePaths(libPath),
configOptions.defaultPythonVersion,
importFailureInfo
);
@ -115,11 +104,7 @@ export function findPythonSearchPaths(
const pathResult = host.getPythonSearchPaths(configOptions.pythonPath, importFailureInfo);
if (includeWatchPathsOnly && workspaceRoot) {
const paths = pathResult.paths
.filter(
(p) =>
!containsPath(workspaceRoot, p, /* ignoreCase */ true) ||
containsPath(pathResult.prefix, p, /* ignoreCase */ true)
)
.filter((p) => !p.startsWith(workspaceRoot) || p.startsWith(pathResult.prefix))
.map((p) => fs.realCasePath(p));
return paths;
@ -135,10 +120,10 @@ export function isPythonBinary(p: string): boolean {
function findSitePackagesPath(
fs: FileSystem,
libPath: string,
libPath: Uri,
pythonVersion: PythonVersion | undefined,
importFailureInfo: string[]
): string | undefined {
): Uri | undefined {
if (fs.existsSync(libPath)) {
importFailureInfo.push(`Found path '${libPath}'; looking for ${pathConsts.sitePackages}`);
} else {
@ -146,7 +131,7 @@ function findSitePackagesPath(
return undefined;
}
const sitePackagesPath = combinePaths(libPath, pathConsts.sitePackages);
const sitePackagesPath = libPath.combinePaths(pathConsts.sitePackages);
if (fs.existsSync(sitePackagesPath)) {
importFailureInfo.push(`Found path '${sitePackagesPath}'`);
return sitePackagesPath;
@ -160,8 +145,8 @@ function findSitePackagesPath(
// Candidate directories start with "python3.".
const candidateDirs = entries.directories.filter((dirName) => {
if (dirName.startsWith('python3.')) {
const dirPath = combinePaths(libPath, dirName, pathConsts.sitePackages);
if (dirName.fileName.startsWith('python3.')) {
const dirPath = dirName.combinePaths(pathConsts.sitePackages);
return fs.existsSync(dirPath);
}
return false;
@ -170,9 +155,11 @@ function findSitePackagesPath(
// If there is a python3.X directory (where 3.X matches the configured python
// version), prefer that over other python directories.
if (pythonVersion) {
const preferredDir = candidateDirs.find((dirName) => dirName === `python${versionToString(pythonVersion)}`);
const preferredDir = candidateDirs.find(
(dirName) => dirName.fileName === `python${versionToString(pythonVersion)}`
);
if (preferredDir) {
const dirPath = combinePaths(libPath, preferredDir, pathConsts.sitePackages);
const dirPath = preferredDir.combinePaths(pathConsts.sitePackages);
importFailureInfo.push(`Found path '${dirPath}'`);
return dirPath;
}
@ -182,7 +169,7 @@ function findSitePackagesPath(
// first directory that starts with "python". Most of the time, there will be
// only one.
if (candidateDirs.length > 0) {
const dirPath = combinePaths(libPath, candidateDirs[0], pathConsts.sitePackages);
const dirPath = candidateDirs[0].combinePaths(pathConsts.sitePackages);
importFailureInfo.push(`Found path '${dirPath}'`);
return dirPath;
}
@ -190,8 +177,8 @@ function findSitePackagesPath(
return undefined;
}
export function getPathsFromPthFiles(fs: FileSystem, parentDir: string): string[] {
const searchPaths: string[] = [];
export function getPathsFromPthFiles(fs: FileSystem, parentDir: Uri): Uri[] {
const searchPaths: Uri[] = [];
// Get a list of all *.pth files within the specified directory.
const pthFiles = fs
@ -200,7 +187,7 @@ export function getPathsFromPthFiles(fs: FileSystem, parentDir: string): string[
.sort((a, b) => compareComparableValues(a.name, b.name));
pthFiles.forEach((pthFile) => {
const filePath = fs.realCasePath(combinePaths(parentDir, pthFile.name));
const filePath = fs.realCasePath(parentDir.combinePaths(pthFile.name));
const fileStats = tryStat(fs, filePath);
// Skip all files that are much larger than expected.
@ -210,7 +197,7 @@ export function getPathsFromPthFiles(fs: FileSystem, parentDir: string): string[
lines.forEach((line) => {
const trimmedLine = line.trim();
if (trimmedLine.length > 0 && !trimmedLine.startsWith('#') && !trimmedLine.match(/^import\s/)) {
const pthPath = combinePaths(parentDir, trimmedLine);
const pthPath = parentDir.combinePaths(trimmedLine);
if (fs.existsSync(pthPath) && isDirectory(fs, pthPath)) {
searchPaths.push(fs.realCasePath(pthPath));
}
@ -222,8 +209,8 @@ export function getPathsFromPthFiles(fs: FileSystem, parentDir: string): string[
return searchPaths;
}
function addPathIfUnique(pathList: string[], pathToAdd: string) {
if (!pathList.some((path) => path === pathToAdd)) {
function addPathIfUnique(pathList: Uri[], pathToAdd: Uri) {
if (!pathList.some((path) => path.key === pathToAdd.key)) {
pathList.push(pathToAdd);
return true;
}

File diff suppressed because it is too large Load Diff

View File

@ -20,13 +20,14 @@ import { DiagnosticSink, TextRangeDiagnosticSink } from '../common/diagnosticSin
import { ServiceProvider } from '../common/extensibility';
import { FileSystem } from '../common/fileSystem';
import { LogTracker, getPathForLogging } from '../common/logTracker';
import { getFileName, normalizeSlashes, stripFileExtension } from '../common/pathUtils';
import { stripFileExtension } from '../common/pathUtils';
import { convertOffsetsToRange, convertTextRangeToRange } from '../common/positionUtils';
import { ServiceKeys } from '../common/serviceProviderExtensions';
import * as StringUtils from '../common/stringUtils';
import { Range, TextRange, getEmptyRange } from '../common/textRange';
import { TextRangeCollection } from '../common/textRangeCollection';
import { Duration, timingStats } from '../common/timing';
import { Uri } from '../common/uri/uri';
import { Localizer } from '../localization/localize';
import { ModuleNode } from '../parser/parseNodes';
import { IParser, ModuleImport, ParseOptions, ParseResults, Parser } from '../parser/parser';
@ -179,12 +180,9 @@ export class SourceFile {
// Console interface to use for debugging.
private _console: ConsoleInterface;
// File path unique to this file within the workspace. May not represent
// Uri unique to this file within the workspace. May not represent
// a real file on disk.
private readonly _filePath: string;
// File path on disk. May not be unique.
private readonly _realFilePath: string;
private readonly _uri: Uri;
// Period-delimited import path for the module.
private _moduleName: string;
@ -232,46 +230,42 @@ export class SourceFile {
constructor(
readonly serviceProvider: ServiceProvider,
filePath: string,
uri: Uri,
moduleName: string,
isThirdPartyImport: boolean,
isThirdPartyPyTypedPresent: boolean,
editMode: SourceFileEditMode,
console?: ConsoleInterface,
logTracker?: LogTracker,
realFilePath?: string,
ipythonMode?: IPythonMode
) {
this.fileSystem = serviceProvider.get(ServiceKeys.fs);
this._console = console || new StandardConsole();
this._editMode = editMode;
this._filePath = filePath;
this._realFilePath = realFilePath ?? filePath;
this._uri = uri;
this._moduleName = moduleName;
this._isStubFile = filePath.endsWith('.pyi');
this._isStubFile = uri.hasExtension('.pyi');
this._isThirdPartyImport = isThirdPartyImport;
this._isThirdPartyPyTypedPresent = isThirdPartyPyTypedPresent;
const fileName = getFileName(filePath);
const fileName = uri.fileName;
this._isTypingStubFile =
this._isStubFile &&
(this._filePath.endsWith(normalizeSlashes('stdlib/typing.pyi')) || fileName === 'typing_extensions.pyi');
this._isStubFile && (this._uri.pathEndsWith('stdlib/typing.pyi') || fileName === 'typing_extensions.pyi');
this._isTypingExtensionsStubFile = this._isStubFile && fileName === 'typing_extensions.pyi';
this._isTypeshedStubFile =
this._isStubFile && this._filePath.endsWith(normalizeSlashes('stdlib/_typeshed/__init__.pyi'));
this._isTypeshedStubFile = this._isStubFile && this._uri.pathEndsWith('stdlib/_typeshed/__init__.pyi');
this._isBuiltInStubFile = false;
if (this._isStubFile) {
if (
this._filePath.endsWith(normalizeSlashes('stdlib/collections/__init__.pyi')) ||
this._filePath.endsWith(normalizeSlashes('stdlib/asyncio/futures.pyi')) ||
this._filePath.endsWith(normalizeSlashes('stdlib/asyncio/tasks.pyi')) ||
this._filePath.endsWith(normalizeSlashes('stdlib/builtins.pyi')) ||
this._filePath.endsWith(normalizeSlashes('stdlib/_importlib_modulespec.pyi')) ||
this._filePath.endsWith(normalizeSlashes('stdlib/dataclasses.pyi')) ||
this._filePath.endsWith(normalizeSlashes('stdlib/abc.pyi')) ||
this._filePath.endsWith(normalizeSlashes('stdlib/enum.pyi')) ||
this._filePath.endsWith(normalizeSlashes('stdlib/queue.pyi')) ||
this._filePath.endsWith(normalizeSlashes('stdlib/types.pyi'))
this._uri.pathEndsWith('stdlib/collections/__init__.pyi') ||
this._uri.pathEndsWith('stdlib/asyncio/futures.pyi') ||
this._uri.pathEndsWith('stdlib/asyncio/tasks.pyi') ||
this._uri.pathEndsWith('stdlib/builtins.pyi') ||
this._uri.pathEndsWith('stdlib/_importlib_modulespec.pyi') ||
this._uri.pathEndsWith('stdlib/dataclasses.pyi') ||
this._uri.pathEndsWith('stdlib/abc.pyi') ||
this._uri.pathEndsWith('stdlib/enum.pyi') ||
this._uri.pathEndsWith('stdlib/queue.pyi') ||
this._uri.pathEndsWith('stdlib/types.pyi')
) {
this._isBuiltInStubFile = true;
}
@ -282,16 +276,12 @@ export class SourceFile {
this._ipythonMode = ipythonMode ?? IPythonMode.None;
}
getRealFilePath(): string {
return this._realFilePath;
}
getIPythonMode(): IPythonMode {
return this._ipythonMode;
}
getFilePath(): string {
return this._filePath;
getUri(): Uri {
return this._uri;
}
getModuleName(): string {
@ -300,7 +290,7 @@ export class SourceFile {
}
// Synthesize a module name using the file path.
return stripFileExtension(getFileName(this._filePath));
return stripFileExtension(this._uri.fileName);
}
setModuleName(name: string) {
@ -378,8 +368,8 @@ export class SourceFile {
// that of the previous contents.
try {
// Read the file's contents.
if (this.fileSystem.existsSync(this._filePath)) {
const fileContents = this.fileSystem.readFileSync(this._filePath, 'utf8');
if (this.fileSystem.existsSync(this._uri)) {
const fileContents = this.fileSystem.readFileSync(this._uri, 'utf8');
if (fileContents.length !== this._writableData.lastFileContentLength) {
return true;
@ -465,16 +455,16 @@ export class SourceFile {
// Otherwise, get content from file system.
try {
// Check the file's length before attempting to read its full contents.
const fileStat = this.fileSystem.statSync(this._filePath);
const fileStat = this.fileSystem.statSync(this._uri);
if (fileStat.size > _maxSourceFileSize) {
this._console.error(
`File length of "${this._filePath}" is ${fileStat.size} ` +
`File length of "${this._uri}" is ${fileStat.size} ` +
`which exceeds the maximum supported file size of ${_maxSourceFileSize}`
);
throw new Error('File larger than max');
}
return this.fileSystem.readFileSync(this._filePath, 'utf8');
return this.fileSystem.readFileSync(this._uri, 'utf8');
} catch (error) {
return undefined;
}
@ -580,7 +570,7 @@ export class SourceFile {
// (or at least cancel) prior to calling again. It returns true if a parse
// was required and false if the parse information was up to date already.
parse(configOptions: ConfigOptions, importResolver: ImportResolver, content?: string): boolean {
return this._logTracker.log(`parsing: ${this._getPathForLogging(this._filePath)}`, (logState) => {
return this._logTracker.log(`parsing: ${this._getPathForLogging(this._uri)}`, (logState) => {
// If the file is already parsed, we can skip.
if (!this.isParseRequired()) {
logState.suppress();
@ -608,7 +598,7 @@ export class SourceFile {
diagSink.addError(`Source file could not be read`, getEmptyRange());
fileContents = '';
if (!this.fileSystem.existsSync(this._realFilePath)) {
if (!this.fileSystem.existsSync(this._uri)) {
this._writableData.isFileDeleted = true;
}
}
@ -618,7 +608,7 @@ export class SourceFile {
// Parse the token stream, building the abstract syntax tree.
const parseResults = this._parseFile(
configOptions,
this._filePath,
this._uri,
fileContents!,
this._ipythonMode,
diagSink
@ -632,7 +622,7 @@ export class SourceFile {
this._writableData.parseResults.tokenizerOutput.pyrightIgnoreLines;
// Resolve imports.
const execEnvironment = configOptions.findExecEnvironment(this._filePath);
const execEnvironment = configOptions.findExecEnvironment(this._uri);
timingStats.resolveImportsTime.timeOperation(() => {
const importResult = this._resolveImports(
importResolver,
@ -648,7 +638,7 @@ export class SourceFile {
// Is this file in a "strict" path?
const useStrict =
configOptions.strict.find((strictFileSpec) => strictFileSpec.regExp.test(this._realFilePath)) !==
configOptions.strict.find((strictFileSpec) => this._uri.matchesRegex(strictFileSpec.regExp)) !==
undefined;
const commentDiags: CommentUtils.CommentDiagnostic[] = [];
@ -680,7 +670,10 @@ export class SourceFile {
(typeof e.message === 'string' ? e.message : undefined) ||
JSON.stringify(e);
this._console.error(
Localizer.Diagnostic.internalParseError().format({ file: this.getFilePath(), message })
Localizer.Diagnostic.internalParseError().format({
file: this.getUri().toUserVisibleString(),
message,
})
);
// Create dummy parse results.
@ -708,7 +701,10 @@ export class SourceFile {
const diagSink = this.createDiagnosticSink();
diagSink.addError(
Localizer.Diagnostic.internalParseError().format({ file: this.getFilePath(), message }),
Localizer.Diagnostic.internalParseError().format({
file: this.getUri().toUserVisibleString(),
message,
}),
getEmptyRange()
);
this._writableData.parseDiagnostics = diagSink.fetchAndClear();
@ -740,7 +736,7 @@ export class SourceFile {
assert(!this._writableData.isBindingInProgress, 'Bind called while binding in progress');
assert(this._writableData.parseResults !== undefined, 'Parse results not available');
return this._logTracker.log(`binding: ${this._getPathForLogging(this._filePath)}`, () => {
return this._logTracker.log(`binding: ${this._getPathForLogging(this._uri)}`, () => {
try {
// Perform name binding.
timingStats.bindTime.timeOperation(() => {
@ -777,12 +773,18 @@ export class SourceFile {
(typeof e.message === 'string' ? e.message : undefined) ||
JSON.stringify(e);
this._console.error(
Localizer.Diagnostic.internalBindError().format({ file: this.getFilePath(), message })
Localizer.Diagnostic.internalBindError().format({
file: this.getUri().toUserVisibleString(),
message,
})
);
const diagSink = this.createDiagnosticSink();
diagSink.addError(
Localizer.Diagnostic.internalBindError().format({ file: this.getFilePath(), message }),
Localizer.Diagnostic.internalBindError().format({
file: this.getUri().toUserVisibleString(),
message,
}),
getEmptyRange()
);
this._writableData.bindDiagnostics = diagSink.fetchAndClear();
@ -814,7 +816,7 @@ export class SourceFile {
assert(this.isCheckingRequired(), 'Check called unnecessarily');
assert(this._writableData.parseResults !== undefined, 'Parse results not available');
return this._logTracker.log(`checking: ${this._getPathForLogging(this._filePath)}`, () => {
return this._logTracker.log(`checking: ${this._getPathForLogging(this._uri)}`, () => {
try {
timingStats.typeCheckerTime.timeOperation(() => {
const checkDuration = new Duration();
@ -840,11 +842,17 @@ export class SourceFile {
(typeof e.message === 'string' ? e.message : undefined) ||
JSON.stringify(e);
this._console.error(
Localizer.Diagnostic.internalTypeCheckingError().format({ file: this.getFilePath(), message })
Localizer.Diagnostic.internalTypeCheckingError().format({
file: this.getUri().toUserVisibleString(),
message,
})
);
const diagSink = this.createDiagnosticSink();
diagSink.addError(
Localizer.Diagnostic.internalTypeCheckingError().format({ file: this.getFilePath(), message }),
Localizer.Diagnostic.internalTypeCheckingError().format({
file: this.getUri().toUserVisibleString(),
message,
}),
getEmptyRange()
);
@ -1097,7 +1105,7 @@ export class SourceFile {
'\n' +
cirDep
.getPaths()
.map((path) => ' ' + path)
.map((path) => ' ' + path.toUserVisibleString())
.join('\n'),
getEmptyRange()
);
@ -1120,7 +1128,7 @@ export class SourceFile {
this._addTaskListDiagnostics(configOptions.taskListTokens, diagList);
// If the file is in the ignore list, clear the diagnostic list.
if (configOptions.ignore.find((ignoreFileSpec) => ignoreFileSpec.regExp.test(this._realFilePath))) {
if (configOptions.ignore.find((ignoreFileSpec) => this._uri.matchesRegex(ignoreFileSpec.regExp))) {
diagList = [];
}
@ -1243,13 +1251,13 @@ export class SourceFile {
futureImports,
builtinsScope,
diagnosticSink: analysisDiagnostics,
executionEnvironment: configOptions.findExecEnvironment(this._filePath),
executionEnvironment: configOptions.findExecEnvironment(this._uri),
diagnosticRuleSet: this._diagnosticRuleSet,
fileContents,
lines: this._writableData.parseResults!.tokenizerOutput.lines,
typingSymbolAliases: this._writableData.parseResults!.typingSymbolAliases,
definedConstants: configOptions.defineConstant,
filePath: this._filePath,
fileUri: this._uri,
moduleName: this.getModuleName(),
isStubFile: this._isStubFile,
isTypingStubFile: this._isTypingStubFile,
@ -1281,7 +1289,7 @@ export class SourceFile {
const imports: ImportResult[] = [];
const resolveAndAddIfNotSelf = (nameParts: string[], skipMissingImport = false) => {
const importResult = importResolver.resolveImport(this._filePath, execEnv, {
const importResult = importResolver.resolveImport(this._uri, execEnv, {
leadingDots: 0,
nameParts,
importedSymbols: undefined,
@ -1292,7 +1300,7 @@ export class SourceFile {
}
// Avoid importing module from the module file itself.
if (importResult.resolvedPaths.length === 0 || importResult.resolvedPaths[0] !== this._filePath) {
if (importResult.resolvedUris.length === 0 || importResult.resolvedUris[0] !== this._uri) {
imports.push(importResult);
return importResult;
}
@ -1314,7 +1322,7 @@ export class SourceFile {
}
for (const moduleImport of moduleImports) {
const importResult = importResolver.resolveImport(this._filePath, execEnv, {
const importResult = importResolver.resolveImport(this._uri, execEnv, {
leadingDots: moduleImport.leadingDots,
nameParts: moduleImport.nameParts,
importedSymbols: moduleImport.importedSymbols,
@ -1347,24 +1355,24 @@ export class SourceFile {
};
}
private _getPathForLogging(filepath: string) {
return getPathForLogging(this.fileSystem, filepath);
private _getPathForLogging(fileUri: Uri) {
return getPathForLogging(this.fileSystem, fileUri);
}
private _parseFile(
configOptions: ConfigOptions,
filePath: string,
fileUri: Uri,
fileContents: string,
ipythonMode: IPythonMode,
diagSink: DiagnosticSink
) {
// Use the configuration options to determine the environment in which
// this source file will be executed.
const execEnvironment = configOptions.findExecEnvironment(filePath);
const execEnvironment = configOptions.findExecEnvironment(fileUri);
const parseOptions = new ParseOptions();
parseOptions.ipythonMode = ipythonMode;
if (filePath.endsWith('pyi')) {
if (fileUri.pathEndsWith('pyi')) {
parseOptions.isStubFile = true;
}
parseOptions.pythonVersion = execEnvironment.pythonVersion;
@ -1378,7 +1386,7 @@ export class SourceFile {
private _fireFileDirtyEvent() {
this.serviceProvider.tryGet(ServiceKeys.stateMutationListeners)?.forEach((l) => {
try {
l.fileDirty?.(this._filePath);
l.fileDirty?.(this._uri);
} catch (ex: any) {
const console = this.serviceProvider.tryGet(ServiceKeys.console);
if (console) {

View File

@ -34,9 +34,9 @@ export function verifyNoCyclesInChainedFiles<T extends SourceFileInfo>(program:
return;
}
const set = new Set<string>([fileInfo.sourceFile.getFilePath()]);
const set = new Set<string>([fileInfo.sourceFile.getUri().key]);
while (nextChainedFile) {
const path = nextChainedFile.sourceFile.getFilePath();
const path = nextChainedFile.sourceFile.getUri().key;
if (set.has(path)) {
// We found a cycle.
fail(
@ -90,7 +90,7 @@ function _parseAllOpenCells(program: ProgramView): void {
continue;
}
program.getParseResults(file.sourceFile.getFilePath());
program.getParseResults(file.sourceFile.getUri());
program.handleMemoryHighUsage();
}
}

View File

@ -14,31 +14,31 @@ import { appendArray } from '../common/collectionUtils';
import { ExecutionEnvironment } from '../common/configOptions';
import { isDefined } from '../common/core';
import { assertNever } from '../common/debug';
import { combinePaths, getAnyExtensionFromPath, stripFileExtension } from '../common/pathUtils';
import { Uri } from '../common/uri/uri';
import { ClassNode, ImportFromNode, ModuleNode, ParseNode, ParseNodeType } from '../parser/parseNodes';
import {
AliasDeclaration,
ClassDeclaration,
Declaration,
FunctionDeclaration,
ParameterDeclaration,
SpecialBuiltInClassDeclaration,
VariableDeclaration,
isAliasDeclaration,
isClassDeclaration,
isFunctionDeclaration,
isParameterDeclaration,
isSpecialBuiltInClassDeclaration,
isVariableDeclaration,
ParameterDeclaration,
SpecialBuiltInClassDeclaration,
VariableDeclaration,
} from './declaration';
import { ImportResolver } from './importResolver';
import { SourceFileInfo } from './sourceFileInfo';
import { SourceFile } from './sourceFile';
import { SourceFileInfo } from './sourceFileInfo';
import { isUserCode } from './sourceFileInfoUtils';
import { buildImportTree } from './sourceMapperUtils';
import { TypeEvaluator } from './typeEvaluatorTypes';
import { ClassType, isFunction, isInstantiableClass, isOverloadedFunction } from './types';
import { lookUpClassMember } from './typeUtils';
import { ClassType, isFunction, isInstantiableClass, isOverloadedFunction } from './types';
type ClassOrFunctionOrVariableDeclaration =
| ClassDeclaration
@ -47,8 +47,8 @@ type ClassOrFunctionOrVariableDeclaration =
| VariableDeclaration;
// Creates and binds a shadowed file within the program.
export type ShadowFileBinder = (stubFilePath: string, implFilePath: string) => SourceFile | undefined;
export type BoundSourceGetter = (filePath: string) => SourceFileInfo | undefined;
export type ShadowFileBinder = (stubFileUri: Uri, implFileUri: Uri) => SourceFile | undefined;
export type BoundSourceGetter = (fileUri: Uri) => SourceFileInfo | undefined;
export class SourceMapper {
constructor(
@ -63,10 +63,10 @@ export class SourceMapper {
private _cancelToken: CancellationToken
) {}
findModules(stubFilePath: string): ModuleNode[] {
const sourceFiles = this._isStubThatShouldBeMappedToImplementation(stubFilePath)
? this._getBoundSourceFilesFromStubFile(stubFilePath)
: [this._boundSourceGetter(stubFilePath)?.sourceFile];
findModules(stubFileUri: Uri): ModuleNode[] {
const sourceFiles = this._isStubThatShouldBeMappedToImplementation(stubFileUri)
? this._getBoundSourceFilesFromStubFile(stubFileUri)
: [this._boundSourceGetter(stubFileUri)?.sourceFile];
return sourceFiles
.filter(isDefined)
@ -74,8 +74,8 @@ export class SourceMapper {
.filter(isDefined);
}
getModuleNode(filePath: string): ModuleNode | undefined {
return this._boundSourceGetter(filePath)?.sourceFile.getParseResults()?.parseTree;
getModuleNode(fileUri: Uri): ModuleNode | undefined {
return this._boundSourceGetter(fileUri)?.sourceFile.getParseResults()?.parseTree;
}
findDeclarations(stubDecl: Declaration): Declaration[] {
@ -94,13 +94,13 @@ export class SourceMapper {
return [];
}
findDeclarationsByType(originatedPath: string, type: ClassType, useTypeAlias = false): Declaration[] {
findDeclarationsByType(originatedPath: Uri, type: ClassType, useTypeAlias = false): Declaration[] {
const result: ClassOrFunctionOrVariableDeclaration[] = [];
this._addClassTypeDeclarations(originatedPath, type, result, new Set<string>(), useTypeAlias);
return result;
}
findClassDeclarationsByType(originatedPath: string, type: ClassType): ClassDeclaration[] {
findClassDeclarationsByType(originatedPath: Uri, type: ClassType): ClassDeclaration[] {
const result = this.findDeclarationsByType(originatedPath, type);
return result.filter((r) => isClassDeclaration(r)).map((r) => r as ClassDeclaration);
}
@ -111,17 +111,17 @@ export class SourceMapper {
.map((d) => d as FunctionDeclaration);
}
isUserCode(path: string): boolean {
return isUserCode(this._boundSourceGetter(path));
isUserCode(uri: Uri): boolean {
return isUserCode(this._boundSourceGetter(uri));
}
getNextFileName(path: string) {
const withoutExtension = stripFileExtension(path);
getNextFileName(uri: Uri) {
const withoutExtension = uri.stripExtension();
let suffix = 1;
let result = `${withoutExtension}_${suffix}.py`;
let result = withoutExtension.addExtension(`_${suffix}.py`);
while (this.isUserCode(result) && suffix < 1000) {
suffix += 1;
result = `${withoutExtension}_${suffix}.py`;
result = withoutExtension.addExtension(`_${suffix}.py`);
}
return result;
}
@ -132,7 +132,7 @@ export class SourceMapper {
) {
if (stubDecl.node.valueExpression.nodeType === ParseNodeType.Name) {
const className = stubDecl.node.valueExpression.value;
const sourceFiles = this._getBoundSourceFilesFromStubFile(stubDecl.path);
const sourceFiles = this._getBoundSourceFilesFromStubFile(stubDecl.uri);
return sourceFiles.flatMap((sourceFile) =>
this._findClassDeclarationsByName(sourceFile, className, recursiveDeclCache)
@ -144,7 +144,7 @@ export class SourceMapper {
private _findClassOrTypeAliasDeclarations(stubDecl: ClassDeclaration, recursiveDeclCache = new Set<string>()) {
const className = this._getFullClassName(stubDecl.node);
const sourceFiles = this._getBoundSourceFilesFromStubFile(stubDecl.path);
const sourceFiles = this._getBoundSourceFilesFromStubFile(stubDecl.uri);
return sourceFiles.flatMap((sourceFile) =>
this._findClassDeclarationsByName(sourceFile, className, recursiveDeclCache)
@ -156,7 +156,7 @@ export class SourceMapper {
recursiveDeclCache = new Set<string>()
): ClassOrFunctionOrVariableDeclaration[] {
const functionName = stubDecl.node.name.value;
const sourceFiles = this._getBoundSourceFilesFromStubFile(stubDecl.path);
const sourceFiles = this._getBoundSourceFilesFromStubFile(stubDecl.uri);
if (stubDecl.isMethod) {
const classNode = ParseTreeUtils.getEnclosingClass(stubDecl.node);
@ -184,7 +184,7 @@ export class SourceMapper {
}
const variableName = stubDecl.node.value;
const sourceFiles = this._getBoundSourceFilesFromStubFile(stubDecl.path);
const sourceFiles = this._getBoundSourceFilesFromStubFile(stubDecl.uri);
const classNode = ParseTreeUtils.getEnclosingClass(stubDecl.node);
if (classNode) {
@ -270,7 +270,7 @@ export class SourceMapper {
): VariableDeclaration[] {
let result: VariableDeclaration[] = [];
const uniqueId = `@${sourceFile.getFilePath()}/c/${className}/v/${variableName}`;
const uniqueId = `@${sourceFile.getUri()}/c/${className}/v/${variableName}`;
if (recursiveDeclCache.has(uniqueId)) {
return result;
}
@ -283,7 +283,7 @@ export class SourceMapper {
variableName,
(decl, cache, result) => {
if (isVariableDeclaration(decl)) {
if (this._isStubThatShouldBeMappedToImplementation(decl.path)) {
if (this._isStubThatShouldBeMappedToImplementation(decl.uri)) {
for (const implDecl of this._findVariableDeclarations(decl, cache)) {
if (isVariableDeclaration(implDecl)) {
result.push(implDecl);
@ -309,7 +309,7 @@ export class SourceMapper {
): ClassOrFunctionOrVariableDeclaration[] {
let result: ClassOrFunctionOrVariableDeclaration[] = [];
const uniqueId = `@${sourceFile.getFilePath()}/c/${className}/f/${functionName}`;
const uniqueId = `@${sourceFile.getUri()}/c/${className}/f/${functionName}`;
if (recursiveDeclCache.has(uniqueId)) {
return result;
}
@ -322,7 +322,7 @@ export class SourceMapper {
functionName,
(decl, cache, result) => {
if (isFunctionDeclaration(decl)) {
if (this._isStubThatShouldBeMappedToImplementation(decl.path)) {
if (this._isStubThatShouldBeMappedToImplementation(decl.uri)) {
appendArray(result, this._findFunctionOrTypeAliasDeclarations(decl, cache));
} else {
result.push(decl);
@ -343,7 +343,7 @@ export class SourceMapper {
): ClassOrFunctionOrVariableDeclaration[] {
const result: ClassOrFunctionOrVariableDeclaration[] = [];
const uniqueId = `@${sourceFile.getFilePath()}/v/${variableName}`;
const uniqueId = `@${sourceFile.getUri()}/v/${variableName}`;
if (recursiveDeclCache.has(uniqueId)) {
return result;
}
@ -377,7 +377,7 @@ export class SourceMapper {
): ClassOrFunctionOrVariableDeclaration[] {
const result: ClassOrFunctionOrVariableDeclaration[] = [];
const uniqueId = `@${sourceFile.getFilePath()}/f/${functionName}`;
const uniqueId = `@${sourceFile.getUri()}/f/${functionName}`;
if (recursiveDeclCache.has(uniqueId)) {
return result;
}
@ -438,7 +438,7 @@ export class SourceMapper {
): ClassOrFunctionOrVariableDeclaration[] {
const result: ClassOrFunctionOrVariableDeclaration[] = [];
const uniqueId = `@${sourceFile.getFilePath()}[${parentNode.start}]${className}`;
const uniqueId = `@${sourceFile.getUri()}[${parentNode.start}]${className}`;
if (recursiveDeclCache.has(uniqueId)) {
return result;
}
@ -464,7 +464,7 @@ export class SourceMapper {
recursiveDeclCache: Set<string>
) {
if (isVariableDeclaration(decl)) {
if (this._isStubThatShouldBeMappedToImplementation(decl.path)) {
if (this._isStubThatShouldBeMappedToImplementation(decl.uri)) {
appendArray(result, this._findVariableDeclarations(decl, recursiveDeclCache));
} else {
result.push(decl);
@ -487,7 +487,7 @@ export class SourceMapper {
recursiveDeclCache: Set<string>
) {
if (isClassDeclaration(decl)) {
if (this._isStubThatShouldBeMappedToImplementation(decl.path)) {
if (this._isStubThatShouldBeMappedToImplementation(decl.uri)) {
appendArray(result, this._findClassOrTypeAliasDeclarations(decl, recursiveDeclCache));
} else {
result.push(decl);
@ -495,7 +495,7 @@ export class SourceMapper {
} else if (isSpecialBuiltInClassDeclaration(decl)) {
result.push(decl);
} else if (isFunctionDeclaration(decl)) {
if (this._isStubThatShouldBeMappedToImplementation(decl.path)) {
if (this._isStubThatShouldBeMappedToImplementation(decl.uri)) {
appendArray(result, this._findFunctionOrTypeAliasDeclarations(decl, recursiveDeclCache));
} else {
result.push(decl);
@ -525,7 +525,7 @@ export class SourceMapper {
this._addClassOrFunctionDeclarations(overloadDecl, result, recursiveDeclCache);
}
} else if (isInstantiableClass(type)) {
this._addClassTypeDeclarations(decl.path, type, result, recursiveDeclCache);
this._addClassTypeDeclarations(decl.uri, type, result, recursiveDeclCache);
}
}
}
@ -541,7 +541,7 @@ export class SourceMapper {
// and second, clone the given decl and set path to the generated pyi for the
// builtin module (ex, _io) to make resolveAliasDeclaration to work.
// once the path is set, our regular code path will work as expected.
if (decl.path || !decl.node) {
if (!decl.uri.isEmpty() || !decl.node) {
// If module actually exists, nothing we need to do.
return decl;
}
@ -563,20 +563,19 @@ export class SourceMapper {
// ImportResolver might be able to generate or extract builtin module's info
// from runtime if we provide right synthesized stub path.
const fakeStubPath = combinePaths(
stdLibPath,
const fakeStubPath = stdLibPath.combinePaths(
getModuleName()
.nameParts.map((n) => n.value)
.join('.') + '.pyi'
);
const sources = this._getSourceFiles(fakeStubPath, fileInfo.filePath);
const sources = this._getSourceFiles(fakeStubPath, fileInfo.fileUri);
if (sources.length === 0) {
return decl;
}
const synthesizedDecl = { ...decl };
synthesizedDecl.path = sources[0].getFilePath();
synthesizedDecl.uri = sources[0].getUri();
return synthesizedDecl;
@ -595,14 +594,14 @@ export class SourceMapper {
}
private _addClassTypeDeclarations(
originated: string,
originated: Uri,
type: ClassType,
result: ClassOrFunctionOrVariableDeclaration[],
recursiveDeclCache: Set<string>,
useTypeAlias = false
) {
const filePath = type.details.filePath;
const sourceFiles = this._getSourceFiles(filePath, /* stubToShadow */ undefined, originated);
const fileUri = type.details.fileUri;
const sourceFiles = this._getSourceFiles(fileUri, /* stubToShadow */ undefined, originated);
const fullName = useTypeAlias && type.typeAliasInfo ? type.typeAliasInfo.fullName : type.details.fullName;
const fullClassName = fullName.substring(type.details.moduleName.length + 1 /* +1 for trailing dot */);
@ -612,13 +611,13 @@ export class SourceMapper {
}
}
private _getSourceFiles(filePath: string, stubToShadow?: string, originated?: string) {
private _getSourceFiles(fileUri: Uri, stubToShadow?: Uri, originated?: Uri) {
const sourceFiles: SourceFile[] = [];
if (this._isStubThatShouldBeMappedToImplementation(filePath)) {
appendArray(sourceFiles, this._getBoundSourceFilesFromStubFile(filePath, stubToShadow, originated));
if (this._isStubThatShouldBeMappedToImplementation(fileUri)) {
appendArray(sourceFiles, this._getBoundSourceFilesFromStubFile(fileUri, stubToShadow, originated));
} else {
const sourceFileInfo = this._boundSourceGetter(filePath);
const sourceFileInfo = this._boundSourceGetter(fileUri);
if (sourceFileInfo) {
sourceFiles.push(sourceFileInfo.sourceFile);
}
@ -645,14 +644,14 @@ export class SourceMapper {
for (const decl of symbol.getDeclarations()) {
if (
!isAliasDeclaration(decl) ||
!decl.path ||
decl.uri.isEmpty() ||
decl.node.nodeType !== ParseNodeType.ImportFrom ||
!decl.node.isWildcardImport
) {
continue;
}
const uniqueId = `@${decl.path}/l/${symbolName}`;
const uniqueId = `@${decl.uri.key}/l/${symbolName}`;
if (recursiveDeclCache.has(uniqueId)) {
continue;
}
@ -667,7 +666,7 @@ export class SourceMapper {
// function.
recursiveDeclCache.add(uniqueId);
const sourceFiles = this._getSourceFiles(decl.path);
const sourceFiles = this._getSourceFiles(decl.uri);
for (const sourceFile of sourceFiles) {
const moduleNode = sourceFile.getParseResults()?.parseTree;
if (!moduleNode) {
@ -728,28 +727,21 @@ export class SourceMapper {
return fullName.reverse().join('.');
}
private _getBoundSourceFilesFromStubFile(
stubFilePath: string,
stubToShadow?: string,
originated?: string
): SourceFile[] {
const paths = this._getSourcePathsFromStub(
stubFilePath,
originated ?? this._fromFile?.sourceFile.getFilePath()
);
return paths.map((fp) => this._fileBinder(stubToShadow ?? stubFilePath, fp)).filter(isDefined);
private _getBoundSourceFilesFromStubFile(stubFileUri: Uri, stubToShadow?: Uri, originated?: Uri): SourceFile[] {
const paths = this._getSourcePathsFromStub(stubFileUri, originated ?? this._fromFile?.sourceFile.getUri());
return paths.map((fp) => this._fileBinder(stubToShadow ?? stubFileUri, fp)).filter(isDefined);
}
private _getSourcePathsFromStub(stubFilePath: string, fromFile: string | undefined): string[] {
// Attempt our stubFilePath to see if we can resolve it as a source file path
let results = this._importResolver.getSourceFilesFromStub(stubFilePath, this._execEnv, this._mapCompiled);
private _getSourcePathsFromStub(stubFileUri: Uri, fromFile: Uri | undefined): Uri[] {
// Attempt our stubFileUri to see if we can resolve it as a source file path
let results = this._importResolver.getSourceFilesFromStub(stubFileUri, this._execEnv, this._mapCompiled);
if (results.length > 0) {
return results;
}
// If that didn't work, try looking through the graph up to our fromFile.
// One of them should be able to resolve to an actual file.
const stubFileImportTree = this._getStubFileImportTree(stubFilePath, fromFile);
const stubFileImportTree = this._getStubFileImportTree(stubFileUri, fromFile);
// Go through the items in this tree until we find at least one path.
for (let i = 0; i < stubFileImportTree.length; i++) {
@ -766,43 +758,41 @@ export class SourceMapper {
return [];
}
private _getStubFileImportTree(stubFilePath: string, fromFile: string | undefined): string[] {
if (!fromFile || !this._isStubThatShouldBeMappedToImplementation(stubFilePath)) {
private _getStubFileImportTree(stubFileUri: Uri, fromFile: Uri | undefined): Uri[] {
if (!fromFile || !this._isStubThatShouldBeMappedToImplementation(stubFileUri)) {
// No path to search, just return the starting point.
return [stubFilePath];
return [stubFileUri];
} else {
// Otherwise recurse through the importedBy list up to our 'fromFile'.
return buildImportTree(
fromFile,
stubFilePath,
stubFileUri,
(p) => {
const boundSourceInfo = this._boundSourceGetter(p);
return boundSourceInfo
? boundSourceInfo.importedBy.map((info) => info.sourceFile.getFilePath())
: [];
return boundSourceInfo ? boundSourceInfo.importedBy.map((info) => info.sourceFile.getUri()) : [];
},
this._cancelToken
).filter((p) => this._isStubThatShouldBeMappedToImplementation(p));
}
}
private _isStubThatShouldBeMappedToImplementation(filePath: string): boolean {
private _isStubThatShouldBeMappedToImplementation(fileUri: Uri): boolean {
if (this._preferStubs) {
return false;
}
const stub = isStubFile(filePath);
const stub = isStubFile(fileUri);
if (!stub) {
return false;
}
// If we get the same file as a source file, then we treat the file as a regular file even if it has "pyi" extension.
return this._importResolver
.getSourceFilesFromStub(filePath, this._execEnv, this._mapCompiled)
.every((f) => f !== filePath);
.getSourceFilesFromStub(fileUri, this._execEnv, this._mapCompiled)
.every((f) => f !== fileUri);
}
}
export function isStubFile(filePath: string): boolean {
return getAnyExtensionFromPath(filePath, ['.pyi'], /* ignoreCase */ false) === '.pyi';
export function isStubFile(uri: Uri): boolean {
return uri.lastExtension === '.pyi';
}

View File

@ -5,6 +5,7 @@
*/
import { CancellationToken } from 'vscode-jsonrpc';
import { Uri } from '../common/uri/uri';
const MAX_TREE_SEARCH_COUNT = 1000;
@ -15,12 +16,7 @@ class NumberReference {
// Builds an array of imports from the 'from' to the 'to' entry where 'from'
// is on the front of the array and the item just before 'to' is on the
// back of the array.
export function buildImportTree(
to: string,
from: string,
next: (from: string) => string[],
token: CancellationToken
): string[] {
export function buildImportTree(to: Uri, from: Uri, next: (from: Uri) => Uri[], token: CancellationToken): Uri[] {
const totalCountRef = new NumberReference();
const results = _buildImportTreeImpl(to, from, next, [], totalCountRef, token);
@ -29,25 +25,25 @@ export function buildImportTree(
}
function _buildImportTreeImpl(
to: string,
from: string,
next: (from: string) => string[],
previous: string[],
to: Uri,
from: Uri,
next: (from: Uri) => Uri[],
previous: Uri[],
totalSearched: NumberReference,
token: CancellationToken
): string[] {
): Uri[] {
// Exit early if cancellation is requested or we've exceeded max count
if (totalSearched.value > MAX_TREE_SEARCH_COUNT || token.isCancellationRequested) {
return [];
}
totalSearched.value += 1;
if (from === to) {
if (from.equals(to)) {
// At the top, previous should have our way into this recursion.
return previous.length ? previous : [from];
}
if (previous.length > 1 && previous.find((s) => s === from)) {
if (previous.length > 1 && previous.find((s) => s.equals(from))) {
// Fail the search, we're stuck in a loop.
return [];
}

View File

@ -8,8 +8,9 @@
import { isNumber, isString } from '../common/core';
import { assertNever } from '../common/debug';
import { ensureTrailingDirectorySeparator, stripFileExtension } from '../common/pathUtils';
import { stripFileExtension } from '../common/pathUtils';
import { convertOffsetToPosition } from '../common/positionUtils';
import { Uri } from '../common/uri/uri';
import { ParseNode, ParseNodeType, isExpressionNode } from '../parser/parseNodes';
import { AbsoluteModuleDescriptor } from './analyzerFileInfo';
import * as AnalyzerNodeInfo from './analyzerNodeInfo';
@ -22,10 +23,10 @@ export type PrintableType = ParseNode | Declaration | Symbol | Type | undefined;
export interface TracePrinter {
print(o: PrintableType): string;
printFileOrModuleName(filePathOrModule: string | AbsoluteModuleDescriptor): string;
printFileOrModuleName(fileUriOrModule: Uri | AbsoluteModuleDescriptor): string;
}
export function createTracePrinter(roots: string[]): TracePrinter {
export function createTracePrinter(roots: Uri[]): TracePrinter {
function wrap(value: string | undefined, ch = "'") {
return value ? `${ch}${value}${ch}` : '';
}
@ -33,25 +34,22 @@ export function createTracePrinter(roots: string[]): TracePrinter {
// Sort roots in desc order so that we compare longer path first
// when getting relative path.
// ex) d:/root/.env/lib/site-packages, d:/root/.env
roots = roots
.map((r) => ensureTrailingDirectorySeparator(r))
.sort((a, b) => a.localeCompare(b))
.reverse();
roots = roots.sort((a, b) => a.key.localeCompare(b.key)).reverse();
const separatorRegExp = /[\\/]/g;
function printFileOrModuleName(filePathOrModule: string | AbsoluteModuleDescriptor | undefined) {
if (filePathOrModule) {
if (typeof filePathOrModule === 'string') {
function printFileOrModuleName(fileUriOrModule: Uri | AbsoluteModuleDescriptor | undefined) {
if (fileUriOrModule) {
if (Uri.isUri(fileUriOrModule)) {
for (const root of roots) {
if (filePathOrModule.startsWith(root)) {
const subFile = filePathOrModule.substring(root.length);
return stripFileExtension(subFile).replace(separatorRegExp, '.');
if (fileUriOrModule.isChild(root)) {
const subFile = root.getRelativePath(fileUriOrModule);
return stripFileExtension(subFile!).replace(separatorRegExp, '.');
}
}
return filePathOrModule;
} else if (filePathOrModule.nameParts) {
return filePathOrModule.nameParts.join('.');
return fileUriOrModule.toUserVisibleString();
} else if (fileUriOrModule.nameParts) {
return fileUriOrModule.nameParts.join('.');
}
}
return '';
@ -117,33 +115,33 @@ export function createTracePrinter(roots: string[]): TracePrinter {
if (decl) {
switch (decl.type) {
case DeclarationType.Alias:
return `Alias, ${printNode(decl.node)} (${printFileOrModuleName(decl.path)})`;
return `Alias, ${printNode(decl.node)} (${printFileOrModuleName(decl.uri)})`;
case DeclarationType.Class:
return `Class, ${printNode(decl.node)} (${printFileOrModuleName(decl.path)})`;
return `Class, ${printNode(decl.node)} (${printFileOrModuleName(decl.uri)})`;
case DeclarationType.Function:
return `Function, ${printNode(decl.node)} (${printFileOrModuleName(decl.path)})`;
return `Function, ${printNode(decl.node)} (${printFileOrModuleName(decl.uri)})`;
case DeclarationType.Intrinsic:
return `Intrinsic, ${printNode(decl.node)} ${decl.intrinsicType} (${printFileOrModuleName(
decl.path
decl.uri
)})`;
case DeclarationType.Parameter:
return `Parameter, ${printNode(decl.node)} (${printFileOrModuleName(decl.path)})`;
return `Parameter, ${printNode(decl.node)} (${printFileOrModuleName(decl.uri)})`;
case DeclarationType.TypeParameter:
return `TypeParameter, ${printNode(decl.node)} (${printFileOrModuleName(decl.path)})`;
return `TypeParameter, ${printNode(decl.node)} (${printFileOrModuleName(decl.uri)})`;
case DeclarationType.SpecialBuiltInClass:
return `SpecialBuiltInClass, ${printNode(decl.node)} (${printFileOrModuleName(decl.path)})`;
return `SpecialBuiltInClass, ${printNode(decl.node)} (${printFileOrModuleName(decl.uri)})`;
case DeclarationType.Variable:
return `Variable, ${printNode(decl.node)} (${printFileOrModuleName(decl.path)})`;
return `Variable, ${printNode(decl.node)} (${printFileOrModuleName(decl.uri)})`;
case DeclarationType.TypeAlias:
return `TypeAlias, ${printNode(decl.node)} (${printFileOrModuleName(decl.path)})`;
return `TypeAlias, ${printNode(decl.node)} (${printFileOrModuleName(decl.uri)})`;
default:
assertNever(decl);
@ -174,7 +172,7 @@ export function createTracePrinter(roots: string[]): TracePrinter {
return '';
}
let path = printPath ? `(${printFileOrModuleName(getFileInfo(node)?.filePath)})` : '';
let path = printPath ? `(${printFileOrModuleName(getFileInfo(node)?.fileUri)})` : '';
const fileInfo = getFileInfo(node);
if (fileInfo?.lines) {
@ -228,7 +226,7 @@ export function createTracePrinter(roots: string[]): TracePrinter {
function isDeclaration(o: any): o is Declaration {
const d = o as Declaration;
return d && isNumber(d.type) && isString(d.path) && isString(d.moduleName);
return d && isNumber(d.type) && Uri.isUri(d.uri) && isString(d.moduleName);
}
function isType(o: any): o is Type {

View File

@ -33,6 +33,7 @@ import {
TypeCategory,
} from '../analyzer/types';
import { addIfNotNull, appendArray } from '../common/collectionUtils';
import { Uri } from '../common/uri/uri';
import { ModuleNode, ParseNodeType } from '../parser/parseNodes';
import { TypeEvaluator } from './typeEvaluatorTypes';
import {
@ -160,7 +161,7 @@ export function getPropertyDocStringInherited(
export function getVariableInStubFileDocStrings(decl: VariableDeclaration, sourceMapper: SourceMapper) {
const docStrings: string[] = [];
if (!isStubFile(decl.path)) {
if (!isStubFile(decl.uri)) {
return docStrings;
}
@ -193,14 +194,14 @@ export function getModuleDocStringFromModuleNodes(modules: ModuleNode[]): string
return undefined;
}
export function getModuleDocStringFromPaths(filePaths: string[], sourceMapper: SourceMapper) {
export function getModuleDocStringFromUris(uris: Uri[], sourceMapper: SourceMapper) {
const modules: ModuleNode[] = [];
for (const filePath of filePaths) {
if (isStubFile(filePath)) {
addIfNotNull(modules, sourceMapper.getModuleNode(filePath));
for (const uri of uris) {
if (isStubFile(uri)) {
addIfNotNull(modules, sourceMapper.getModuleNode(uri));
}
appendArray(modules, sourceMapper.findModules(filePath));
appendArray(modules, sourceMapper.findModules(uri));
}
return getModuleDocStringFromModuleNodes(modules);
@ -213,8 +214,8 @@ export function getModuleDocString(
) {
let docString = type.docString;
if (!docString) {
const filePath = resolvedDecl?.path ?? type.filePath;
docString = getModuleDocStringFromPaths([filePath], sourceMapper);
const uri = resolvedDecl?.uri ?? type.fileUri;
docString = getModuleDocStringFromUris([uri], sourceMapper);
}
return docString;
@ -228,12 +229,7 @@ export function getClassDocString(
let docString = classType.details.docString;
if (!docString && resolvedDecl && isClassDeclaration(resolvedDecl)) {
docString = _getFunctionOrClassDeclsDocString([resolvedDecl]);
if (
!docString &&
resolvedDecl &&
isStubFile(resolvedDecl.path) &&
resolvedDecl.type === DeclarationType.Class
) {
if (!docString && resolvedDecl && isStubFile(resolvedDecl.uri) && resolvedDecl.type === DeclarationType.Class) {
for (const implDecl of sourceMapper.findDeclarations(resolvedDecl)) {
if (isVariableDeclaration(implDecl) && !!implDecl.docString) {
docString = implDecl.docString;
@ -249,7 +245,7 @@ export function getClassDocString(
}
if (!docString && resolvedDecl) {
const implDecls = sourceMapper.findClassDeclarationsByType(resolvedDecl.path, classType);
const implDecls = sourceMapper.findClassDeclarationsByType(resolvedDecl.uri, classType);
if (implDecls) {
const classDecls = implDecls.filter((d) => isClassDeclaration(d)).map((d) => d);
docString = _getFunctionOrClassDeclsDocString(classDecls);
@ -294,7 +290,7 @@ function _getOverloadedFunctionDocStrings(
docStrings.push(overload.details.docString);
}
});
} else if (resolvedDecl && isStubFile(resolvedDecl.path) && isFunctionDeclaration(resolvedDecl)) {
} else if (resolvedDecl && isStubFile(resolvedDecl.uri) && isFunctionDeclaration(resolvedDecl)) {
const implDecls = sourceMapper.findFunctionDeclarations(resolvedDecl);
const docString = _getFunctionOrClassDeclsDocString(implDecls);
if (docString) {
@ -372,7 +368,7 @@ function _getFunctionDocString(type: Type, resolvedDecl: FunctionDeclaration | u
function _getFunctionDocStringFromDeclaration(resolvedDecl: FunctionDeclaration, sourceMapper: SourceMapper) {
let docString = _getFunctionOrClassDeclsDocString([resolvedDecl]);
if (!docString && isStubFile(resolvedDecl.path)) {
if (!docString && isStubFile(resolvedDecl.uri)) {
const implDecls = sourceMapper.findFunctionDeclarations(resolvedDecl);
docString = _getFunctionOrClassDeclsDocString(implDecls);
}

View File

@ -26,6 +26,7 @@ import { DiagnosticRule } from '../common/diagnosticRules';
import { convertOffsetToPosition, convertOffsetsToRange } from '../common/positionUtils';
import { PythonVersion } from '../common/pythonVersion';
import { TextRange } from '../common/textRange';
import { Uri } from '../common/uri/uri';
import { Localizer, ParameterizedString } from '../localization/localize';
import {
ArgumentCategory,
@ -665,7 +666,7 @@ export function createTypeEvaluator(importLookup: ImportLookup, evaluatorOptions
`Type cache flag mismatch for node type ${node.nodeType} ` +
`(parent ${node.parent?.nodeType ?? 'none'}): ` +
`cached flags = ${expectedFlags}, access flags = ${flags}, ` +
`file = {${fileInfo.filePath} [${position.line + 1}:${position.character + 1}]}`;
`file = {${fileInfo.fileUri} [${position.line + 1}:${position.character + 1}]}`;
if (evaluatorOptions.verifyTypeCacheEvaluatorFlags) {
fail(message);
} else {
@ -2895,7 +2896,7 @@ export function createTypeEvaluator(importLookup: ImportLookup, evaluatorOptions
function getTypeOfModule(node: ParseNode, symbolName: string, nameParts: string[]) {
const fileInfo = AnalyzerNodeInfo.getFileInfo(node);
const lookupResult = importLookup({ nameParts, importingFilePath: fileInfo.filePath });
const lookupResult = importLookup({ nameParts, importingFileUri: fileInfo.fileUri });
if (!lookupResult) {
return undefined;
@ -5355,9 +5356,7 @@ export function createTypeEvaluator(importLookup: ImportLookup, evaluatorOptions
if (getAttrSymbol) {
const isModuleGetAttrSupported =
fileInfo.executionEnvironment.pythonVersion >= PythonVersion.V3_7 ||
getAttrSymbol
.getDeclarations()
.some((decl) => decl.path.toLowerCase().endsWith('.pyi'));
getAttrSymbol.getDeclarations().some((decl) => decl.uri.hasExtension('.pyi'));
if (isModuleGetAttrSupported) {
const getAttrTypeResult = getEffectiveTypeOfSymbolForUsage(getAttrSymbol);
@ -8904,7 +8903,7 @@ export function createTypeEvaluator(importLookup: ImportLookup, evaluatorOptions
if (diagnostic && overrideDecl) {
diagnostic.addRelatedInfo(
Localizer.DiagnosticAddendum.overloadIndex().format({ index: bestMatch.overloadIndex + 1 }),
overrideDecl.path,
overrideDecl.uri,
overrideDecl.range
);
}
@ -9717,7 +9716,7 @@ export function createTypeEvaluator(importLookup: ImportLookup, evaluatorOptions
newClassName,
'',
'',
AnalyzerNodeInfo.getFileInfo(errorNode).filePath,
AnalyzerNodeInfo.getFileInfo(errorNode).fileUri,
ClassTypeFlags.None,
ParseTreeUtils.getTypeSourceId(errorNode),
ClassType.cloneAsInstantiable(returnType),
@ -12628,7 +12627,7 @@ export function createTypeEvaluator(importLookup: ImportLookup, evaluatorOptions
className,
ParseTreeUtils.getClassFullName(errorNode, fileInfo.moduleName, className),
fileInfo.moduleName,
fileInfo.filePath,
fileInfo.fileUri,
classFlags,
ParseTreeUtils.getTypeSourceId(errorNode),
/* declaredMetaclass */ undefined,
@ -12689,7 +12688,7 @@ export function createTypeEvaluator(importLookup: ImportLookup, evaluatorOptions
className,
ParseTreeUtils.getClassFullName(errorNode, fileInfo.moduleName, className),
fileInfo.moduleName,
fileInfo.filePath,
fileInfo.fileUri,
ClassTypeFlags.None,
ParseTreeUtils.getTypeSourceId(errorNode),
/* declaredMetaclass */ undefined,
@ -15269,7 +15268,7 @@ export function createTypeEvaluator(importLookup: ImportLookup, evaluatorOptions
assignedName,
ParseTreeUtils.getClassFullName(node, fileInfo.moduleName, assignedName),
fileInfo.moduleName,
fileInfo.filePath,
fileInfo.fileUri,
ClassTypeFlags.BuiltInClass | ClassTypeFlags.SpecialBuiltIn,
/* typeSourceId */ 0,
/* declaredMetaclass */ undefined,
@ -15835,7 +15834,7 @@ export function createTypeEvaluator(importLookup: ImportLookup, evaluatorOptions
node.name.value,
ParseTreeUtils.getClassFullName(node, fileInfo.moduleName, node.name.value),
fileInfo.moduleName,
fileInfo.filePath,
fileInfo.fileUri,
classFlags,
/* typeSourceId */ 0,
/* declaredMetaclass */ undefined,
@ -16615,7 +16614,16 @@ export function createTypeEvaluator(importLookup: ImportLookup, evaluatorOptions
);
const updatedClassType = ClassType.cloneWithNewTypeParameters(classType, updatedTypeParams);
const dummyTypeObject = ClassType.createInstantiable('__varianceDummy', '', '', '', 0, 0, undefined, undefined);
const dummyTypeObject = ClassType.createInstantiable(
'__varianceDummy',
'',
'',
Uri.empty(),
0,
0,
undefined,
undefined
);
updatedTypeParams.forEach((param, paramIndex) => {
// Skip variadics and ParamSpecs.
@ -16999,7 +17007,7 @@ export function createTypeEvaluator(importLookup: ImportLookup, evaluatorOptions
Localizer.DiagnosticAddendum.initSubclassLocation().format({
name: printType(convertToInstance(initSubclassMethodInfo.classType)),
}),
initSubclassDecl.path,
initSubclassDecl.uri,
initSubclassDecl.range
);
}
@ -18381,7 +18389,7 @@ export function createTypeEvaluator(importLookup: ImportLookup, evaluatorOptions
const importInfo = AnalyzerNodeInfo.getImportInfo(parentNode.module);
if (importInfo && importInfo.isImportFound && !importInfo.isNativeLib) {
const resolvedPath = importInfo.resolvedPaths[importInfo.resolvedPaths.length - 1];
const resolvedPath = importInfo.resolvedUris[importInfo.resolvedUris.length - 1];
const importLookupInfo = importLookup(resolvedPath);
let reportError = false;
@ -18403,7 +18411,7 @@ export function createTypeEvaluator(importLookup: ImportLookup, evaluatorOptions
}
}
}
} else if (!resolvedPath) {
} else if (resolvedPath.isEmpty()) {
// This corresponds to the "from . import a" form.
reportError = true;
}
@ -20207,15 +20215,15 @@ export function createTypeEvaluator(importLookup: ImportLookup, evaluatorOptions
namePartIndex >= 0 &&
importInfo &&
!importInfo.isNativeLib &&
namePartIndex < importInfo.resolvedPaths.length
namePartIndex < importInfo.resolvedUris.length
) {
if (importInfo.resolvedPaths[namePartIndex]) {
if (importInfo.resolvedUris[namePartIndex]) {
evaluateTypesForStatement(node);
// Synthesize an alias declaration for this name part. The only
// time this case is used is for IDE services such as
// the find all references, hover provider and etc.
declarations.push(createSynthesizedAliasDeclaration(importInfo.resolvedPaths[namePartIndex]));
declarations.push(createSynthesizedAliasDeclaration(importInfo.resolvedUris[namePartIndex]));
}
}
} else if (node.parent && node.parent.nodeType === ParseNodeType.Argument && node === node.parent.name) {
@ -20634,8 +20642,8 @@ export function createTypeEvaluator(importLookup: ImportLookup, evaluatorOptions
loaderActions: ModuleLoaderActions,
importLookup: ImportLookup
): Type {
if (loaderActions.path && loaderActions.loadSymbolsFromPath) {
const lookupResults = importLookup(loaderActions.path);
if (!loaderActions.uri.isEmpty() && loaderActions.loadSymbolsFromPath) {
const lookupResults = importLookup(loaderActions.uri);
if (lookupResults) {
moduleType.fields = lookupResults.symbolTable;
moduleType.docString = lookupResults.docString;
@ -20658,7 +20666,7 @@ export function createTypeEvaluator(importLookup: ImportLookup, evaluatorOptions
symbolType = UnknownType.create();
} else {
const moduleName = moduleType.moduleName ? moduleType.moduleName + '.' + name : '';
const importedModuleType = ModuleType.create(moduleName, implicitImport.path);
const importedModuleType = ModuleType.create(moduleName, implicitImport.uri);
symbolType = applyLoaderActionsToModuleType(importedModuleType, implicitImport, importLookup);
}
@ -20676,7 +20684,7 @@ export function createTypeEvaluator(importLookup: ImportLookup, evaluatorOptions
if (resolvedDecl.type === DeclarationType.Alias) {
// Build a module type that corresponds to the declaration and
// its associated loader actions.
const moduleType = ModuleType.create(resolvedDecl.moduleName, resolvedDecl.path);
const moduleType = ModuleType.create(resolvedDecl.moduleName, resolvedDecl.uri);
if (resolvedDecl.symbolName && resolvedDecl.submoduleFallback) {
return applyLoaderActionsToModuleType(moduleType, resolvedDecl.submoduleFallback, importLookup);
} else {

View File

@ -1480,7 +1480,7 @@ function narrowTypeForIsInstance(
className,
ParseTreeUtils.getClassFullName(errorNode, fileInfo.moduleName, className),
fileInfo.moduleName,
fileInfo.filePath,
fileInfo.fileUri,
ClassTypeFlags.None,
ParseTreeUtils.getTypeSourceId(errorNode),
/* declaredMetaclass */ undefined,
@ -2545,7 +2545,7 @@ function narrowTypeForCallable(
className,
ParseTreeUtils.getClassFullName(errorNode, fileInfo.moduleName, className),
fileInfo.moduleName,
fileInfo.filePath,
fileInfo.fileUri,
ClassTypeFlags.None,
ParseTreeUtils.getTypeSourceId(errorNode),
/* declaredMetaclass */ undefined,

View File

@ -8,6 +8,7 @@
* and analyzed python source file.
*/
import { Uri } from '../common/uri/uri';
import {
ArgumentCategory,
AssignmentNode,
@ -158,13 +159,13 @@ export class TypeStubWriter extends ParseTreeWalker {
private _trackedImportFrom = new Map<string, TrackedImportFrom>();
private _accessedImportedSymbols = new Set<string>();
constructor(private _stubPath: string, private _sourceFile: SourceFile, private _evaluator: TypeEvaluator) {
constructor(private _stubPath: Uri, private _sourceFile: SourceFile, private _evaluator: TypeEvaluator) {
super();
// As a heuristic, we'll include all of the import statements
// in "__init__.pyi" files even if they're not locally referenced
// because these are often used as ways to re-export symbols.
if (this._stubPath.endsWith('__init__.pyi')) {
if (this._stubPath.fileName === '__init__.pyi') {
this._includeAllImports = true;
}
}

View File

@ -97,7 +97,7 @@ export function createTypedDictType(
className,
ParseTreeUtils.getClassFullName(errorNode, fileInfo.moduleName, className),
fileInfo.moduleName,
fileInfo.filePath,
fileInfo.fileUri,
ClassTypeFlags.TypedDictClass,
ParseTreeUtils.getTypeSourceId(errorNode),
/* declaredMetaclass */ undefined,
@ -147,7 +147,7 @@ export function createTypedDictType(
const declaration: VariableDeclaration = {
type: DeclarationType.Variable,
node: entry.name,
path: fileInfo.filePath,
uri: fileInfo.fileUri,
typeAnnotationNode: entry.valueExpression,
isRuntimeTypeExpression: true,
range: convertOffsetsToRange(
@ -209,7 +209,7 @@ export function createTypedDictTypeInlined(
className,
ParseTreeUtils.getClassFullName(dictNode, fileInfo.moduleName, className),
fileInfo.moduleName,
fileInfo.filePath,
fileInfo.fileUri,
ClassTypeFlags.TypedDictClass,
ParseTreeUtils.getTypeSourceId(dictNode),
/* declaredMetaclass */ undefined,
@ -773,7 +773,7 @@ function getTypedDictFieldsFromDictSyntax(
const declaration: VariableDeclaration = {
type: DeclarationType.Variable,
node: entry.keyExpression,
path: fileInfo.filePath,
uri: fileInfo.fileUri,
typeAnnotationNode: entry.valueExpression,
isRuntimeTypeExpression: !isInline,
range: convertOffsetsToRange(

View File

@ -8,6 +8,7 @@
*/
import { assert } from '../common/debug';
import { Uri } from '../common/uri/uri';
import { ArgumentNode, ExpressionNode, NameNode, ParameterCategory } from '../parser/parseNodes';
import { FunctionDeclaration } from './declaration';
import { Symbol, SymbolTable } from './symbol';
@ -383,18 +384,18 @@ export interface ModuleType extends TypeBase {
// The period-delimited import name of this module.
moduleName: string;
filePath: string;
fileUri: Uri;
}
export namespace ModuleType {
export function create(moduleName: string, filePath: string, symbolTable?: SymbolTable) {
export function create(moduleName: string, fileUri: Uri, symbolTable?: SymbolTable) {
const newModuleType: ModuleType = {
category: TypeCategory.Module,
fields: symbolTable || new Map<string, Symbol>(),
loaderFields: new Map<string, Symbol>(),
flags: TypeFlags.Instantiable | TypeFlags.Instantiable,
moduleName,
filePath,
fileUri,
};
return newModuleType;
}
@ -561,7 +562,7 @@ interface ClassDetails {
name: string;
fullName: string;
moduleName: string;
filePath: string;
fileUri: Uri;
flags: ClassTypeFlags;
typeSourceId: TypeSourceId;
baseClasses: Type[];
@ -709,7 +710,7 @@ export namespace ClassType {
name: string,
fullName: string,
moduleName: string,
filePath: string,
fileUri: Uri,
flags: ClassTypeFlags,
typeSourceId: TypeSourceId,
declaredMetaclass: ClassType | UnknownType | undefined,
@ -722,7 +723,7 @@ export namespace ClassType {
name,
fullName,
moduleName,
filePath,
fileUri,
flags,
typeSourceId,
baseClasses: [],

View File

@ -13,17 +13,17 @@ import { BackgroundAnalysisBase, BackgroundAnalysisRunnerBase } from './backgrou
import { InitializationData } from './backgroundThreadBase';
import { getCancellationFolderName } from './common/cancellationUtils';
import { ConfigOptions } from './common/configOptions';
import { ConsoleInterface } from './common/console';
import { FullAccessHost } from './common/fullAccessHost';
import { Host } from './common/host';
import { ServiceProvider } from './common/serviceProvider';
import { getRootUri } from './common/uri/uriUtils';
export class BackgroundAnalysis extends BackgroundAnalysisBase {
constructor(console: ConsoleInterface) {
super(console);
constructor(serviceProvider: ServiceProvider) {
super(serviceProvider.console());
const initialData: InitializationData = {
rootDirectory: (global as any).__rootDirectory as string,
rootUri: getRootUri(serviceProvider.fs().isCaseSensitive)?.toString() ?? '',
cancellationFolderName: getCancellationFolderName(),
runner: undefined,
};
@ -40,7 +40,7 @@ export class BackgroundAnalysisRunner extends BackgroundAnalysisRunnerBase {
}
protected override createHost(): Host {
return new FullAccessHost(this.fs);
return new FullAccessHost(this.getServiceProvider());
}
protected override createImportResolver(

View File

@ -10,6 +10,7 @@ import { CancellationToken } from 'vscode-languageserver';
import { MessageChannel, MessagePort, Worker, parentPort, threadId, workerData } from 'worker_threads';
import { AnalysisCompleteCallback, AnalysisResults, analyzeProgram, nullCallback } from './analyzer/analysis';
import { BackgroundAnalysisProgram, InvalidatedReason } from './analyzer/backgroundAnalysisProgram';
import { ImportResolver } from './analyzer/importResolver';
import { OpenFileOptions, Program } from './analyzer/program';
import {
@ -33,9 +34,9 @@ import { FileDiagnostics } from './common/diagnosticSink';
import { disposeCancellationToken, getCancellationTokenFromId } from './common/fileBasedCancellationUtils';
import { Host, HostKind } from './common/host';
import { LogTracker } from './common/logTracker';
import { Range } from './common/textRange';
import { BackgroundAnalysisProgram, InvalidatedReason } from './analyzer/backgroundAnalysisProgram';
import { ServiceProvider } from './common/serviceProvider';
import { Range } from './common/textRange';
import { Uri } from './common/uri/uri';
export class BackgroundAnalysisBase {
private _worker: Worker | undefined;
@ -57,8 +58,8 @@ export class BackgroundAnalysisBase {
this.enqueueRequest({ requestType: 'setConfigOptions', data: configOptions });
}
setTrackedFiles(filePaths: string[]) {
this.enqueueRequest({ requestType: 'setTrackedFiles', data: filePaths });
setTrackedFiles(fileUris: Uri[]) {
this.enqueueRequest({ requestType: 'setTrackedFiles', data: fileUris.map((f) => f.toString()) });
}
setAllowedThirdPartyImports(importNames: string[]) {
@ -69,36 +70,36 @@ export class BackgroundAnalysisBase {
this.enqueueRequest({ requestType: 'ensurePartialStubPackages', data: { executionRoot } });
}
setFileOpened(filePath: string, version: number | null, contents: string, options: OpenFileOptions) {
setFileOpened(fileUri: Uri, version: number | null, contents: string, options: OpenFileOptions) {
this.enqueueRequest({
requestType: 'setFileOpened',
data: { filePath, version, contents, options },
data: { fileUri: fileUri.toString(), version, contents, options },
});
}
updateChainedFilePath(filePath: string, chainedFilePath: string | undefined) {
updateChainedUri(fileUri: Uri, chainedUri: Uri | undefined) {
this.enqueueRequest({
requestType: 'updateChainedFilePath',
data: { filePath, chainedFilePath },
requestType: 'updateChainedFileUri',
data: { fileUri: fileUri.toString(), chainedUri: chainedUri?.toString() },
});
}
setFileClosed(filePath: string, isTracked?: boolean) {
this.enqueueRequest({ requestType: 'setFileClosed', data: { filePath, isTracked } });
setFileClosed(fileUri: Uri, isTracked?: boolean) {
this.enqueueRequest({ requestType: 'setFileClosed', data: { fileUri, isTracked } });
}
addInterimFile(filePath: string) {
this.enqueueRequest({ requestType: 'addInterimFile', data: { filePath } });
addInterimFile(fileUri: Uri) {
this.enqueueRequest({ requestType: 'addInterimFile', data: { fileUri: fileUri.toString() } });
}
markAllFilesDirty(evenIfContentsAreSame: boolean) {
this.enqueueRequest({ requestType: 'markAllFilesDirty', data: { evenIfContentsAreSame } });
}
markFilesDirty(filePaths: string[], evenIfContentsAreSame: boolean) {
markFilesDirty(fileUris: Uri[], evenIfContentsAreSame: boolean) {
this.enqueueRequest({
requestType: 'markFilesDirty',
data: { filePaths, evenIfContentsAreSame },
data: { fileUris: fileUris.map((f) => f.toString()), evenIfContentsAreSame },
});
}
@ -106,7 +107,7 @@ export class BackgroundAnalysisBase {
this._startOrResumeAnalysis('analyze', program, token);
}
async analyzeFile(filePath: string, token: CancellationToken): Promise<boolean> {
async analyzeFile(fileUri: Uri, token: CancellationToken): Promise<boolean> {
throwIfCancellationRequested(token);
const { port1, port2 } = new MessageChannel();
@ -115,7 +116,7 @@ export class BackgroundAnalysisBase {
const cancellationId = getCancellationTokenId(token);
this.enqueueRequest({
requestType: 'analyzeFile',
data: { filePath, cancellationId },
data: { fileUri: fileUri.toString(), cancellationId },
port: port2,
});
@ -127,7 +128,7 @@ export class BackgroundAnalysisBase {
return result;
}
async getDiagnosticsForRange(filePath: string, range: Range, token: CancellationToken): Promise<Diagnostic[]> {
async getDiagnosticsForRange(fileUri: Uri, range: Range, token: CancellationToken): Promise<Diagnostic[]> {
throwIfCancellationRequested(token);
const { port1, port2 } = new MessageChannel();
@ -136,7 +137,7 @@ export class BackgroundAnalysisBase {
const cancellationId = getCancellationTokenId(token);
this.enqueueRequest({
requestType: 'getDiagnosticsForRange',
data: { filePath, range, cancellationId },
data: { fileUri: fileUri.toString(), range, cancellationId },
port: port2,
});
@ -149,9 +150,9 @@ export class BackgroundAnalysisBase {
}
async writeTypeStub(
targetImportPath: string,
targetImportPath: Uri,
targetIsSingleFile: boolean,
stubPath: string,
stubPath: Uri,
token: CancellationToken
): Promise<any> {
throwIfCancellationRequested(token);
@ -162,7 +163,12 @@ export class BackgroundAnalysisBase {
const cancellationId = getCancellationTokenId(token);
this.enqueueRequest({
requestType: 'writeTypeStub',
data: { targetImportPath, targetIsSingleFile, stubPath, cancellationId },
data: {
targetImportPath: targetImportPath.toString(),
targetIsSingleFile,
stubPath: stubPath.toString(),
cancellationId,
},
port: port2,
});
@ -283,15 +289,15 @@ export abstract class BackgroundAnalysisRunnerBase extends BackgroundThreadBase
protected importResolver: ImportResolver;
protected logTracker: LogTracker;
protected isCaseSensitive = true;
protected constructor(serviceProvider: ServiceProvider) {
super(workerData as InitializationData, serviceProvider);
// Stash the base directory into a global variable.
const data = workerData as InitializationData;
this.log(LogLevel.Info, `Background analysis(${threadId}) root directory: ${data.rootDirectory}`);
this._configOptions = new ConfigOptions(data.rootDirectory);
this.log(LogLevel.Info, `Background analysis(${threadId}) root directory: ${data.rootUri}`);
this._configOptions = new ConfigOptions(Uri.parse(data.rootUri, serviceProvider.fs().isCaseSensitive));
this.importResolver = this.createImportResolver(serviceProvider, this._configOptions, this.createHost());
const console = this.getConsole();
@ -339,20 +345,20 @@ export abstract class BackgroundAnalysisRunnerBase extends BackgroundThreadBase
case 'analyzeFile': {
run(() => {
const { filePath, cancellationId } = msg.data;
const { fileUri, cancellationId } = msg.data;
const token = getCancellationTokenFromId(cancellationId);
return this.handleAnalyzeFile(filePath, token);
return this.handleAnalyzeFile(fileUri, token);
}, msg.port!);
break;
}
case 'getDiagnosticsForRange': {
run(() => {
const { filePath, range, cancellationId } = msg.data;
const { fileUri, range, cancellationId } = msg.data;
const token = getCancellationTokenFromId(cancellationId);
return this.handleGetDiagnosticsForRange(filePath, range, token);
return this.handleGetDiagnosticsForRange(fileUri, range, token);
}, msg.port!);
break;
}
@ -394,26 +400,26 @@ export abstract class BackgroundAnalysisRunnerBase extends BackgroundThreadBase
}
case 'setFileOpened': {
const { filePath, version, contents, options } = msg.data;
this.handleSetFileOpened(filePath, version, contents, options);
const { fileUri, version, contents, options } = msg.data;
this.handleSetFileOpened(fileUri, version, contents, options);
break;
}
case 'updateChainedFilePath': {
const { filePath, chainedFilePath } = msg.data;
this.handleUpdateChainedFilePath(filePath, chainedFilePath);
case 'updateChainedFileUri': {
const { fileUri, chainedUri } = msg.data;
this.handleUpdateChainedfileUri(fileUri, chainedUri);
break;
}
case 'setFileClosed': {
const { filePath, isTracked } = msg.data;
this.handleSetFileClosed(filePath, isTracked);
const { fileUri, isTracked } = msg.data;
this.handleSetFileClosed(fileUri, isTracked);
break;
}
case 'addInterimFile': {
const { filePath } = msg.data;
this.handleAddInterimFile(filePath);
const { fileUri } = msg.data;
this.handleAddInterimFile(fileUri);
break;
}
@ -424,8 +430,8 @@ export abstract class BackgroundAnalysisRunnerBase extends BackgroundThreadBase
}
case 'markFilesDirty': {
const { filePaths, evenIfContentsAreSame } = msg.data;
this.handleMarkFilesDirty(filePaths, evenIfContentsAreSame);
const { fileUris, evenIfContentsAreSame } = msg.data;
this.handleMarkFilesDirty(fileUris, evenIfContentsAreSame);
break;
}
@ -499,14 +505,14 @@ export abstract class BackgroundAnalysisRunnerBase extends BackgroundThreadBase
}
}
protected handleAnalyzeFile(filePath: string, token: CancellationToken) {
protected handleAnalyzeFile(fileUri: string, token: CancellationToken) {
throwIfCancellationRequested(token);
return this.program.analyzeFile(filePath, token);
return this.program.analyzeFile(Uri.parse(fileUri, this.isCaseSensitive), token);
}
protected handleGetDiagnosticsForRange(filePath: string, range: Range, token: CancellationToken) {
protected handleGetDiagnosticsForRange(fileUri: string, range: Range, token: CancellationToken) {
throwIfCancellationRequested(token);
return this.program.getDiagnosticsForRange(filePath, range);
return this.program.getDiagnosticsForRange(Uri.parse(fileUri, this.isCaseSensitive), range);
}
protected handleWriteTypeStub(
@ -524,7 +530,12 @@ export abstract class BackgroundAnalysisRunnerBase extends BackgroundThreadBase
token
);
this.program.writeTypeStub(targetImportPath, targetIsSingleFile, stubPath, token);
this.program.writeTypeStub(
Uri.parse(targetImportPath, this.isCaseSensitive),
targetIsSingleFile,
Uri.parse(stubPath, this.isCaseSensitive),
token
);
}
protected handleSetImportResolver(hostKind: HostKind) {
@ -548,8 +559,8 @@ export abstract class BackgroundAnalysisRunnerBase extends BackgroundThreadBase
this.program.setImportResolver(this.importResolver);
}
protected handleSetTrackedFiles(filePaths: string[]) {
const diagnostics = this.program.setTrackedFiles(filePaths);
protected handleSetTrackedFiles(fileUris: string[]) {
const diagnostics = this.program.setTrackedFiles(fileUris.map((f) => Uri.parse(f, this.isCaseSensitive)));
this._reportDiagnostics(diagnostics, this.program.getFilesToAnalyzeCount(), 0);
}
@ -565,29 +576,35 @@ export abstract class BackgroundAnalysisRunnerBase extends BackgroundThreadBase
}
protected handleSetFileOpened(
filePath: string,
fileUri: string,
version: number | null,
contents: string,
options: OpenFileOptions | undefined
) {
this.program.setFileOpened(filePath, version, contents, options);
this.program.setFileOpened(Uri.parse(fileUri, this.isCaseSensitive), version, contents, options);
}
protected handleUpdateChainedFilePath(filePath: string, chainedFilePath: string | undefined) {
this.program.updateChainedFilePath(filePath, chainedFilePath);
protected handleUpdateChainedfileUri(fileUri: string, chainedfileUri: string | undefined) {
this.program.updateChainedUri(
Uri.parse(fileUri, this.isCaseSensitive),
chainedfileUri ? Uri.parse(chainedfileUri, this.isCaseSensitive) : undefined
);
}
protected handleSetFileClosed(filePath: string, isTracked: boolean | undefined) {
const diagnostics = this.program.setFileClosed(filePath, isTracked);
protected handleSetFileClosed(fileUri: string, isTracked: boolean | undefined) {
const diagnostics = this.program.setFileClosed(Uri.parse(fileUri, this.isCaseSensitive), isTracked);
this._reportDiagnostics(diagnostics, this.program.getFilesToAnalyzeCount(), 0);
}
protected handleAddInterimFile(filePath: string) {
this.program.addInterimFile(filePath);
protected handleAddInterimFile(fileUri: string) {
this.program.addInterimFile(Uri.parse(fileUri, this.isCaseSensitive));
}
protected handleMarkFilesDirty(filePaths: string[], evenIfContentsAreSame: boolean) {
this.program.markFilesDirty(filePaths, evenIfContentsAreSame);
protected handleMarkFilesDirty(fileUris: string[], evenIfContentsAreSame: boolean) {
this.program.markFilesDirty(
fileUris.map((f) => Uri.parse(f, this.isCaseSensitive)),
evenIfContentsAreSame
);
}
protected handleMarkAllFilesDirty(evenIfContentsAreSame: boolean) {
@ -666,7 +683,7 @@ export abstract class BackgroundAnalysisRunnerBase extends BackgroundThreadBase
function convertAnalysisResults(result: AnalysisResults): AnalysisResults {
result.diagnostics = result.diagnostics.map((f: FileDiagnostics) => {
return {
filePath: f.filePath,
fileUri: f.fileUri,
version: f.version,
diagnostics: convertDiagnostics(f.diagnostics),
};
@ -692,7 +709,7 @@ function convertDiagnostics(diagnostics: Diagnostic[]) {
if (d._relatedInfo) {
for (const info of d._relatedInfo) {
diag.addRelatedInfo(info.message, info.filePath, info.range);
diag.addRelatedInfo(info.message, info.fileUri, info.range);
}
}
@ -708,7 +725,7 @@ export type AnalysisRequestKind =
| 'setAllowedThirdPartyImports'
| 'ensurePartialStubPackages'
| 'setFileOpened'
| 'updateChainedFilePath'
| 'updateChainedFileUri'
| 'setFileClosed'
| 'markAllFilesDirty'
| 'markFilesDirty'

View File

@ -12,11 +12,12 @@ import { OperationCanceledException, setCancellationFolderName } from './common/
import { ConfigOptions } from './common/configOptions';
import { ConsoleInterface, LogLevel } from './common/console';
import * as debug from './common/debug';
import { FileSpec } from './common/pathUtils';
import { createFromRealFileSystem, RealTempFile } from './common/realFileSystem';
import { ServiceProvider } from './common/serviceProvider';
import './common/serviceProviderExtensions';
import { ServiceKeys } from './common/serviceProviderExtensions';
import { Uri } from './common/uri/uri';
import { FileSpec } from './common/uri/uriUtils';
export class BackgroundConsole implements ConsoleInterface {
// We always generate logs in the background. For the foreground,
@ -48,9 +49,6 @@ export class BackgroundThreadBase {
protected constructor(data: InitializationData, serviceProvider?: ServiceProvider) {
setCancellationFolderName(data.cancellationFolderName);
// Stash the base directory into a global variable.
(global as any).__rootDirectory = data.rootDirectory;
// Make sure there's a file system and a console interface.
this._serviceProvider = serviceProvider ?? new ServiceProvider();
if (!this._serviceProvider.tryGet(ServiceKeys.console)) {
@ -60,8 +58,17 @@ export class BackgroundThreadBase {
this._serviceProvider.add(ServiceKeys.fs, createFromRealFileSystem(this.getConsole()));
}
if (!this._serviceProvider.tryGet(ServiceKeys.tempFile)) {
this._serviceProvider.add(ServiceKeys.tempFile, new RealTempFile());
this._serviceProvider.add(
ServiceKeys.tempFile,
new RealTempFile(this._serviceProvider.fs().isCaseSensitive)
);
}
// Stash the base directory into a global variable.
(global as any).__rootDirectory = Uri.parse(
data.rootUri,
this._serviceProvider.fs().isCaseSensitive
).getFilePath();
}
protected get fs() {
@ -170,7 +177,7 @@ export function getBackgroundWaiter<T>(port: MessagePort): Promise<T> {
}
export interface InitializationData {
rootDirectory: string;
rootUri: string;
cancellationFolderName: string | undefined;
runner: string | undefined;
title?: string;

View File

@ -9,6 +9,7 @@
import { CancellationToken, ExecuteCommandParams } from 'vscode-languageserver';
import { OperationCanceledException } from '../common/cancellationUtils';
import { Uri } from '../common/uri/uri';
import { LanguageServerInterface } from '../languageServerBase';
import { AnalyzerServiceExecutor } from '../languageService/analyzerServiceExecutor';
import { ServerCommand } from './commandController';
@ -18,9 +19,9 @@ export class CreateTypeStubCommand implements ServerCommand {
async execute(cmdParams: ExecuteCommandParams, token: CancellationToken): Promise<any> {
if (cmdParams.arguments && cmdParams.arguments.length >= 2) {
const workspaceRoot = cmdParams.arguments[0] as string;
const workspaceRoot = Uri.parse(cmdParams.arguments[0] as string, this._ls.rootUri.isCaseSensitive);
const importName = cmdParams.arguments[1] as string;
const callingFile = cmdParams.arguments[2] as string;
const callingFile = Uri.parse(cmdParams.arguments[2] as string, this._ls.rootUri.isCaseSensitive);
const service = await AnalyzerServiceExecutor.cloneService(
this._ls,

View File

@ -29,6 +29,7 @@ import { isNumber, isString } from '../common/core';
import { convertOffsetToPosition, convertOffsetsToRange } from '../common/positionUtils';
import { TextRange } from '../common/textRange';
import { TextRangeCollection } from '../common/textRangeCollection';
import { Uri } from '../common/uri/uri';
import { LanguageServerInterface } from '../languageServerBase';
import {
ArgumentCategory,
@ -132,11 +133,11 @@ export class DumpFileDebugInfoCommand implements ServerCommand {
return [];
}
const filePath = params.arguments[0] as string;
const fileUri = Uri.parse(params.arguments[0] as string, this._ls.rootUri.isCaseSensitive);
const kind = params.arguments[1];
const workspace = await this._ls.getWorkspaceForFile(filePath);
const parseResults = workspace.service.getParseResult(workspace.service.fs.realCasePath(filePath));
const workspace = await this._ls.getWorkspaceForFile(fileUri);
const parseResults = workspace.service.getParseResult(workspace.service.fs.realCasePath(fileUri));
if (!parseResults) {
return [];
}
@ -157,7 +158,7 @@ export class DumpFileDebugInfoCommand implements ServerCommand {
},
};
collectingConsole.info(`* Dump debug info for '${filePath}'`);
collectingConsole.info(`* Dump debug info for '${fileUri.toUserVisibleString()}'`);
switch (kind) {
case 'tokens': {
@ -166,7 +167,7 @@ export class DumpFileDebugInfoCommand implements ServerCommand {
for (let i = 0; i < parseResults.tokenizerOutput.tokens.count; i++) {
const token = parseResults.tokenizerOutput.tokens.getItemAt(i);
collectingConsole.info(
`[${i}] ${getTokenString(filePath, token, parseResults.tokenizerOutput.lines)}`
`[${i}] ${getTokenString(fileUri, token, parseResults.tokenizerOutput.lines)}`
);
}
break;
@ -174,7 +175,7 @@ export class DumpFileDebugInfoCommand implements ServerCommand {
case 'nodes': {
collectingConsole.info(`* Node info`);
const dumper = new TreeDumper(filePath, parseResults.tokenizerOutput.lines);
const dumper = new TreeDumper(fileUri, parseResults.tokenizerOutput.lines);
dumper.walk(parseResults.parseTree);
collectingConsole.info(dumper.output);
@ -189,7 +190,7 @@ export class DumpFileDebugInfoCommand implements ServerCommand {
}
collectingConsole.info(`* Type info`);
collectingConsole.info(`${getTypeEvaluatorString(filePath, evaluator, parseResults, start, end)}`);
collectingConsole.info(`${getTypeEvaluatorString(fileUri, evaluator, parseResults, start, end)}`);
break;
}
case 'cachedtypes': {
@ -201,9 +202,7 @@ export class DumpFileDebugInfoCommand implements ServerCommand {
}
collectingConsole.info(`* Cached Type info`);
collectingConsole.info(
`${getTypeEvaluatorString(filePath, evaluator, parseResults, start, end, true)}`
);
collectingConsole.info(`${getTypeEvaluatorString(fileUri, evaluator, parseResults, start, end, true)}`);
break;
}
@ -239,14 +238,14 @@ function stringify(value: any, replacer: (this: any, key: string, value: any) =>
}
function getTypeEvaluatorString(
file: string,
uri: Uri,
evaluator: TypeEvaluator,
results: ParseResults,
start: number,
end: number,
cacheOnly?: boolean
) {
const dumper = new TreeDumper(file, results.tokenizerOutput.lines);
const dumper = new TreeDumper(uri, results.tokenizerOutput.lines);
const node = findNodeByOffset(results.parseTree, start) ?? findNodeByOffset(results.parseTree, end);
if (!node) {
return 'N/A';
@ -549,7 +548,7 @@ class TreeDumper extends ParseTreeWalker {
private _indentation = '';
private _output = '';
constructor(private _file: string, private _lines: TextRangeCollection<TextRange>) {
constructor(private _uri: Uri, private _lines: TextRangeCollection<TextRange>) {
super();
}
@ -604,7 +603,7 @@ class TreeDumper extends ParseTreeWalker {
override visitBinaryOperation(node: BinaryOperationNode) {
this._log(
`${this._getPrefix(node)} ${getTokenString(
this._file,
this._uri,
node.operatorToken,
this._lines
)} ${getOperatorTypeString(node.operator)}} parenthesized:(${node.parenthesized})`
@ -697,7 +696,7 @@ class TreeDumper extends ParseTreeWalker {
`${this._getPrefix(node)} wildcard import:(${node.isWildcardImport}) paren:(${
node.usesParens
}) wildcard token:(${
node.wildcardToken ? getTokenString(this._file, node.wildcardToken, this._lines) : 'N/A'
node.wildcardToken ? getTokenString(this._uri, node.wildcardToken, this._lines) : 'N/A'
}) missing import keyword:(${node.missingImportKeyword})`
);
return true;
@ -784,7 +783,7 @@ class TreeDumper extends ParseTreeWalker {
}
override visitName(node: NameNode) {
this._log(`${this._getPrefix(node)} ${getTokenString(this._file, node.token, this._lines)} ${node.value}`);
this._log(`${this._getPrefix(node)} ${getTokenString(this._uri, node.token, this._lines)} ${node.value}`);
return true;
}
@ -834,7 +833,7 @@ class TreeDumper extends ParseTreeWalker {
}
override visitString(node: StringNode) {
this._log(`${this._getPrefix(node)} ${getTokenString(this._file, node.token, this._lines)} ${node.value}`);
this._log(`${this._getPrefix(node)} ${getTokenString(this._uri, node.token, this._lines)} ${node.value}`);
return true;
}
@ -866,7 +865,7 @@ class TreeDumper extends ParseTreeWalker {
override visitUnaryOperation(node: UnaryOperationNode) {
this._log(
`${this._getPrefix(node)} ${getTokenString(
this._file,
this._uri,
node.operatorToken,
this._lines
)} ${getOperatorTypeString(node.operator)}`
@ -988,7 +987,7 @@ class TreeDumper extends ParseTreeWalker {
private _getPrefix(node: ParseNode) {
const pos = convertOffsetToPosition(node.start, this._lines);
// VS code's output window expects 1 based values, print the line/char with 1 based.
return `[${node.id}] '${this._file}:${pos.line + 1}:${pos.character + 1}' => ${printParseNodeType(
return `[${node.id}] '${this._uri.toString()}:${pos.line + 1}:${pos.character + 1}' => ${printParseNodeType(
node.nodeType
)} ${getTextSpanString(node, this._lines)} =>`;
}
@ -1066,9 +1065,9 @@ function getErrorExpressionCategoryString(type: ErrorExpressionCategory) {
}
}
function getTokenString(file: string, token: Token, lines: TextRangeCollection<TextRange>) {
function getTokenString(uri: Uri, token: Token, lines: TextRangeCollection<TextRange>) {
const pos = convertOffsetToPosition(token.start, lines);
let str = `'${file}:${pos.line + 1}:${pos.character + 1}' (`;
let str = `'${uri.toUserVisibleString()}:${pos.line + 1}:${pos.character + 1}' (`;
str += getTokenTypeString(token.type);
str += getNewLineInfo(token);
str += getOperatorInfo(token);

View File

@ -8,6 +8,7 @@
import { CancellationToken, ExecuteCommandParams } from 'vscode-languageserver';
import { Uri } from '../common/uri/uri';
import { convertToFileTextEdits, convertToWorkspaceEdit } from '../common/workspaceEditUtils';
import { LanguageServerInterface } from '../languageServerBase';
import { performQuickAction } from '../languageService/quickActions';
@ -19,20 +20,19 @@ export class QuickActionCommand implements ServerCommand {
async execute(params: ExecuteCommandParams, token: CancellationToken): Promise<any> {
if (params.arguments && params.arguments.length >= 1) {
const docUri = params.arguments[0] as string;
const docUri = Uri.parse(params.arguments[0] as string, this._ls.rootUri.isCaseSensitive);
const otherArgs = params.arguments.slice(1);
const filePath = this._ls.decodeTextDocumentUri(docUri);
const workspace = await this._ls.getWorkspaceForFile(filePath);
const workspace = await this._ls.getWorkspaceForFile(docUri);
if (params.command === Commands.orderImports && workspace.disableOrganizeImports) {
return [];
}
const editActions = workspace.service.run((p) => {
return performQuickAction(p, filePath, params.command, otherArgs, token);
return performQuickAction(p, docUri, params.command, otherArgs, token);
}, token);
return convertToWorkspaceEdit(workspace.service.fs, convertToFileTextEdits(filePath, editActions ?? []));
return convertToWorkspaceEdit(convertToFileTextEdits(docUri, editActions ?? []));
}
}
}

View File

@ -18,19 +18,11 @@ import { TaskListToken } from './diagnostic';
import { DiagnosticRule } from './diagnosticRules';
import { FileSystem } from './fileSystem';
import { Host } from './host';
import {
FileSpec,
combinePaths,
ensureTrailingDirectorySeparator,
getFileSpec,
isDirectory,
normalizePath,
realCasePath,
resolvePaths,
} from './pathUtils';
import { PythonVersion, latestStablePythonVersion, versionFromString, versionToString } from './pythonVersion';
import { ServiceProvider } from './serviceProvider';
import { ServiceKeys } from './serviceProviderExtensions';
import { Uri } from './uri/uri';
import { FileSpec, getFileSpec, isDirectory } from './uri/uriUtils';
export enum PythonPlatform {
Darwin = 'Darwin',
@ -39,10 +31,9 @@ export enum PythonPlatform {
}
export class ExecutionEnvironment {
// Root directory for execution - absolute or relative to the
// project root.
// Root directory for execution.
// Undefined if this is a rootless environment (e.g., open file mode).
root?: string;
root?: Uri;
// Name of a virtual environment if there is one, otherwise
// just the path to the python executable.
@ -55,18 +46,18 @@ export class ExecutionEnvironment {
pythonPlatform?: string | undefined;
// Default to no extra paths.
extraPaths: string[] = [];
extraPaths: Uri[] = [];
// Default to "." which indicates every file in the project.
constructor(
name: string,
root: string,
root: Uri,
defaultPythonVersion: PythonVersion | undefined,
defaultPythonPlatform: string | undefined,
defaultExtraPaths: string[] | undefined
defaultExtraPaths: Uri[] | undefined
) {
this.name = name;
this.root = root || undefined;
this.root = root;
this.pythonVersion = defaultPythonVersion || latestStablePythonVersion;
this.pythonPlatform = defaultPythonPlatform;
this.extraPaths = Array.from(defaultExtraPaths ?? []);
@ -780,9 +771,9 @@ export function getStrictDiagnosticRuleSet(): DiagnosticRuleSet {
return diagSettings;
}
export function matchFileSpecs(configOptions: ConfigOptions, filePath: string, isFile = true) {
export function matchFileSpecs(configOptions: ConfigOptions, uri: Uri, isFile = true) {
for (const includeSpec of configOptions.include) {
if (FileSpec.matchIncludeFileSpec(includeSpec.regExp, configOptions.exclude, filePath, isFile)) {
if (FileSpec.matchIncludeFileSpec(includeSpec.regExp, configOptions.exclude, uri, isFile)) {
return true;
}
}
@ -795,19 +786,19 @@ export function matchFileSpecs(configOptions: ConfigOptions, filePath: string, i
export class ConfigOptions {
// Absolute directory of project. All relative paths in the config
// are based on this path.
projectRoot: string;
projectRoot: Uri;
// Path to python interpreter.
pythonPath?: string | undefined;
pythonPath?: Uri | undefined;
// Name of the python environment.
pythonEnvironmentName?: string | undefined;
// Path to use for typeshed definitions.
typeshedPath?: string | undefined;
typeshedPath?: Uri | undefined;
// Path to custom typings (stub) modules.
stubPath?: string | undefined;
stubPath?: Uri | undefined;
// A list of file specs to include in the analysis. Can contain
// directories, in which case all "*.py" files within those directories
@ -887,7 +878,7 @@ export class ConfigOptions {
// directories. This is used in conjunction with the "venv" name in
// the config file to identify the python environment used for resolving
// third-party modules.
venvPath?: string | undefined;
venvPath?: Uri | undefined;
// Default venv environment.
venv?: string | undefined;
@ -899,7 +890,7 @@ export class ConfigOptions {
defaultPythonPlatform?: string | undefined;
// Default extraPaths. Can be overridden by executionEnvironment.
defaultExtraPaths?: string[] | undefined;
defaultExtraPaths?: Uri[] | undefined;
//---------------------------------------------------------------
// Internal-only switches
@ -917,7 +908,7 @@ export class ConfigOptions {
// Controls how hover and completion function signatures are displayed.
functionSignatureDisplay: SignatureDisplayType;
constructor(projectRoot: string, typeCheckingMode?: string) {
constructor(projectRoot: Uri, typeCheckingMode?: string) {
this.projectRoot = projectRoot;
this.typeCheckingMode = typeCheckingMode;
this.diagnosticRuleSet = ConfigOptions.getDiagnosticRuleSet(typeCheckingMode);
@ -950,17 +941,15 @@ export class ConfigOptions {
);
}
// Finds the best execution environment for a given file path. The
// Finds the best execution environment for a given file uri. The
// specified file path should be absolute.
// If no matching execution environment can be found, a default
// execution environment is used.
findExecEnvironment(filePath: string): ExecutionEnvironment {
findExecEnvironment(file: Uri): ExecutionEnvironment {
return (
this.executionEnvironments.find((env) => {
const envRoot = ensureTrailingDirectorySeparator(
normalizePath(combinePaths(this.projectRoot, env.root))
);
return filePath.startsWith(envRoot);
const envRoot = Uri.isUri(env.root) ? env.root : this.projectRoot.combinePaths(env.root || '');
return file.startsWith(envRoot);
}) ?? this.getDefaultExecEnvironment()
);
}
@ -997,7 +986,7 @@ export class ConfigOptions {
} else if (isAbsolute(fileSpec)) {
console.error(`Ignoring path "${fileSpec}" in "include" array because it is not relative.`);
} else {
this.include.push(getFileSpec(serviceProvider, this.projectRoot, fileSpec));
this.include.push(getFileSpec(this.projectRoot, fileSpec));
}
});
}
@ -1016,7 +1005,7 @@ export class ConfigOptions {
} else if (isAbsolute(fileSpec)) {
console.error(`Ignoring path "${fileSpec}" in "exclude" array because it is not relative.`);
} else {
this.exclude.push(getFileSpec(serviceProvider, this.projectRoot, fileSpec));
this.exclude.push(getFileSpec(this.projectRoot, fileSpec));
}
});
}
@ -1035,7 +1024,7 @@ export class ConfigOptions {
} else if (isAbsolute(fileSpec)) {
console.error(`Ignoring path "${fileSpec}" in "ignore" array because it is not relative.`);
} else {
this.ignore.push(getFileSpec(serviceProvider, this.projectRoot, fileSpec));
this.ignore.push(getFileSpec(this.projectRoot, fileSpec));
}
});
}
@ -1054,7 +1043,7 @@ export class ConfigOptions {
} else if (isAbsolute(fileSpec)) {
console.error(`Ignoring path "${fileSpec}" in "strict" array because it is not relative.`);
} else {
this.strict.push(getFileSpec(serviceProvider, this.projectRoot, fileSpec));
this.strict.push(getFileSpec(this.projectRoot, fileSpec));
}
});
}
@ -1116,7 +1105,7 @@ export class ConfigOptions {
if (typeof configObj.venvPath !== 'string') {
console.error(`Config "venvPath" field must contain a string.`);
} else {
this.venvPath = normalizePath(combinePaths(this.projectRoot, configObj.venvPath));
this.venvPath = this.projectRoot.combinePaths(configObj.venvPath);
}
}
@ -1141,7 +1130,7 @@ export class ConfigOptions {
if (typeof path !== 'string') {
console.error(`Config "extraPaths" field ${pathIndex} must be a string.`);
} else {
this.defaultExtraPaths!.push(normalizePath(combinePaths(this.projectRoot, path)));
this.defaultExtraPaths!.push(this.projectRoot.combinePaths(path));
}
});
}
@ -1181,8 +1170,8 @@ export class ConfigOptions {
console.error(`Config "typeshedPath" field must contain a string.`);
} else {
this.typeshedPath = configObj.typeshedPath
? normalizePath(combinePaths(this.projectRoot, configObj.typeshedPath))
: '';
? this.projectRoot.combinePaths(configObj.typeshedPath)
: undefined;
}
}
@ -1195,7 +1184,7 @@ export class ConfigOptions {
console.error(`Config "typingsPath" field must contain a string.`);
} else {
console.error(`Config "typingsPath" is now deprecated. Please, use stubPath instead.`);
this.stubPath = normalizePath(combinePaths(this.projectRoot, configObj.typingsPath));
this.stubPath = this.projectRoot.combinePaths(configObj.typingsPath);
}
}
@ -1203,7 +1192,7 @@ export class ConfigOptions {
if (typeof configObj.stubPath !== 'string') {
console.error(`Config "stubPath" field must contain a string.`);
} else {
this.stubPath = normalizePath(combinePaths(this.projectRoot, configObj.stubPath));
this.stubPath = this.projectRoot.combinePaths(configObj.stubPath);
}
}
@ -1345,20 +1334,20 @@ export class ConfigOptions {
}
ensureDefaultExtraPaths(fs: FileSystem, autoSearchPaths: boolean, extraPaths: string[] | undefined) {
const paths: string[] = [];
const paths: Uri[] = [];
if (autoSearchPaths) {
// Auto-detect the common scenario where the sources are under the src folder
const srcPath = resolvePaths(this.projectRoot, pathConsts.src);
if (fs.existsSync(srcPath) && !fs.existsSync(resolvePaths(srcPath, '__init__.py'))) {
paths.push(realCasePath(srcPath, fs));
const srcPath = this.projectRoot.combinePaths(pathConsts.src);
if (fs.existsSync(srcPath) && !fs.existsSync(srcPath.combinePaths('__init__.py'))) {
paths.push(fs.realCasePath(srcPath));
}
}
if (extraPaths && extraPaths.length > 0) {
for (const p of extraPaths) {
const path = resolvePaths(this.projectRoot, p);
paths.push(realCasePath(path, fs));
const path = this.projectRoot.combinePaths(p);
paths.push(fs.realCasePath(path));
if (isDirectory(fs, path)) {
appendArray(paths, getPathsFromPthFiles(fs, path));
}
@ -1384,7 +1373,7 @@ export class ConfigOptions {
}
private _getEnvironmentName(): string {
return this.pythonEnvironmentName || this.pythonPath || 'python';
return this.pythonEnvironmentName || this.pythonPath?.toString() || 'python';
}
private _convertBoolean(value: any, fieldName: string, defaultValue: boolean): boolean {
@ -1429,7 +1418,7 @@ export class ConfigOptions {
// Validate the root.
if (envObj.root && typeof envObj.root === 'string') {
newExecEnv.root = normalizePath(combinePaths(this.projectRoot, envObj.root));
newExecEnv.root = this.projectRoot.combinePaths(envObj.root);
} else {
console.error(`Config executionEnvironments index ${index}: missing root value.`);
}
@ -1449,7 +1438,7 @@ export class ConfigOptions {
` extraPaths field ${pathIndex} must be a string.`
);
} else {
newExecEnv.extraPaths.push(normalizePath(combinePaths(this.projectRoot, path)));
newExecEnv.extraPaths.push(this.projectRoot.combinePaths(path));
}
});
}

View File

@ -11,6 +11,7 @@ import { Commands } from '../commands/commands';
import { appendArray } from './collectionUtils';
import { DiagnosticLevel } from './configOptions';
import { Range, TextRange } from './textRange';
import { Uri } from './uri/uri';
const defaultMaxDepth = 5;
const defaultMaxLineCount = 8;
@ -63,7 +64,7 @@ export interface DiagnosticAction {
}
export interface DiagnosticWithinFile {
filePath: string;
uri: Uri;
diagnostic: Diagnostic;
}
@ -74,13 +75,13 @@ export interface CreateTypeStubFileAction extends DiagnosticAction {
export interface RenameShadowedFileAction extends DiagnosticAction {
action: ActionKind.RenameShadowedFileAction;
oldFile: string;
newFile: string;
oldUri: Uri;
newUri: Uri;
}
export interface DiagnosticRelatedInfo {
message: string;
filePath: string;
uri: Uri;
range: Range;
priority: TaskListPriority;
}
@ -118,13 +119,8 @@ export class Diagnostic {
return this._rule;
}
addRelatedInfo(
message: string,
filePath: string,
range: Range,
priority: TaskListPriority = TaskListPriority.Normal
) {
this._relatedInfo.push({ filePath, message, range, priority });
addRelatedInfo(message: string, fileUri: Uri, range: Range, priority: TaskListPriority = TaskListPriority.Normal) {
this._relatedInfo.push({ uri: fileUri, message, range, priority });
}
getRelatedInfo() {

View File

@ -14,10 +14,11 @@ import { convertOffsetsToRange } from './positionUtils';
import { hashString } from './stringUtils';
import { Range, TextRange } from './textRange';
import { TextRangeCollection } from './textRangeCollection';
import { Uri } from './uri/uri';
// Represents a collection of diagnostics within a file.
export interface FileDiagnostics {
filePath: string;
fileUri: Uri;
version: number | undefined;
diagnostics: Diagnostic[];
}

View File

@ -8,6 +8,7 @@
*/
import { Range, rangesAreEqual } from './textRange';
import { Uri } from './uri/uri';
export interface TextEditAction {
range: Range;
@ -15,7 +16,7 @@ export interface TextEditAction {
}
export interface FileEditAction extends TextEditAction {
filePath: string;
fileUri: Uri;
}
export interface FileEditActions {
@ -31,18 +32,18 @@ export interface FileOperation {
export interface RenameFileOperation extends FileOperation {
kind: 'rename';
oldFilePath: string;
newFilePath: string;
oldFileUri: Uri;
newFileUri: Uri;
}
export interface CreateFileOperation extends FileOperation {
kind: 'create';
filePath: string;
fileUri: Uri;
}
export interface DeleteFileOperation extends FileOperation {
kind: 'delete';
filePath: string;
fileUri: Uri;
}
export namespace TextEditAction {
@ -53,13 +54,13 @@ export namespace TextEditAction {
export namespace FileEditAction {
export function is(value: any): value is FileEditAction {
return value.filePath !== undefined && TextEditAction.is(value);
return value.fileUri !== undefined && TextEditAction.is(value);
}
export function areEqual(e1: FileEditAction, e2: FileEditAction) {
return (
e1 === e2 ||
(e1.filePath === e2.filePath &&
(e1.fileUri.equals(e2.fileUri) &&
rangesAreEqual(e1.range, e2.range) &&
e1.replacementText === e2.replacementText)
);

View File

@ -8,43 +8,35 @@
import * as os from 'os';
import {
combinePaths,
ensureTrailingDirectorySeparator,
getPathComponents,
hasTrailingDirectorySeparator,
} from './pathUtils';
import { Uri } from './uri/uri';
// Expands certain predefined variables supported within VS Code settings.
// Ideally, VS Code would provide an API for doing this expansion, but
// it doesn't. We'll handle the most common variables here as a convenience.
export function expandPathVariables(rootPath: string, path: string): string {
const pathParts = getPathComponents(path);
export function expandPathVariables(rootPath: Uri, path: string): string {
// Make sure the pathStr looks like a URI path.
let pathStr = path.replace(/\\/g, '/');
const expandedParts: string[] = [];
for (const part of pathParts) {
const trimmedPart = part.trim();
// Make sure all replacements look like URI paths too.
const replace = (match: RegExp, replaceValue: string) => {
pathStr = pathStr.replace(match, replaceValue.replace(/\\/g, '/'));
};
if (trimmedPart === '${workspaceFolder}') {
expandedParts.push(rootPath);
} else if (trimmedPart === '${env:HOME}' && process.env.HOME !== undefined) {
expandedParts.push(process.env.HOME);
} else if (trimmedPart === '${env:USERNAME}' && process.env.USERNAME !== undefined) {
expandedParts.push(process.env.USERNAME);
} else if (trimmedPart === '${env:VIRTUAL_ENV}' && process.env.VIRTUAL_ENV !== undefined) {
expandedParts.push(process.env.VIRTUAL_ENV);
} else if (trimmedPart === '~' && os.homedir) {
expandedParts.push(os.homedir() || process.env.HOME || process.env.USERPROFILE || '~');
} else {
expandedParts.push(part);
}
// Replace everything inline.
pathStr = pathStr.replace(/\$\{workspaceFolder\}/g, rootPath.getPath());
if (process.env.HOME !== undefined) {
replace(/\$\{env:HOME\}/g, process.env.HOME || '');
}
if (process.env.USERNAME !== undefined) {
replace(/\$\{env:USERNAME\}/g, process.env.USERNAME || '');
}
if (process.env.VIRTUAL_ENV !== undefined) {
replace(/\$\{env:VIRTUAL_ENV\}/g, process.env.VIRTUAL_ENV || '');
}
if (os.homedir) {
replace(/\/~/g, os.homedir() || process.env.HOME || process.env.USERPROFILE || '~');
replace(/^~/g, os.homedir() || process.env.HOME || process.env.USERPROFILE || '~');
}
if (expandedParts.length === 0) {
return path;
}
const root = expandedParts.shift()!;
const expandedPath = combinePaths(root, ...expandedParts);
return hasTrailingDirectorySeparator(path) ? ensureTrailingDirectorySeparator(expandedPath) : expandedPath;
return pathStr;
}

View File

@ -11,19 +11,20 @@ import { CancellationToken } from 'vscode-languageserver';
import { Declaration } from '../analyzer/declaration';
import { ImportResolver } from '../analyzer/importResolver';
import * as prog from '../analyzer/program';
import { IPythonMode } from '../analyzer/sourceFile';
import { SourceMapper } from '../analyzer/sourceMapper';
import { SymbolTable } from '../analyzer/symbol';
import { TypeEvaluator } from '../analyzer/typeEvaluatorTypes';
import { Diagnostic } from '../common/diagnostic';
import { ServerSettings } from '../languageServerBase';
import { ParseNode } from '../parser/parseNodes';
import { ParseResults } from '../parser/parser';
import { ConfigOptions } from './configOptions';
import { ConsoleInterface } from './console';
import { ReadOnlyFileSystem } from './fileSystem';
import { Range } from './textRange';
import { SymbolTable } from '../analyzer/symbol';
import { Diagnostic } from '../common/diagnostic';
import { IPythonMode } from '../analyzer/sourceFile';
import { GroupServiceKey, ServiceKey } from './serviceProvider';
import { Range } from './textRange';
import { Uri } from './uri/uri';
export interface SourceFile {
// See whether we can convert these to regular properties.
@ -31,9 +32,8 @@ export interface SourceFile {
isThirdPartyPyTypedPresent(): boolean;
getIPythonMode(): IPythonMode;
getFilePath(): string;
getUri(): Uri;
getFileContent(): string | undefined;
getRealFilePath(): string | undefined;
getClientVersion(): number | undefined;
getOpenFileContents(): string | undefined;
getModuleSymbolTable(): SymbolTable | undefined;
@ -73,7 +73,7 @@ export interface ServiceProvider {
// Readonly wrapper around a Program. Makes sure it doesn't mutate the program.
export interface ProgramView {
readonly id: string;
readonly rootPath: string;
readonly rootPath: Uri;
readonly console: ConsoleInterface;
readonly evaluator: TypeEvaluator | undefined;
readonly configOptions: ConfigOptions;
@ -81,21 +81,16 @@ export interface ProgramView {
readonly fileSystem: ReadOnlyFileSystem;
readonly serviceProvider: ServiceProvider;
owns(file: string): boolean;
owns(uri: Uri): boolean;
getSourceFileInfoList(): readonly SourceFileInfo[];
getParseResults(filePath: string): ParseResults | undefined;
getSourceFileInfo(filePath: string): SourceFileInfo | undefined;
getChainedFilePath(filePath: string): string | undefined;
getSourceMapper(
filePath: string,
token: CancellationToken,
mapCompiled?: boolean,
preferStubs?: boolean
): SourceMapper;
getParseResults(fileUri: Uri): ParseResults | undefined;
getSourceFileInfo(fileUri: Uri): SourceFileInfo | undefined;
getChainedUri(fileUri: Uri): Uri | undefined;
getSourceMapper(fileUri: Uri, token: CancellationToken, mapCompiled?: boolean, preferStubs?: boolean): SourceMapper;
// Consider getDiagnosticsForRange to call `analyzeFile` automatically if the file is not analyzed.
analyzeFile(filePath: string, token: CancellationToken): boolean;
getDiagnosticsForRange(filePath: string, range: Range): Diagnostic[];
analyzeFile(fileUri: Uri, token: CancellationToken): boolean;
getDiagnosticsForRange(fileUri: Uri, range: Range): Diagnostic[];
// See whether we can get rid of these methods
handleMemoryHighUsage(): void;
@ -106,9 +101,9 @@ export interface ProgramView {
// and doesn't forward the request to the BG thread.
// One can use this when edits are temporary such as `runEditMode` or `test`
export interface EditableProgram extends ProgramView {
addInterimFile(file: string): void;
setFileOpened(filePath: string, version: number | null, contents: string, options?: prog.OpenFileOptions): void;
updateChainedFilePath(filePath: string, chainedFilePath: string | undefined): void;
addInterimFile(uri: Uri): void;
setFileOpened(fileUri: Uri, version: number | null, contents: string, options?: prog.OpenFileOptions): void;
updateChainedUri(fileUri: Uri, chainedUri: Uri | undefined): void;
}
// Mutable wrapper around a program. Allows the FG thread to forward this request to the BG thread
@ -116,7 +111,7 @@ export interface EditableProgram extends ProgramView {
export interface ProgramMutator {
addInterimFile(file: string): void;
setFileOpened(
filePath: string,
fileUri: Uri,
version: number | null,
contents: string,
ipythonMode: IPythonMode,
@ -162,7 +157,7 @@ export interface SymbolUsageProvider {
}
export interface StatusMutationListener {
fileDirty?: (filePath: string) => void;
fileDirty?: (fileUri: Uri) => void;
clearCache?: () => void;
updateSettings?: <T extends ServerSettings>(settings: T) => void;
}

View File

@ -11,6 +11,7 @@
// * NOTE * except tests, this should be only file that import "fs"
import type * as fs from 'fs';
import { FileWatcher, FileWatcherEventHandler } from './fileWatcher';
import { Uri } from './uri/uri';
export interface Stats {
size: number;
@ -33,48 +34,47 @@ export interface MkDirOptions {
}
export interface ReadOnlyFileSystem {
existsSync(path: string): boolean;
chdir(path: string): void;
readdirEntriesSync(path: string): fs.Dirent[];
readdirSync(path: string): string[];
readFileSync(path: string, encoding?: null): Buffer;
readFileSync(path: string, encoding: BufferEncoding): string;
readFileSync(path: string, encoding?: BufferEncoding | null): string | Buffer;
readonly isCaseSensitive: boolean;
existsSync(uri: Uri): boolean;
chdir(uri: Uri): void;
readdirEntriesSync(uri: Uri): fs.Dirent[];
readdirSync(uri: Uri): string[];
readFileSync(uri: Uri, encoding?: null): Buffer;
readFileSync(uri: Uri, encoding: BufferEncoding): string;
readFileSync(uri: Uri, encoding?: BufferEncoding | null): string | Buffer;
statSync(path: string): Stats;
realpathSync(path: string): string;
getModulePath(): string;
statSync(uri: Uri): Stats;
realpathSync(uri: Uri): Uri;
getModulePath(): Uri;
// Async I/O
readFile(path: string): Promise<Buffer>;
readFileText(path: string, encoding?: BufferEncoding): Promise<string>;
readFile(uri: Uri): Promise<Buffer>;
readFileText(uri: Uri, encoding?: BufferEncoding): Promise<string>;
// Return path in casing on OS.
realCasePath(path: string): string;
realCasePath(uri: Uri): Uri;
// See whether the file is mapped to another location.
isMappedFilePath(filepath: string): boolean;
isMappedUri(uri: Uri): boolean;
// Get original filepath if the given filepath is mapped.
getOriginalFilePath(mappedFilePath: string): string;
// Get original uri if the given uri is mapped.
getOriginalUri(mappedUri: Uri): Uri;
// Get mapped filepath if the given filepath is mapped.
getMappedFilePath(originalFilepath: string): string;
// Get mapped uri if the given uri is mapped.
getMappedUri(originalUri: Uri): Uri;
getUri(path: string): string;
isInZip(path: string): boolean;
isInZip(uri: Uri): boolean;
}
export interface FileSystem extends ReadOnlyFileSystem {
mkdirSync(path: string, options?: MkDirOptions): void;
writeFileSync(path: string, data: string | Buffer, encoding: BufferEncoding | null): void;
mkdirSync(uri: Uri, options?: MkDirOptions): void;
writeFileSync(uri: Uri, data: string | Buffer, encoding: BufferEncoding | null): void;
unlinkSync(path: string): void;
rmdirSync(path: string): void;
unlinkSync(uri: Uri): void;
rmdirSync(uri: Uri): void;
createFileSystemWatcher(paths: string[], listener: FileWatcherEventHandler): FileWatcher;
createReadStream(path: string): fs.ReadStream;
createWriteStream(path: string): fs.WriteStream;
copyFileSync(src: string, dst: string): void;
createFileSystemWatcher(uris: Uri[], listener: FileWatcherEventHandler): FileWatcher;
createReadStream(uri: Uri): fs.ReadStream;
createWriteStream(uri: Uri): fs.WriteStream;
copyFileSync(uri: Uri, dst: Uri): void;
}
export interface TmpfileOptions {
@ -84,8 +84,8 @@ export interface TmpfileOptions {
export interface TempFile {
// The directory returned by tmpdir must exist and be the same each time tmpdir is called.
tmpdir(): string;
tmpfile(options?: TmpfileOptions): string;
tmpdir(): Uri;
tmpfile(options?: TmpfileOptions): Uri;
dispose(): void;
}

View File

@ -6,16 +6,17 @@
* file watcher related functionality.
*/
import { Stats } from './fileSystem';
import { Uri } from './uri/uri';
export type FileWatcherEventType = 'add' | 'addDir' | 'change' | 'unlink' | 'unlinkDir';
export type FileWatcherEventHandler = (eventName: FileWatcherEventType, path: string, stats?: Stats) => void;
export type FileWatcherEventHandler = (eventName: FileWatcherEventType, uri: Uri, stats?: Stats) => void;
export interface FileWatcher {
close(): void;
}
export interface FileWatcherHandler {
onFileChange(eventType: FileWatcherEventType, path: string): void;
onFileChange(eventType: FileWatcherEventType, uri: Uri): void;
}
export interface FileWatcherProvider {
@ -23,7 +24,7 @@ export interface FileWatcherProvider {
}
export const nullFileWatcherHandler: FileWatcherHandler = {
onFileChange(_1: FileWatcherEventType, _2: string): void {
onFileChange(_1: FileWatcherEventType, _2: Uri): void {
// do nothing
},
};

View File

@ -13,10 +13,12 @@ import { PythonPathResult } from '../analyzer/pythonPathUtils';
import { OperationCanceledException, throwIfCancellationRequested } from './cancellationUtils';
import { PythonPlatform } from './configOptions';
import { assertNever } from './debug';
import { FileSystem } from './fileSystem';
import { HostKind, NoAccessHost, ScriptOutput } from './host';
import { isDirectory, normalizePath } from './pathUtils';
import { normalizePath } from './pathUtils';
import { PythonVersion, versionFromMajorMinor } from './pythonVersion';
import { ServiceProvider } from './serviceProvider';
import { Uri } from './uri/uri';
import { isDirectory } from './uri/uriUtils';
// preventLocalImports removes the working directory from sys.path.
// The -c flag adds it automatically, which can allow some stdlib
@ -60,7 +62,7 @@ export class LimitedAccessHost extends NoAccessHost {
}
export class FullAccessHost extends LimitedAccessHost {
constructor(protected fs: FileSystem) {
constructor(protected serviceProvider: ServiceProvider) {
super();
}
@ -68,29 +70,29 @@ export class FullAccessHost extends LimitedAccessHost {
return HostKind.FullAccess;
}
static createHost(kind: HostKind, fs: FileSystem) {
static createHost(kind: HostKind, serviceProvider: ServiceProvider) {
switch (kind) {
case HostKind.NoAccess:
return new NoAccessHost();
case HostKind.LimitedAccess:
return new LimitedAccessHost();
case HostKind.FullAccess:
return new FullAccessHost(fs);
return new FullAccessHost(serviceProvider);
default:
assertNever(kind);
}
}
override getPythonSearchPaths(pythonPath?: string, logInfo?: string[]): PythonPathResult {
override getPythonSearchPaths(pythonPath?: Uri, logInfo?: string[]): PythonPathResult {
const importFailureInfo = logInfo ?? [];
let result = this._executePythonInterpreter(pythonPath, (p) =>
this._getSearchPathResultFromInterpreter(this.fs, p, importFailureInfo)
let result = this._executePythonInterpreter(pythonPath?.getFilePath(), (p) =>
this._getSearchPathResultFromInterpreter(p, importFailureInfo)
);
if (!result) {
result = {
paths: [],
prefix: '',
prefix: undefined,
};
}
@ -102,12 +104,12 @@ export class FullAccessHost extends LimitedAccessHost {
return result;
}
override getPythonVersion(pythonPath?: string, logInfo?: string[]): PythonVersion | undefined {
override getPythonVersion(pythonPath?: Uri, logInfo?: string[]): PythonVersion | undefined {
const importFailureInfo = logInfo ?? [];
try {
const commandLineArgs: string[] = ['-c', extractVersion];
const execOutput = this._executePythonInterpreter(pythonPath, (p) =>
const execOutput = this._executePythonInterpreter(pythonPath?.getFilePath(), (p) =>
child_process.execFileSync(p, commandLineArgs, { encoding: 'utf8' })
);
@ -128,8 +130,8 @@ export class FullAccessHost extends LimitedAccessHost {
}
override runScript(
pythonPath: string | undefined,
script: string,
pythonPath: Uri | undefined,
script: Uri,
args: string[],
cwd: string,
token: CancellationToken
@ -141,9 +143,9 @@ export class FullAccessHost extends LimitedAccessHost {
return new Promise<{ stdout: string; stderr: string }>((resolve, reject) => {
let stdout = '';
let stderr = '';
const commandLineArgs = [script, ...args];
const commandLineArgs = [script.getFilePath(), ...args];
const child = this._executePythonInterpreter(pythonPath, (p) =>
const child = this._executePythonInterpreter(pythonPath?.getFilePath(), (p) =>
child_process.spawn(p, commandLineArgs, { cwd })
);
const tokenWatch = token.onCancellationRequested(() => {
@ -210,20 +212,19 @@ export class FullAccessHost extends LimitedAccessHost {
}
private _getSearchPathResultFromInterpreter(
fs: FileSystem,
interpreter: string,
interpreterPath: string,
importFailureInfo: string[]
): PythonPathResult | undefined {
const result: PythonPathResult = {
paths: [],
prefix: '',
prefix: undefined,
};
try {
const commandLineArgs: string[] = ['-c', extractSys];
importFailureInfo.push(`Executing interpreter: '${interpreter}'`);
const execOutput = child_process.execFileSync(interpreter, commandLineArgs, { encoding: 'utf8' });
importFailureInfo.push(`Executing interpreter: '${interpreterPath}'`);
const execOutput = child_process.execFileSync(interpreterPath, commandLineArgs, { encoding: 'utf8' });
const isCaseSensitive = this.serviceProvider.fs().isCaseSensitive;
// Parse the execOutput. It should be a JSON-encoded array of paths.
try {
@ -232,16 +233,20 @@ export class FullAccessHost extends LimitedAccessHost {
execSplitEntry = execSplitEntry.trim();
if (execSplitEntry) {
const normalizedPath = normalizePath(execSplitEntry);
const normalizedUri = Uri.file(normalizedPath, isCaseSensitive);
// Skip non-existent paths and broken zips/eggs.
if (fs.existsSync(normalizedPath) && isDirectory(fs, normalizedPath)) {
result.paths.push(normalizedPath);
if (
this.serviceProvider.fs().existsSync(normalizedUri) &&
isDirectory(this.serviceProvider.fs(), normalizedUri)
) {
result.paths.push(normalizedUri);
} else {
importFailureInfo.push(`Skipping '${normalizedPath}' because it is not a valid directory`);
}
}
}
result.prefix = execSplit.prefix;
result.prefix = Uri.file(execSplit.prefix, isCaseSensitive);
if (result.paths.length === 0) {
importFailureInfo.push(`Found no valid directories`);

View File

@ -11,6 +11,7 @@ import { CancellationToken } from 'vscode-languageserver';
import { PythonPathResult } from '../analyzer/pythonPathUtils';
import { PythonPlatform } from './configOptions';
import { PythonVersion } from './pythonVersion';
import { Uri } from './uri/uri';
export const enum HostKind {
FullAccess,
@ -25,12 +26,12 @@ export interface ScriptOutput {
export interface Host {
readonly kind: HostKind;
getPythonSearchPaths(pythonPath?: string, logInfo?: string[]): PythonPathResult;
getPythonVersion(pythonPath?: string, logInfo?: string[]): PythonVersion | undefined;
getPythonSearchPaths(pythonPath?: Uri, logInfo?: string[]): PythonPathResult;
getPythonVersion(pythonPath?: Uri, logInfo?: string[]): PythonVersion | undefined;
getPythonPlatform(logInfo?: string[]): PythonPlatform | undefined;
runScript(
pythonPath: string | undefined,
script: string,
pythonPath: Uri | undefined,
script: Uri,
args: string[],
cwd: string,
token: CancellationToken
@ -42,16 +43,16 @@ export class NoAccessHost implements Host {
return HostKind.NoAccess;
}
getPythonSearchPaths(pythonPath?: string, logInfo?: string[]): PythonPathResult {
getPythonSearchPaths(pythonPath?: Uri, logInfo?: string[]): PythonPathResult {
logInfo?.push('No access to python executable.');
return {
paths: [],
prefix: '',
prefix: undefined,
};
}
getPythonVersion(pythonPath?: string, logInfo?: string[]): PythonVersion | undefined {
getPythonVersion(pythonPath?: Uri, logInfo?: string[]): PythonVersion | undefined {
return undefined;
}
@ -60,8 +61,8 @@ export class NoAccessHost implements Host {
}
async runScript(
pythonPath: string | undefined,
scriptPath: string,
pythonPath: Uri | undefined,
scriptPath: Uri,
args: string[],
cwd: string,
token: CancellationToken

View File

@ -9,16 +9,17 @@
import { ConsoleInterface, LogLevel } from './console';
import { ReadOnlyFileSystem } from './fileSystem';
import { Duration, timingStats } from './timing';
import { Uri } from './uri/uri';
// Consider an operation "long running" if it goes longer than this.
const durationThresholdForInfoInMs = 2000;
export function getPathForLogging(fs: ReadOnlyFileSystem, filepath: string) {
if (fs.isMappedFilePath(filepath)) {
return fs.getOriginalFilePath(filepath);
export function getPathForLogging(fs: ReadOnlyFileSystem, fileUri: Uri) {
if (fs.isMappedUri(fileUri)) {
return fs.getOriginalUri(fileUri);
}
return filepath;
return fileUri;
}
export class LogTracker {

View File

@ -7,24 +7,14 @@
* Pathname utility functions.
*/
import type { Dirent } from 'fs';
import * as path from 'path';
import { URI, Utils } from 'vscode-uri';
import { Char } from './charCodes';
import { some } from './collectionUtils';
import { GetCanonicalFileName, identity } from './core';
import { randomBytesHex } from './crypto';
import * as debug from './debug';
import { ServiceProvider } from './extensibility';
import { FileSystem, ReadOnlyFileSystem, Stats, TempFile } from './fileSystem';
import { ServiceKeys } from './serviceProviderExtensions';
import { equateStringsCaseInsensitive, equateStringsCaseSensitive } from './stringUtils';
let _fsCaseSensitivity: boolean | undefined = undefined;
let _underTest: boolean = false;
const _uriSchemePattern = /^\w[\w\d+.-]*$/;
export interface FileSpec {
// File specs can contain wildcard characters (**, *, ?). This
// specifies the first portion of the file spec that contains
@ -72,52 +62,26 @@ export interface FileSystemEntries {
directories: string[];
}
export function forEachAncestorDirectory(
directory: string,
callback: (directory: string) => string | undefined
): string | undefined {
while (true) {
const result = callback(directory);
if (result !== undefined) {
return result;
}
const parentPath = getDirectoryPath(directory);
if (parentPath === directory) {
return undefined;
}
directory = parentPath;
}
}
export function getDirectoryPath(pathString: string): string {
if (isUri(pathString)) {
return Utils.dirname(URI.parse(pathString).with({ fragment: '', query: '' })).toString();
}
return pathString.substr(0, Math.max(getRootLength(pathString), pathString.lastIndexOf(path.sep)));
}
export function isUri(pathString: string) {
return pathString.indexOf(':') > 1 && _uriSchemePattern.test(pathString.split(':')[0]);
}
/**
* Returns length of the root part of a path or URL (i.e. length of "/", "x:/", "//server/").
*/
export function getRootLength(pathString: string, checkUri = true): number {
if (pathString.charAt(0) === path.sep) {
if (pathString.charAt(1) !== path.sep) {
export function getRootLength(pathString: string, sep = path.sep): number {
if (pathString.charAt(0) === sep) {
if (pathString.charAt(1) !== sep) {
return 1; // POSIX: "/" (or non-normalized "\")
}
const p1 = pathString.indexOf(path.sep, 2);
const p1 = pathString.indexOf(sep, 2);
if (p1 < 0) {
return pathString.length; // UNC: "//server" or "\\server"
}
return p1 + 1; // UNC: "//server/" or "\\server\"
}
if (pathString.charAt(1) === ':') {
if (pathString.charAt(2) === path.sep) {
if (pathString.charAt(2) === sep) {
return 3; // DOS: "c:/" or "c:\"
}
if (pathString.length === 2) {
@ -125,25 +89,16 @@ export function getRootLength(pathString: string, checkUri = true): number {
}
}
if (checkUri && isUri(pathString)) {
const uri = URI.parse(pathString);
if (uri.authority) {
return uri.scheme.length + 3; // URI: "file://"
} else {
return uri.scheme.length + 1; // URI: "untitled:"
}
}
return 0;
}
export function getPathSeparator(pathString: string) {
return isUri(pathString) ? '/' : path.sep;
return path.sep;
}
export function getPathComponents(pathString: string) {
const normalizedPath = normalizeSlashes(pathString);
const rootLength = getRootLength(normalizedPath, /* checkUri */ isUri(normalizedPath));
const rootLength = getRootLength(normalizedPath);
const root = normalizedPath.substring(0, rootLength);
const sep = getPathSeparator(pathString);
const rest = normalizedPath.substring(rootLength).split(sep);
@ -211,47 +166,11 @@ export function getRelativePath(dirPath: string, relativeTo: string) {
return relativePath;
}
// Creates a directory hierarchy for a path, starting from some ancestor path.
export function makeDirectories(fs: FileSystem, dirPath: string, startingFromDirPath: string) {
if (!dirPath.startsWith(startingFromDirPath)) {
return;
}
const pathComponents = getPathComponents(dirPath);
const relativeToComponents = getPathComponents(startingFromDirPath);
let curPath = startingFromDirPath;
for (let i = relativeToComponents.length; i < pathComponents.length; i++) {
curPath = combinePaths(curPath, pathComponents[i]);
if (!fs.existsSync(curPath)) {
fs.mkdirSync(curPath);
}
}
}
export function getFileSize(fs: ReadOnlyFileSystem, path: string) {
const stat = tryStat(fs, path);
if (stat?.isFile()) {
return stat.size;
}
return 0;
}
export function fileExists(fs: ReadOnlyFileSystem, path: string): boolean {
return fileSystemEntryExists(fs, path, FileSystemEntryKind.File);
}
export function directoryExists(fs: ReadOnlyFileSystem, path: string): boolean {
return fileSystemEntryExists(fs, path, FileSystemEntryKind.Directory);
}
const getInvalidSeparator = (sep: string) => (sep === '/' ? '\\' : '/');
export function normalizeSlashes(pathString: string, sep = path.sep): string {
if (!isUri(pathString)) {
if (pathString.includes(getInvalidSeparator(sep))) {
const separatorRegExp = /[\\/]/g;
return pathString.replace(separatorRegExp, sep);
}
if (pathString.includes(getInvalidSeparator(sep))) {
const separatorRegExp = /[\\/]/g;
return pathString.replace(separatorRegExp, sep);
}
return pathString;
@ -271,7 +190,7 @@ export function resolvePaths(path: string, ...paths: (string | undefined)[]): st
return normalizePath(some(paths) ? combinePaths(path, ...paths) : normalizeSlashes(path));
}
function combineFilePaths(pathString: string, ...paths: (string | undefined)[]): string {
export function combinePaths(pathString: string, ...paths: (string | undefined)[]): string {
if (pathString) {
pathString = normalizeSlashes(pathString);
}
@ -283,7 +202,7 @@ function combineFilePaths(pathString: string, ...paths: (string | undefined)[]):
relativePath = normalizeSlashes(relativePath);
if (!pathString || getRootLength(relativePath, /* checkUri */ false) !== 0) {
if (!pathString || getRootLength(relativePath) !== 0) {
pathString = relativePath;
} else {
pathString = ensureTrailingDirectorySeparator(pathString) + relativePath;
@ -293,29 +212,6 @@ function combineFilePaths(pathString: string, ...paths: (string | undefined)[]):
return pathString;
}
export function combinePaths(pathString: string, ...paths: (string | undefined)[]): string {
if (!isUri(pathString)) {
// Not a URI, or a URI with a single letter scheme.
return combineFilePaths(pathString, ...paths);
}
// Go through the paths to see if any are rooted. If so, treat as
// a file path. On linux this might be wrong if a path starts with '/'.
if (some(paths, (p) => !!p && getRootLength(p, /* checkUri */ false) !== 0)) {
return combineFilePaths(pathString, ...paths);
}
// Otherwise this is a URI
const nonEmptyPaths = paths.filter((p) => !!p) as string[];
const uri = URI.parse(pathString);
// Make sure we have a path to append to.
if (uri.path === '' || uri.path === undefined) {
nonEmptyPaths.unshift('/');
}
return Utils.joinPath(uri.with({ fragment: '', query: '' }), ...nonEmptyPaths).toString();
}
/**
* Determines whether a `parent` path contains a `child` path using the provide case sensitivity.
*/
@ -485,9 +381,7 @@ export function getBaseFileName(pathString: string, extensions?: string | readon
// return the trailing portion of the path starting after the last (non-terminal) directory
// separator but not including any trailing directory separator.
pathString = stripTrailingDirectorySeparator(pathString);
const name = isUri(pathString)
? Utils.basename(URI.parse(pathString).with({ fragment: '', query: '' }))
: pathString.slice(Math.max(getRootLength(pathString), pathString.lastIndexOf(path.sep) + 1));
const name = pathString.slice(Math.max(getRootLength(pathString), pathString.lastIndexOf(path.sep) + 1));
const extension =
extensions !== undefined && ignoreCase !== undefined
? getAnyExtensionFromPath(name, extensions, ignoreCase)
@ -561,7 +455,7 @@ export function stripTrailingDirectorySeparator(pathString: string) {
if (!hasTrailingDirectorySeparator(pathString)) {
return pathString;
}
return pathString.substr(0, pathString.length - 1);
return pathString.slice(0, pathString.length - 1);
}
export function getFileExtension(fileName: string, multiDotExtension = false) {
@ -571,7 +465,7 @@ export function getFileExtension(fileName: string, multiDotExtension = false) {
fileName = getFileName(fileName);
const firstDotIndex = fileName.indexOf('.');
return fileName.substr(firstDotIndex);
return fileName.slice(firstDotIndex);
}
export function getFileName(pathString: string) {
@ -592,102 +486,8 @@ export function stripFileExtension(fileName: string, multiDotExtension = false)
return fileName.substr(0, fileName.length - ext.length);
}
export function realCasePath(pathString: string, fileSystem: ReadOnlyFileSystem): string {
return isUri(pathString) ? pathString : fileSystem.realCasePath(pathString);
}
export function normalizePath(pathString: string): string {
if (!isUri(pathString)) {
return normalizeSlashes(path.normalize(pathString));
}
// Must be a URI, already normalized.
return pathString;
}
export function isDirectory(fs: ReadOnlyFileSystem, path: string): boolean {
return tryStat(fs, path)?.isDirectory() ?? false;
}
export function isFile(fs: ReadOnlyFileSystem, path: string, treatZipDirectoryAsFile = false): boolean {
const stats = tryStat(fs, path);
if (stats?.isFile()) {
return true;
}
if (!treatZipDirectoryAsFile) {
return false;
}
return stats?.isZipDirectory?.() ?? false;
}
export function tryStat(fs: ReadOnlyFileSystem, path: string): Stats | undefined {
try {
if (fs.existsSync(path)) {
return fs.statSync(path);
}
} catch (e: any) {
return undefined;
}
return undefined;
}
export function tryRealpath(fs: ReadOnlyFileSystem, path: string): string | undefined {
try {
return fs.realCasePath(path);
} catch (e: any) {
return undefined;
}
}
export function getFileSystemEntries(fs: ReadOnlyFileSystem, path: string): FileSystemEntries {
try {
return getFileSystemEntriesFromDirEntries(fs.readdirEntriesSync(path || '.'), fs, path);
} catch (e: any) {
return { files: [], directories: [] };
}
}
// Sorts the entires into files and directories, including any symbolic links.
export function getFileSystemEntriesFromDirEntries(
dirEntries: Dirent[],
fs: ReadOnlyFileSystem,
path: string
): FileSystemEntries {
const entries = dirEntries.sort((a, b) => {
if (a.name < b.name) {
return -1;
} else if (a.name > b.name) {
return 1;
} else {
return 0;
}
});
const files: string[] = [];
const directories: string[] = [];
for (const entry of entries) {
// This is necessary because on some file system node fails to exclude
// "." and "..". See https://github.com/nodejs/node/issues/4002
if (entry.name === '.' || entry.name === '..') {
continue;
}
if (entry.isFile()) {
files.push(entry.name);
} else if (entry.isDirectory()) {
directories.push(entry.name);
} else if (entry.isSymbolicLink()) {
const entryPath = combinePaths(path, entry.name);
const stat = tryStat(fs, entryPath);
if (stat?.isFile()) {
files.push(entry.name);
} else if (stat?.isDirectory()) {
directories.push(entry.name);
}
}
}
return { files, directories };
return normalizeSlashes(path.normalize(pathString));
}
// Transforms a relative file spec (one that potentially contains
@ -803,25 +603,6 @@ export function hasPythonExtension(path: string) {
return path.endsWith('.py') || path.endsWith('.pyi');
}
export function getFileSpec(sp: ServiceProvider, rootPath: string, fileSpec: string): FileSpec {
let regExPattern = getWildcardRegexPattern(rootPath, fileSpec);
const escapedSeparator = getRegexEscapedSeparator(getPathSeparator(rootPath));
regExPattern = `^(${regExPattern})($|${escapedSeparator})`;
const fs = sp.get(ServiceKeys.fs);
const tmp = sp.tryGet(ServiceKeys.tempFile);
const regExp = new RegExp(regExPattern, isFileSystemCaseSensitive(fs, tmp) ? undefined : 'i');
const wildcardRoot = getWildcardRoot(rootPath, fileSpec);
const hasDirectoryWildcard = isDirectoryWildcardPatternPresent(fileSpec);
return {
wildcardRoot,
regExp,
hasDirectoryWildcard,
};
}
export function getRegexEscapedSeparator(pathSep: string = path.sep) {
// we don't need to escape "/" in typescript regular expression
return pathSep === '/' ? '/' : '\\\\';
@ -908,168 +689,3 @@ function getPathComponentsRelativeTo(
}
return ['', ...relative, ...components];
}
const enum FileSystemEntryKind {
File,
Directory,
}
function fileSystemEntryExists(fs: ReadOnlyFileSystem, path: string, entryKind: FileSystemEntryKind): boolean {
try {
const stat = fs.statSync(path);
switch (entryKind) {
case FileSystemEntryKind.File:
return stat.isFile();
case FileSystemEntryKind.Directory:
return stat.isDirectory();
default:
return false;
}
} catch (e: any) {
return false;
}
}
export function convertUriToPath(fs: ReadOnlyFileSystem, uriString: string): string {
return realCasePath(fs.getMappedFilePath(extractPathFromUri(uriString)), fs);
}
export function extractPathFromUri(uriString: string) {
const uri = URI.parse(uriString);
// Only for file scheme do we actually modify anything. All other uri strings
// maintain the same value they started with.
if (uri.scheme === 'file' && !uri.fragment) {
// When schema is "file", we use fsPath so that we can handle things like UNC paths.
let convertedPath = normalizePath(uri.fsPath);
// If this is a DOS-style path with a drive letter, remove
// the leading slash.
if (convertedPath.match(/^\\[a-zA-Z]:\\/)) {
convertedPath = convertedPath.slice(1);
}
return convertedPath;
}
return uriString;
}
export function convertPathToUri(fs: ReadOnlyFileSystem, path: string): string {
return fs.getUri(fs.getOriginalFilePath(path));
}
export function setTestingMode(underTest: boolean) {
_underTest = underTest;
}
const isFileSystemCaseSensitiveMap = new WeakMap<FileSystem, boolean>();
export function isFileSystemCaseSensitive(fs: FileSystem, tmp?: TempFile) {
if (!tmp) {
return false;
}
if (!_underTest && _fsCaseSensitivity !== undefined) {
return _fsCaseSensitivity;
}
if (!isFileSystemCaseSensitiveMap.has(fs)) {
_fsCaseSensitivity = isFileSystemCaseSensitiveInternal(fs, tmp);
isFileSystemCaseSensitiveMap.set(fs, _fsCaseSensitivity);
}
return !!isFileSystemCaseSensitiveMap.get(fs);
}
export function isFileSystemCaseSensitiveInternal(fs: FileSystem, tmp: TempFile) {
let filePath: string | undefined = undefined;
try {
// Make unique file name.
let name: string;
let mangledFilePath: string;
do {
name = `${randomBytesHex(21)}-a`;
filePath = path.join(tmp.tmpdir(), name);
mangledFilePath = path.join(tmp.tmpdir(), name.toUpperCase());
} while (fs.existsSync(filePath) || fs.existsSync(mangledFilePath));
fs.writeFileSync(filePath, '', 'utf8');
// If file exists, then it is insensitive.
return !fs.existsSync(mangledFilePath);
} catch (e: any) {
return false;
} finally {
if (filePath) {
// remove temp file created
try {
fs.unlinkSync(filePath);
} catch (e: any) {
/* ignored */
}
}
}
}
export function getLibraryPathWithoutExtension(libraryFilePath: string) {
let filePathWithoutExtension = stripFileExtension(libraryFilePath);
// Strip off the '/__init__' if it's present.
if (filePathWithoutExtension.endsWith('__init__')) {
filePathWithoutExtension = filePathWithoutExtension.substr(0, filePathWithoutExtension.length - 9);
}
return filePathWithoutExtension;
}
export function getDirectoryChangeKind(
fs: ReadOnlyFileSystem,
oldDirectory: string,
newDirectory: string
): 'Same' | 'Renamed' | 'Moved' {
if (fs.realCasePath(oldDirectory) === fs.realCasePath(newDirectory)) {
return 'Same';
}
const relativePaths = getRelativePathComponentsFromDirectory(oldDirectory, newDirectory, (f) => fs.realCasePath(f));
// 3 means only last folder name has changed.
if (relativePaths.length === 3 && relativePaths[1] === '..' && relativePaths[2] !== '..') {
return 'Renamed';
}
return 'Moved';
}
export function deduplicateFolders(listOfFolders: string[][]): string[] {
const foldersToWatch = new Set<string>();
listOfFolders.forEach((folders) => {
folders.forEach((p) => {
if (foldersToWatch.has(p)) {
// Bail out on exact match.
return;
}
for (const existing of foldersToWatch) {
// ex) p: "/user/test" existing: "/user"
if (p.startsWith(existing)) {
// We already have the parent folder in the watch list
return;
}
// ex) p: "/user" folderToWatch: "/user/test"
if (existing.startsWith(p)) {
// We found better one to watch. replace.
foldersToWatch.delete(existing);
foldersToWatch.add(p);
return;
}
}
foldersToWatch.add(p);
});
});
return [...foldersToWatch];
}

View File

@ -8,7 +8,6 @@ import { FakeFS, NativePath, PortablePath, PosixFS, ppath, VirtualFS, ZipFS, Zip
import { getLibzipSync } from '@yarnpkg/libzip';
import * as fs from 'fs';
import * as tmp from 'tmp';
import { URI } from 'vscode-uri';
import { isMainThread } from 'worker_threads';
import { ConsoleInterface, NullConsole } from './console';
@ -21,7 +20,9 @@ import {
FileWatcherProvider,
nullFileWatcherProvider,
} from './fileWatcher';
import { combinePaths, getRootLength, isUri } from './pathUtils';
import { getRootLength } from './pathUtils';
import { Uri } from './uri/uri';
import { getRootUri, isFileSystemCaseSensitive } from './uri/uriUtils';
// Automatically remove files created by tmp at process exit.
tmp.setGracefulCleanup();
@ -208,9 +209,19 @@ class YarnFS extends PosixFS {
const yarnFS = new YarnFS();
class RealFileSystem implements FileSystem {
constructor(private _fileWatcherProvider: FileWatcherProvider, private _console: ConsoleInterface) {}
private _isCaseSensitive = true;
constructor(private _fileWatcherProvider: FileWatcherProvider, private _console: ConsoleInterface) {
this._isCaseSensitive = isFileSystemCaseSensitive(this, new RealTempFile(/* isCaseSensitive */ true));
}
existsSync(path: string) {
get isCaseSensitive(): boolean {
return this._isCaseSensitive;
}
existsSync(uri: Uri) {
if (uri.isEmpty()) {
return false;
}
const path = uri.getFilePath();
try {
// Catch zip open errors. existsSync is assumed to never throw by callers.
return yarnFS.existsSync(path);
@ -219,11 +230,13 @@ class RealFileSystem implements FileSystem {
}
}
mkdirSync(path: string, options?: MkDirOptions) {
mkdirSync(uri: Uri, options?: MkDirOptions) {
const path = uri.getFilePath();
yarnFS.mkdirSync(path, options);
}
chdir(path: string) {
chdir(uri: Uri) {
const path = uri.getFilePath();
// If this file system happens to be running in a worker thread,
// then we can't call 'chdir'.
if (isMainThread) {
@ -231,11 +244,13 @@ class RealFileSystem implements FileSystem {
}
}
readdirSync(path: string): string[] {
readdirSync(uri: Uri): string[] {
const path = uri.getFilePath();
return yarnFS.readdirSync(path);
}
readdirEntriesSync(path: string): fs.Dirent[] {
readdirEntriesSync(uri: Uri): fs.Dirent[] {
const path = uri.getFilePath();
return yarnFS.readdirSync(path, { withFileTypes: true }).map((entry): fs.Dirent => {
// Treat zip/egg files as directories.
// See: https://github.com/yarnpkg/berry/blob/master/packages/vscode-zipfs/sources/ZipFSProvider.ts
@ -257,21 +272,24 @@ class RealFileSystem implements FileSystem {
});
}
readFileSync(path: string, encoding?: null): Buffer;
readFileSync(path: string, encoding: BufferEncoding): string;
readFileSync(path: string, encoding?: BufferEncoding | null): Buffer | string;
readFileSync(path: string, encoding: BufferEncoding | null = null) {
readFileSync(uri: Uri, encoding?: null): Buffer;
readFileSync(uri: Uri, encoding: BufferEncoding): string;
readFileSync(uri: Uri, encoding?: BufferEncoding | null): Buffer | string;
readFileSync(uri: Uri, encoding: BufferEncoding | null = null) {
const path = uri.getFilePath();
if (encoding === 'utf8' || encoding === 'utf-8') {
return yarnFS.readFileSync(path, 'utf8');
}
return yarnFS.readFileSync(path);
}
writeFileSync(path: string, data: string | Buffer, encoding: BufferEncoding | null) {
writeFileSync(uri: Uri, data: string | Buffer, encoding: BufferEncoding | null) {
const path = uri.getFilePath();
yarnFS.writeFileSync(path, data, encoding || undefined);
}
statSync(path: string) {
statSync(uri: Uri) {
const path = uri.getFilePath();
const stat = yarnFS.statSync(path);
// Treat zip/egg files as directories.
// See: https://github.com/yarnpkg/berry/blob/master/packages/vscode-zipfs/sources/ZipFSProvider.ts
@ -286,53 +304,62 @@ class RealFileSystem implements FileSystem {
return stat;
}
rmdirSync(path: string): void {
rmdirSync(uri: Uri): void {
const path = uri.getFilePath();
yarnFS.rmdirSync(path);
}
unlinkSync(path: string) {
unlinkSync(uri: Uri) {
const path = uri.getFilePath();
yarnFS.unlinkSync(path);
}
realpathSync(path: string) {
realpathSync(uri: Uri) {
try {
return yarnFS.realpathSync(path);
const path = uri.getFilePath();
return Uri.file(yarnFS.realpathSync(path), this._isCaseSensitive);
} catch (e: any) {
return path;
return uri;
}
}
getModulePath(): string {
getModulePath(): Uri {
// The entry point to the tool should have set the __rootDirectory
// global variable to point to the directory that contains the
// typeshed-fallback directory.
return (global as any).__rootDirectory;
return getRootUri(this._isCaseSensitive) || Uri.empty();
}
createFileSystemWatcher(paths: string[], listener: FileWatcherEventHandler): FileWatcher {
createFileSystemWatcher(paths: Uri[], listener: FileWatcherEventHandler): FileWatcher {
return this._fileWatcherProvider.createFileWatcher(
paths.map((p) => this.realCasePath(p)),
paths.map((p) => p.getFilePath()),
listener
);
}
createReadStream(path: string): fs.ReadStream {
createReadStream(uri: Uri): fs.ReadStream {
const path = uri.getFilePath();
return yarnFS.createReadStream(path);
}
createWriteStream(path: string): fs.WriteStream {
createWriteStream(uri: Uri): fs.WriteStream {
const path = uri.getFilePath();
return yarnFS.createWriteStream(path);
}
copyFileSync(src: string, dst: string): void {
yarnFS.copyFileSync(src, dst);
copyFileSync(src: Uri, dst: Uri): void {
const srcPath = src.getFilePath();
const destPath = dst.getFilePath();
yarnFS.copyFileSync(srcPath, destPath);
}
readFile(path: string): Promise<Buffer> {
readFile(uri: Uri): Promise<Buffer> {
const path = uri.getFilePath();
return yarnFS.readFilePromise(path);
}
async readFileText(path: string, encoding: BufferEncoding): Promise<string> {
async readFileText(uri: Uri, encoding: BufferEncoding): Promise<string> {
const path = uri.getFilePath();
if (encoding === 'utf8' || encoding === 'utf-8') {
return yarnFS.readFilePromise(path, 'utf8');
}
@ -340,17 +367,18 @@ class RealFileSystem implements FileSystem {
return buffer.toString(encoding);
}
realCasePath(path: string): string {
realCasePath(uri: Uri): Uri {
try {
// If it doesn't exist in the real FS, then just use this path.
if (!this.existsSync(path)) {
return this._getNormalizedPath(path);
if (!this.existsSync(uri)) {
return uri;
}
// If it does exist, skip this for symlinks.
const path = uri.getFilePath();
const stat = fs.lstatSync(path);
if (stat.isSymbolicLink()) {
return this._getNormalizedPath(path);
return uri;
}
// realpathSync.native will return casing as in OS rather than
@ -359,53 +387,34 @@ class RealFileSystem implements FileSystem {
// On UNC mapped drives we want to keep the original drive letter.
if (getRootLength(realCase) !== getRootLength(path)) {
return path;
return uri;
}
return realCase;
return Uri.file(realCase, this._isCaseSensitive);
} catch (e: any) {
// Return as it is, if anything failed.
this._console.log(`Failed to get real file system casing for ${path}: ${e}`);
this._console.log(`Failed to get real file system casing for ${uri}: ${e}`);
return path;
return uri;
}
}
isMappedFilePath(filepath: string): boolean {
isMappedUri(uri: Uri): boolean {
return false;
}
getOriginalFilePath(mappedFilePath: string) {
return mappedFilePath;
getOriginalUri(mappedUri: Uri) {
return mappedUri;
}
getMappedFilePath(originalFilepath: string) {
return originalFilepath;
getMappedUri(originalUri: Uri) {
return originalUri;
}
getUri(path: string): string {
// If this is not a file path, just return the original path.
if (isUri(path)) {
return path;
}
return URI.file(path).toString();
}
isInZip(path: string): boolean {
isInZip(uri: Uri): boolean {
const path = uri.getFilePath();
return /[^\\/]\.(?:egg|zip|jar)[\\/]/.test(path) && yarnFS.isZip(path);
}
private _getNormalizedPath(path: string) {
const driveLength = getRootLength(path);
if (driveLength === 0) {
return path;
}
// `vscode` sometimes uses different casing for drive letter.
// Make sure we normalize at least drive letter.
return combinePaths(fs.realpathSync.native(path.substring(0, driveLength)), path.substring(driveLength));
}
}
interface WorkspaceFileWatcher extends FileWatcher {
@ -436,15 +445,15 @@ export class WorkspaceFileWatcherProvider implements FileWatcherProvider, FileWa
return fileWatcher;
}
onFileChange(eventType: FileWatcherEventType, filePath: string): void {
onFileChange(eventType: FileWatcherEventType, fileUri: Uri): void {
// Since file watcher is a server wide service, we don't know which watcher is
// for which workspace (for multi workspace case), also, we don't know which watcher
// is for source or library. so we need to solely rely on paths that can cause us
// to raise events both for source and library if .venv is inside of workspace root
// for a file change. It is event handler's job to filter those out.
this._fileWatchers.forEach((watcher) => {
if (watcher.workspacePaths.some((dirPath) => filePath.startsWith(dirPath))) {
watcher.eventHandler(eventType, filePath);
if (watcher.workspacePaths.some((dirPath) => fileUri.pathStartsWith(dirPath))) {
watcher.eventHandler(eventType, fileUri);
}
});
}
@ -453,17 +462,15 @@ export class WorkspaceFileWatcherProvider implements FileWatcherProvider, FileWa
export class RealTempFile implements TempFile {
private _tmpdir?: tmp.DirResult;
tmpdir() {
if (!this._tmpdir) {
this._tmpdir = tmp.dirSync({ prefix: 'pyright' });
}
constructor(private readonly _isCaseSensitive: boolean) {}
return this._tmpdir.name;
tmpdir(): Uri {
return Uri.file(this._getTmpDir().name, this._isCaseSensitive);
}
tmpfile(options?: TmpfileOptions): string {
const f = tmp.fileSync({ dir: this.tmpdir(), discardDescriptor: true, ...options });
return f.name;
tmpfile(options?: TmpfileOptions): Uri {
const f = tmp.fileSync({ dir: this._getTmpDir().name, discardDescriptor: true, ...options });
return Uri.file(f.name, this._isCaseSensitive);
}
dispose(): void {
@ -474,4 +481,12 @@ export class RealTempFile implements TempFile {
// ignore
}
}
private _getTmpDir(): tmp.DirResult {
if (!this._tmpdir) {
this._tmpdir = tmp.dirSync({ prefix: 'pyright' });
}
return this._tmpdir;
}
}

View File

@ -11,20 +11,22 @@ import { IPythonMode, SourceFile, SourceFileEditMode } from '../analyzer/sourceF
import { SupportPartialStubs } from '../pyrightFileSystem';
import { ConsoleInterface } from './console';
import {
StatusMutationListener,
DebugInfoInspector,
ServiceProvider as ReadOnlyServiceProvider,
StatusMutationListener,
SymbolDefinitionProvider,
SymbolUsageProviderFactory,
DebugInfoInspector,
} from './extensibility';
import { FileSystem, TempFile } from './fileSystem';
import { LogTracker } from './logTracker';
import { GroupServiceKey, ServiceKey, ServiceProvider } from './serviceProvider';
import { Uri } from './uri/uri';
declare module './serviceProvider' {
interface ServiceProvider {
fs(): FileSystem;
console(): ConsoleInterface;
tmp(): TempFile | undefined;
sourceFileFactory(): ISourceFileFactory;
partialStubs(): SupportPartialStubs;
}
@ -79,6 +81,9 @@ ServiceProvider.prototype.console = function () {
ServiceProvider.prototype.partialStubs = function () {
return this.get(ServiceKeys.partialStubs);
};
ServiceProvider.prototype.tmp = function () {
return this.tryGet(ServiceKeys.tempFile);
};
ServiceProvider.prototype.sourceFileFactory = function () {
const result = this.tryGet(ServiceKeys.sourceFileFactory);
return result || DefaultSourceFileFactory;
@ -87,26 +92,24 @@ ServiceProvider.prototype.sourceFileFactory = function () {
const DefaultSourceFileFactory: ISourceFileFactory = {
createSourceFile(
serviceProvider: ReadOnlyServiceProvider,
filePath: string,
fileUri: Uri,
moduleName: string,
isThirdPartyImport: boolean,
isThirdPartyPyTypedPresent: boolean,
editMode: SourceFileEditMode,
console?: ConsoleInterface,
logTracker?: LogTracker,
realFilePath?: string,
ipythonMode?: IPythonMode
) {
return new SourceFile(
serviceProvider,
filePath,
fileUri,
moduleName,
isThirdPartyImport,
isThirdPartyPyTypedPresent,
editMode,
console,
logTracker,
realFilePath,
ipythonMode
);
},

View File

@ -32,11 +32,11 @@ import {
} from '../parser/parseNodes';
import { ParseResults } from '../parser/parser';
import { appendArray, getOrAdd, removeArrayElements } from './collectionUtils';
import { isString } from './core';
import * as debug from './debug';
import { FileEditAction } from './editAction';
import { convertOffsetToPosition, convertTextRangeToRange } from './positionUtils';
import { doesRangeContain, doRangesIntersect, extendRange, Range, TextRange } from './textRange';
import { Uri } from './uri/uri';
export class TextEditTracker {
private readonly _nodesRemoved: Map<ParseNode, ParseResults> = new Map<ParseNode, ParseResults>();
@ -49,11 +49,11 @@ export class TextEditTracker {
}
addEdits(...edits: FileEditAction[]) {
edits.forEach((e) => this.addEdit(e.filePath, e.range, e.replacementText));
edits.forEach((e) => this.addEdit(e.fileUri, e.range, e.replacementText));
}
addEdit(filePath: string, range: Range, replacementText: string) {
const edits = getOrAdd(this._results, filePath, () => []);
addEdit(fileUri: Uri, range: Range, replacementText: string) {
const edits = getOrAdd(this._results, fileUri.key, () => []);
// If there is any overlapping edit, see whether we can merge edits.
// We can merge edits, if one of them is 'deletion' or 2 edits has the same
@ -70,11 +70,11 @@ export class TextEditTracker {
);
}
edits.push({ filePath, range, replacementText });
edits.push({ fileUri: fileUri, range, replacementText });
}
addEditWithTextRange(parseResults: ParseResults, range: TextRange, replacementText: string) {
const filePath = getFileInfo(parseResults.parseTree).filePath;
const filePath = getFileInfo(parseResults.parseTree).fileUri;
const existing = parseResults.text.substr(range.start, range.length);
if (existing === replacementText) {
@ -93,7 +93,7 @@ export class TextEditTracker {
? (importToDelete.parent as ImportNode).list
: (importToDelete.parent as ImportFromNode).imports;
const filePath = getFileInfo(parseResults.parseTree).filePath;
const filePath = getFileInfo(parseResults.parseTree).fileUri;
const ranges = getTextRangeForImportNameDeletion(
parseResults,
imports,
@ -183,7 +183,7 @@ export class TextEditTracker {
importGroup: ImportGroup,
importNameInfo?: ImportNameInfo[]
) {
const filePath = getFileInfo(parseResults.parseTree).filePath;
const fileUri = getFileInfo(parseResults.parseTree).fileUri;
this.addEdits(
...getTextEditsForAutoImportInsertion(
@ -193,7 +193,7 @@ export class TextEditTracker {
importGroup,
parseResults,
convertOffsetToPosition(parseResults.parseTree.length, parseResults.tokenizerOutput.lines)
).map((e) => ({ filePath, range: e.range, replacementText: e.replacementText }))
).map((e) => ({ fileUri, range: e.range, replacementText: e.replacementText }))
);
}
@ -220,7 +220,7 @@ export class TextEditTracker {
return false;
}
const filePath = getFileInfo(parseResults.parseTree).filePath;
const fileUri = getFileInfo(parseResults.parseTree).fileUri;
const edits = getTextEditsForAutoImportSymbolAddition(importNameInfo, imported, parseResults);
if (imported.node !== updateOptions.currentFromImport) {
@ -228,7 +228,7 @@ export class TextEditTracker {
// node we are working on.
// ex) from xxx import yyy <= we are working on here.
// from xxx import zzz <= but we found this.
this.addEdits(...edits.map((e) => ({ filePath, range: e.range, replacementText: e.replacementText })));
this.addEdits(...edits.map((e) => ({ fileUri, range: e.range, replacementText: e.replacementText })));
return true;
}
@ -247,9 +247,9 @@ export class TextEditTracker {
return false;
}
const deletions = this._getDeletionsForSpan(filePath, edits[0].range);
const deletions = this._getDeletionsForSpan(fileUri, edits[0].range);
if (deletions.length === 0) {
this.addEdit(filePath, edits[0].range, edits[0].replacementText);
this.addEdit(fileUri, edits[0].range, edits[0].replacementText);
return true;
}
@ -265,13 +265,13 @@ export class TextEditTracker {
return false;
}
this._removeEdits(filePath, deletions);
this._removeEdits(fileUri, deletions);
if (importName.alias) {
this._nodesRemoved.delete(importName.alias);
}
this.addEdit(
filePath,
fileUri,
convertTextRangeToRange(importName.name, parseResults.tokenizerOutput.lines),
newLastModuleName
);
@ -279,17 +279,17 @@ export class TextEditTracker {
return true;
}
private _getDeletionsForSpan(filePathOrEdit: string | FileEditAction[], range: Range) {
const edits = this._getOverlappingForSpan(filePathOrEdit, range);
private _getDeletionsForSpan(fileUriOrEdit: Uri | FileEditAction[], range: Range) {
const edits = this._getOverlappingForSpan(fileUriOrEdit, range);
return edits.filter((e) => e.replacementText === '');
}
private _removeEdits(filePathOrEdit: string | FileEditAction[], edits: FileEditAction[]) {
if (isString(filePathOrEdit)) {
filePathOrEdit = this._results.get(filePathOrEdit) ?? [];
private _removeEdits(fileUriOrEdit: Uri | FileEditAction[], edits: FileEditAction[]) {
if (Uri.isUri(fileUriOrEdit)) {
fileUriOrEdit = this._results.get(fileUriOrEdit.key) ?? [];
}
removeArrayElements(filePathOrEdit, (f) => edits.some((e) => FileEditAction.areEqual(f, e)));
removeArrayElements(fileUriOrEdit, (f) => edits.some((e) => FileEditAction.areEqual(f, e)));
}
private _getEditsToMerge(edits: FileEditAction[], range: Range, replacementText: string) {
@ -319,12 +319,12 @@ export class TextEditTracker {
);
}
private _getOverlappingForSpan(filePathOrEdit: string | FileEditAction[], range: Range) {
if (isString(filePathOrEdit)) {
filePathOrEdit = this._results.get(filePathOrEdit) ?? [];
private _getOverlappingForSpan(fileUriOrEdit: Uri | FileEditAction[], range: Range) {
if (Uri.isUri(fileUriOrEdit)) {
fileUriOrEdit = this._results.get(fileUriOrEdit.key) ?? [];
}
return filePathOrEdit.filter((e) => doRangesIntersect(e.range, range));
return fileUriOrEdit.filter((e) => doRangesIntersect(e.range, range));
}
private _processNodeRemoved(token: CancellationToken) {
@ -343,7 +343,7 @@ export class TextEditTracker {
this._pendingNodeToRemove.pop();
const info = getFileInfo(peekNodeToRemove.parseResults.parseTree);
this.addEdit(info.filePath, convertTextRangeToRange(peekNodeToRemove.node, info.lines), '');
this.addEdit(info.fileUri, convertTextRangeToRange(peekNodeToRemove.node, info.lines), '');
}
}
}
@ -370,11 +370,7 @@ export class TextEditTracker {
);
if (nameNodes.length === nodesRemoved.length) {
this.addEdit(
info.filePath,
ParseTreeUtils.getFullStatementRange(importNode, nodeToRemove.parseResults),
''
);
this.addEdit(info.fileUri, ParseTreeUtils.getFullStatementRange(importNode, nodeToRemove.parseResults), '');
// Remove nodes that are handled from queue.
this._removeNodesHandled(nodesRemoved);
@ -397,7 +393,7 @@ export class TextEditTracker {
}
const editSpans = getTextRangeForImportNameDeletion(nodeToRemove.parseResults, nameNodes, ...indices);
editSpans.forEach((e) => this.addEdit(info.filePath, convertTextRangeToRange(e, info.lines), ''));
editSpans.forEach((e) => this.addEdit(info.fileUri, convertTextRangeToRange(e, info.lines), ''));
this._removeNodesHandled(nodesRemoved);
return true;

View File

@ -7,6 +7,8 @@
* Specifies the range of text within a larger string.
*/
import { Uri } from './uri/uri';
export interface TextRange {
readonly start: number;
readonly length: number;
@ -131,7 +133,7 @@ export namespace Range {
// Represents a range within a particular document.
export interface DocumentRange {
path: string;
uri: Uri;
range: Range;
}

View File

@ -0,0 +1,269 @@
/*
* baseUri.ts
* Copyright (c) Microsoft Corporation.
* Licensed under the MIT license.
*
* Base URI class for storing and manipulating URIs.
*/
import { some } from '../collectionUtils';
import { getShortenedFileName, normalizeSlashes } from '../pathUtils';
import { Uri } from './uri';
export abstract class BaseUri implements Uri {
protected constructor(private readonly _key: string) {}
// Unique key for storing in maps.
get key() {
return this._key;
}
// Returns the scheme of the URI.
abstract get scheme(): string;
// Returns whether the underlying file system is case sensitive or not.
abstract get isCaseSensitive(): boolean;
// Returns the last segment of the URI, similar to the UNIX basename command.
abstract get fileName(): string;
// Returns just the fileName without any extensions
get fileNameWithoutExtension(): string {
const fileName = this.fileName;
const index = fileName.lastIndexOf('.');
if (index > 0) {
return fileName.slice(0, index);
} else {
return fileName;
}
}
// Returns the extension of the URI, similar to the UNIX extname command.
abstract get lastExtension(): string;
// Returns a URI where the path just contains the root folder.
abstract get root(): Uri;
// Returns a URI where the path contains the path with .py appended.
get packageUri(): Uri {
// This is assuming that the current path is a file already.
return this.addExtension('.py');
}
// Returns a URI where the path contains the path with .pyi appended.
get packageStubUri(): Uri {
// This is assuming that the current path is a file already.
return this.addExtension('.pyi');
}
// Returns a URI where the path has __init__.py appended.
get initPyUri(): Uri {
// This is assuming that the current path is a directory already.
return this.combinePaths('__init__.py');
}
// Returns a URI where the path has __init__.pyi appended.
get initPyiUri(): Uri {
// This is assuming that the current path is a directory already.
return this.combinePaths('__init__.pyi');
}
// Returns a URI where the path has py.typed appended.
get pytypedUri(): Uri {
// This is assuming that the current path is a directory already.
return this.combinePaths('py.typed');
}
isEmpty(): boolean {
return false;
}
abstract toString(): string;
abstract toUserVisibleString(): string;
abstract matchesRegex(regex: RegExp): boolean;
replaceExtension(ext: string): Uri {
const dir = this.getDirectory();
const base = this.fileName;
const newBase = base.slice(0, base.length - this.lastExtension.length) + ext;
return dir.combinePaths(newBase);
}
addExtension(ext: string): Uri {
return this.addPath(ext);
}
hasExtension(ext: string): boolean {
return this.isCaseSensitive
? this.lastExtension === ext
: this.lastExtension.toLowerCase() === ext.toLowerCase();
}
abstract addPath(extra: string): Uri;
// Returns a URI where the path is the directory name of the original URI, similar to the UNIX dirname command.
abstract getDirectory(): Uri;
getRootPathLength(): number {
return this.getRootPath().length;
}
// Determines whether a path consists only of a path root.
abstract isRoot(): boolean;
// Determines whether a Uri is a child of some parent Uri.
abstract isChild(parent: Uri, ignoreCase?: boolean): boolean;
abstract isLocal(): boolean;
isUntitled(): boolean {
return this.scheme === 'untitled';
}
equals(other: Uri | undefined): boolean {
return this.key === other?.key;
}
abstract startsWith(other: Uri | undefined, ignoreCase?: boolean): boolean;
pathStartsWith(name: string): boolean {
// ignore path separators.
name = normalizeSlashes(name);
return this.getComparablePath().startsWith(name);
}
pathEndsWith(name: string): boolean {
// ignore path separators.
name = normalizeSlashes(name);
return this.getComparablePath().endsWith(name);
}
pathIncludes(include: string): boolean {
// ignore path separators.
include = normalizeSlashes(include);
return this.getComparablePath().includes(include);
}
// How long the path for this Uri is.
abstract getPathLength(): number;
// Combines paths to create a new Uri. Any '..' or '.' path components will be normalized.
abstract combinePaths(...paths: string[]): Uri;
getRelativePath(child: Uri): string | undefined {
if (this.scheme !== child.scheme) {
return undefined;
}
// Unlike getRelativePathComponents, this function should not return relative path
// markers for non children.
if (child.isChild(this)) {
const relativeToComponents = this.getRelativePathComponents(child);
if (relativeToComponents.length > 0) {
return ['.', ...relativeToComponents].join('/');
}
}
return undefined;
}
getPathComponents(): readonly string[] {
// Make sure to freeze the result so that it can't be modified.
return Object.freeze(this.getPathComponentsImpl());
}
abstract getPath(): string;
abstract getFilePath(): string;
getRelativePathComponents(to: Uri): readonly string[] {
const fromComponents = this.getPathComponents();
const toComponents = to.getPathComponents();
let start: number;
for (start = 0; start < fromComponents.length && start < toComponents.length; start++) {
const fromComponent = fromComponents[start];
const toComponent = toComponents[start];
if (fromComponent !== toComponent) {
break;
}
}
if (start === 0) {
return toComponents;
}
const components = toComponents.slice(start);
const relative: string[] = [];
for (; start < fromComponents.length; start++) {
relative.push('..');
}
return [...relative, ...components];
}
getShortenedFileName(maxDirLength: number = 15): string {
return getShortenedFileName(this.getPath(), maxDirLength);
}
stripExtension(): Uri {
const base = this.fileName;
const index = base.lastIndexOf('.');
if (index > 0) {
const stripped = base.slice(0, index);
return this.getDirectory().combinePaths(stripped);
} else {
return this;
}
}
stripAllExtensions(): Uri {
const base = this.fileName;
const stripped = base.split('.')[0];
if (stripped === base) {
return this;
} else {
return this.getDirectory().combinePaths(stripped);
}
}
protected abstract getRootPath(): string;
protected normalizeSlashes(path: string): string {
return path.replace(/\\/g, '/');
}
protected reducePathComponents(components: string[]): string[] {
if (!some(components)) {
return [];
}
// Reduce the path components by eliminating
// any '.' or '..'. We start at 1 because the first component is
// always the root.
const reduced = [components[0]];
for (let i = 1; i < components.length; i++) {
const component = components[i];
if (!component || component === '.') {
continue;
}
if (component === '..') {
if (reduced.length > 1) {
if (reduced[reduced.length - 1] !== '..') {
reduced.pop();
continue;
}
} else if (reduced[0]) {
continue;
}
}
reduced.push(component);
}
return reduced;
}
protected abstract getComparablePath(): string;
protected abstract getPathComponentsImpl(): string[];
}

View File

@ -0,0 +1,123 @@
/*
* emptyUri.ts
* Copyright (c) Microsoft Corporation.
* Licensed under the MIT license.
*
* URI class that represents an empty URI.
*/
import * as debug from '../debug';
import { BaseUri } from './baseUri';
import { Uri } from './uri';
const EmptyKey = '<empty>';
export class EmptyUri extends BaseUri {
private static _instance = new EmptyUri();
private constructor() {
super(EmptyKey);
}
static get instance() {
return EmptyUri._instance;
}
override get scheme(): string {
return '';
}
override get fileName(): string {
return '';
}
override get lastExtension(): string {
return '';
}
override get root(): Uri {
return this;
}
get isCaseSensitive(): boolean {
return true;
}
override isEmpty(): boolean {
return true;
}
override isLocal(): boolean {
return false;
}
override getPath(): string {
return '';
}
override getFilePath(): string {
debug.fail(`EmptyUri.getFilePath() should not be called.`);
}
override toString(): string {
return '';
}
override toUserVisibleString(): string {
return '';
}
override matchesRegex(regex: RegExp): boolean {
return false;
}
override replaceExtension(ext: string): Uri {
return this;
}
override addPath(extra: string): Uri {
return this;
}
override getDirectory(): Uri {
return this;
}
override isRoot(): boolean {
return true;
}
override isChild(parent: Uri): boolean {
return false;
}
override startsWith(other: Uri | undefined): boolean {
return false;
}
override getPathLength(): number {
return 0;
}
override combinePaths(...paths: string[]): Uri {
return this;
}
override getShortenedFileName(maxDirLength: number): string {
return '';
}
override stripExtension(): Uri {
return this;
}
protected override getPathComponentsImpl(): string[] {
return [];
}
protected override getRootPath(): string {
return '';
}
protected override getComparablePath(): string {
return '';
}
}

View File

@ -0,0 +1,203 @@
/*
* fileUri.ts
* Copyright (c) Microsoft Corporation.
* Licensed under the MIT license.
*
* URI class that represents a file path. These URIs are always 'file' schemed.
*/
import { URI } from 'vscode-uri';
import {
ensureTrailingDirectorySeparator,
getDirectoryPath,
getFileExtension,
getFileName,
getPathComponents,
getRootLength,
hasTrailingDirectorySeparator,
isDiskPathRoot,
normalizeSlashes,
resolvePaths,
} from '../pathUtils';
import { BaseUri } from './baseUri';
import { cacheMethodWithNoArgs, cacheProperty, cacheStaticFunc } from './memoization';
import { Uri } from './uri';
export class FileUri extends BaseUri {
private _formattedString: string | undefined;
private constructor(
key: string,
private readonly _filePath: string,
private readonly _query: string,
private readonly _fragment: string,
private readonly _originalString: string | undefined,
private readonly _isCaseSensitive: boolean
) {
super(_isCaseSensitive ? key : key.toLowerCase());
}
override get scheme(): string {
return 'file';
}
@cacheProperty()
override get fileName(): string {
return getFileName(this._filePath);
}
@cacheProperty()
override get lastExtension(): string {
return getFileExtension(this._filePath);
}
@cacheProperty()
override get root(): Uri {
const rootPath = this.getRootPath();
if (rootPath !== this._filePath) {
return FileUri.createFileUri(rootPath, '', '', undefined, this._isCaseSensitive);
}
return this;
}
get isCaseSensitive(): boolean {
return this._isCaseSensitive;
}
@cacheStaticFunc()
static createFileUri(
filePath: string,
query: string,
fragment: string,
originalString: string | undefined,
isCaseSensitive: boolean
): FileUri {
const key = FileUri._createKey(filePath, query, fragment);
return new FileUri(key, filePath, query, fragment, originalString, isCaseSensitive);
}
static isFileUri(uri: Uri): uri is FileUri {
return uri.scheme === 'file' && (uri as any)._filePath !== undefined;
}
override matchesRegex(regex: RegExp): boolean {
// Compare the regex to our path but normalize it for comparison.
// The regex assumes it's comparing itself to a URI path.
const path = this.normalizeSlashes(this._filePath);
return regex.test(path);
}
override toString(): string {
if (!this._formattedString) {
this._formattedString =
this._originalString ||
URI.file(this._filePath).with({ query: this._query, fragment: this._fragment }).toString();
}
return this._formattedString;
}
override toUserVisibleString(): string {
return this._filePath;
}
override addPath(extra: string): Uri {
return FileUri.createFileUri(this._filePath + extra, '', '', undefined, this._isCaseSensitive);
}
override isRoot(): boolean {
return isDiskPathRoot(this._filePath);
}
override isChild(parent: Uri): boolean {
if (!FileUri.isFileUri(parent)) {
return false;
}
return parent._filePath.length < this._filePath.length && this.startsWith(parent);
}
override isLocal(): boolean {
return true;
}
override startsWith(other: Uri | undefined): boolean {
if (!other || !FileUri.isFileUri(other)) {
return false;
}
if (other.isEmpty() !== this.isEmpty()) {
return false;
}
if (this._filePath.length >= other._filePath.length) {
// Make sure the other ends with a / when comparing longer paths, otherwise we might
// say that /a/food is a child of /a/foo.
const otherPath =
this._filePath.length > other._filePath.length && !hasTrailingDirectorySeparator(other._filePath)
? ensureTrailingDirectorySeparator(other._filePath)
: other._filePath;
if (!this.isCaseSensitive) {
return this._filePath.toLowerCase().startsWith(otherPath.toLowerCase());
}
return this._filePath.startsWith(otherPath);
}
return false;
}
override getPathLength(): number {
return this._filePath.length;
}
override getPath(): string {
return this.normalizeSlashes(this._filePath);
}
override getFilePath(): string {
return this._filePath;
}
override combinePaths(...paths: string[]): Uri {
// Resolve and combine paths, never want URIs with '..' in the middle.
let combined = resolvePaths(this._filePath, ...paths);
// Make sure to remove any trailing directory chars.
if (hasTrailingDirectorySeparator(combined) && combined.length > 1) {
combined = combined.slice(0, combined.length - 1);
}
if (combined !== this._filePath) {
return FileUri.createFileUri(combined, '', '', undefined, this._isCaseSensitive);
}
return this;
}
@cacheMethodWithNoArgs()
override getDirectory(): Uri {
const filePath = this._filePath;
let dir = getDirectoryPath(filePath);
if (hasTrailingDirectorySeparator(dir) && dir.length > 1) {
dir = dir.slice(0, -1);
}
if (dir !== filePath) {
return FileUri.createFileUri(dir, '', '', undefined, this._isCaseSensitive);
} else {
return this;
}
}
protected override getPathComponentsImpl(): string[] {
const components = getPathComponents(this._filePath);
// Remove the first one if it's empty. The new algorithm doesn't
// expect this to be there.
if (components.length > 0 && components[0] === '') {
components.shift();
}
return components.map((component) => this.normalizeSlashes(component));
}
protected override getRootPath(): string {
return this._filePath.slice(0, getRootLength(this._filePath));
}
protected override getComparablePath(): string {
return normalizeSlashes(this._filePath);
}
private static _createKey(filePath: string, query: string, fragment: string) {
return `${filePath}${query ? '?' + query : ''}${fragment ? '#' + fragment : ''}`;
}
}

View File

@ -0,0 +1,70 @@
/*
* memoization.ts
* Copyright (c) Microsoft Corporation.
* Licensed under the MIT license.
*
* Decorators used to memoize the result of a function call.
*/
// Cache for static method results.
const staticCache = new Map<string, any>();
// Caches the results of a getter property.
export function cacheProperty() {
return function (target: any, functionName: string, descriptor: PropertyDescriptor) {
const originalMethod = descriptor.get;
descriptor.get = function (this: any, ...args: any) {
// Call the function once to get the result.
const result = originalMethod!.apply(this, args);
// Then we replace the original function with one that just returns the result.
Object.defineProperty(this, functionName, {
get() {
return result;
},
});
return result;
};
return descriptor;
};
}
// Caches the results of method that takes no args.
// This situation can be optimized because the parameters are always the same.
export function cacheMethodWithNoArgs() {
return function (target: any, functionName: string, descriptor: PropertyDescriptor) {
const originalMethod = descriptor.value;
descriptor.value = function (this: any, ...args: any) {
// Call the function once to get the result.
const result = originalMethod.apply(this, args);
// Then we replace the original function with one that just returns the result.
this[functionName] = () => {
// Note that this poses a risk. The result is passed by reference, so if the caller
// modifies the result, it will modify the cached result.
return result;
};
return result;
};
return descriptor;
};
}
// Create a decorator to cache the results of a static method.
export function cacheStaticFunc() {
return function cacheStaticFunc_Fast(target: any, functionName: string, descriptor: PropertyDescriptor) {
const originalMethod = descriptor.value;
descriptor.value = function (...args: any) {
const key = `${functionName}+${args.map((a: any) => a?.toString()).join(',')}`;
let cachedResult: any;
if (!staticCache.has(key)) {
cachedResult = originalMethod.apply(this, args);
staticCache.set(key, cachedResult);
} else {
cachedResult = staticCache.get(key);
}
return cachedResult;
};
return descriptor;
};
}

View File

@ -0,0 +1,215 @@
/*
* uri.ts
* Copyright (c) Microsoft Corporation.
* Licensed under the MIT license.
*
* URI namespace for storing and manipulating URIs.
*/
import { URI, Utils } from 'vscode-uri';
import { combinePaths, isRootedDiskPath } from '../pathUtils';
import { EmptyUri } from './emptyUri';
import { FileUri } from './fileUri';
import { WebUri } from './webUri';
export interface Uri {
// Unique key for storing in maps.
readonly key: string;
// Returns the scheme of the URI.
readonly scheme: string;
// Returns the last segment of the URI, similar to the UNIX basename command.
readonly fileName: string;
// Returns the extension of the URI, similar to the UNIX extname command. This includes '.' on the extension.
readonly lastExtension: string;
// Returns a URI where the path just contains the root folder.
readonly root: Uri;
// Returns a URI where the path contains the directory name with .py appended.
readonly packageUri: Uri;
// Returns a URI where the path contains the directory name with .pyi appended.
readonly packageStubUri: Uri;
// Returns a URI where the path has __init__.py appended.
readonly initPyUri: Uri;
// Returns a URI where the path has __init__.pyi appended.
readonly initPyiUri: Uri;
// Returns a URI where the path has py.typed appended.
readonly pytypedUri: Uri;
// Returns the filename without any extensions
readonly fileNameWithoutExtension: string;
// Indicates if the underlying file system for this URI is case sensitive or not.
readonly isCaseSensitive: boolean;
isEmpty(): boolean;
toString(): string;
toUserVisibleString(): string;
// Determines whether a path consists only of a path root.
isRoot(): boolean;
// Determines whether a Uri is a child of some parent Uri.
isChild(parent: Uri): boolean;
isLocal(): boolean;
isUntitled(): boolean;
equals(other: Uri | undefined): boolean;
startsWith(other: Uri | undefined): boolean;
pathStartsWith(name: string): boolean;
pathEndsWith(name: string): boolean;
pathIncludes(include: string): boolean;
matchesRegex(regex: RegExp): boolean;
addPath(extra: string): Uri;
// Returns a URI where the path is the directory name of the original URI, similar to the UNIX dirname command.
getDirectory(): Uri;
getRootPathLength(): number;
// How long the path for this Uri is.
getPathLength(): number;
combinePaths(...paths: string[]): Uri;
getRelativePath(child: Uri): string | undefined;
getPathComponents(): readonly string[];
getPath(): string;
getFilePath(): string;
getRelativePathComponents(to: Uri): readonly string[];
getShortenedFileName(maxDirLength?: number): string;
stripExtension(): Uri;
stripAllExtensions(): Uri;
replaceExtension(ext: string): Uri;
addExtension(ext: string): Uri;
hasExtension(ext: string): boolean;
}
// Returns just the fsPath path portion of a vscode URI.
function getFilePath(uri: URI): string {
let filePath: string | undefined;
// Compute the file path ourselves. The vscode.URI class doesn't
// treat UNC shares with a single slash as UNC paths.
// https://github.com/microsoft/vscode-uri/blob/53e4ca6263f2e4ddc35f5360c62bc1b1d30f27dd/src/uri.ts#L567
if (uri.authority && uri.path[0] === '/' && uri.path.length === 1) {
filePath = `//${uri.authority}${uri.path}`;
} else {
// Otherwise use the vscode.URI version
filePath = uri.fsPath;
}
// If this is a DOS-style path with a drive letter, remove
// the leading slash.
if (filePath.match(/^\/[a-zA-Z]:\//)) {
filePath = filePath.slice(1);
}
// vscode.URI normalizes the path to use the correct path separators.
// We need to do the same.
if (process.platform === 'win32') {
filePath = filePath.replace(/\//g, '\\');
}
return filePath;
}
// Function called to normalize input URIs. This gets rid of '..' and '.' in the path.
// It also removes any '/' on the end of the path.
// This is slow but should only be called when the URI is first created.
function normalizeUri(uri: string | URI): { uri: URI; str: string } {
// Make sure the drive letter is lower case. This
// is consistent with what VS code does for URIs.
let originalString = URI.isUri(uri) ? uri.toString() : uri;
const parsed = URI.isUri(uri) ? uri : URI.parse(uri);
if (parsed.scheme === 'file') {
// The Vscode.URI parser makes sure the drive is lower cased.
originalString = parsed.toString();
}
// Original URI may not have resolved all the `..` in the path, so remove them.
// Note: this also has the effect of removing any trailing slashes.
const finalURI = Utils.resolvePath(parsed);
const finalString = finalURI.path.length !== parsed.path.length ? finalURI.toString() : originalString;
return { uri: finalURI, str: finalString };
}
export namespace Uri {
export function file(path: string, isCaseSensitive = true, checkRelative = false): Uri {
// Fix path if we're checking for relative paths and this is not a rooted path.
path = checkRelative && !isRootedDiskPath(path) ? combinePaths(process.cwd(), path) : path;
// If this already starts with 'file:', then we can
// parse it normally. It's actually a uri string. Otherwise parse it as a file path.
const normalized = path.startsWith('file:') ? normalizeUri(path) : normalizeUri(URI.file(path));
// Turn the path into a file URI.
return FileUri.createFileUri(
getFilePath(normalized.uri),
normalized.uri.query,
normalized.uri.fragment,
normalized.str,
isCaseSensitive
);
}
export function empty(): Uri {
return EmptyUri.instance;
}
export function parse(value: string | undefined, isCaseSensitive: boolean): Uri {
if (!value) {
return Uri.empty();
}
// Normalize the value here. This gets rid of '..' and '.' in the path. It also removes any
// '/' on the end of the path.
const normalized = normalizeUri(value);
if (normalized.uri.scheme === 'file') {
return FileUri.createFileUri(
getFilePath(normalized.uri),
normalized.uri.query,
normalized.uri.fragment,
normalized.str,
isCaseSensitive
);
}
// Web URIs are always case sensitive.
return WebUri.createWebUri(
normalized.uri.scheme,
normalized.uri.authority,
normalized.uri.path,
normalized.uri.query,
normalized.uri.fragment,
normalized.str
);
}
export function isUri(thing: any): thing is Uri {
return !!thing && typeof thing._key === 'string';
}
}

View File

@ -0,0 +1,406 @@
/*
* uriUtils.ts
* Copyright (c) Microsoft Corporation.
* Licensed under the MIT license.
*
* Utility functions for manipulating URIs.
*/
import type { Dirent } from 'fs';
import { randomBytesHex } from '../crypto';
import { FileSystem, ReadOnlyFileSystem, Stats, TempFile } from '../fileSystem';
import {
getRegexEscapedSeparator,
isDirectoryWildcardPatternPresent,
stripTrailingDirectorySeparator,
} from '../pathUtils';
import { Uri } from './uri';
let _fsCaseSensitivity: boolean | undefined = undefined;
let _underTest: boolean = false;
export interface FileSpec {
// File specs can contain wildcard characters (**, *, ?). This
// specifies the first portion of the file spec that contains
// no wildcards.
wildcardRoot: Uri;
// Regular expression that can be used to match against this
// file spec.
regExp: RegExp;
// Indicates whether the file spec has a directory wildcard (**).
// When present, the search cannot terminate without exploring to
// an arbitrary depth.
hasDirectoryWildcard: boolean;
}
const _includeFileRegex = /\.pyi?$/;
export namespace FileSpec {
export function is(value: any): value is FileSpec {
const candidate: FileSpec = value as FileSpec;
return candidate && !!candidate.wildcardRoot && !!candidate.regExp;
}
export function isInPath(uri: Uri, paths: FileSpec[]) {
return !!paths.find((p) => uri.matchesRegex(p.regExp));
}
export function matchesIncludeFileRegex(uri: Uri, isFile = true) {
return isFile ? uri.matchesRegex(_includeFileRegex) : true;
}
export function matchIncludeFileSpec(includeRegExp: RegExp, exclude: FileSpec[], uri: Uri, isFile = true) {
if (uri.matchesRegex(includeRegExp)) {
if (!FileSpec.isInPath(uri, exclude) && FileSpec.matchesIncludeFileRegex(uri, isFile)) {
return true;
}
}
return false;
}
}
export interface FileSystemEntries {
files: Uri[];
directories: Uri[];
}
export function forEachAncestorDirectory(
directory: Uri,
callback: (directory: Uri) => Uri | undefined
): Uri | undefined {
while (true) {
const result = callback(directory);
if (result !== undefined) {
return result;
}
const parentPath = directory.getDirectory();
if (parentPath.equals(directory)) {
return undefined;
}
directory = parentPath;
}
}
// Creates a directory hierarchy for a path, starting from some ancestor path.
export function makeDirectories(fs: FileSystem, dir: Uri, startingFrom: Uri) {
if (!dir.startsWith(startingFrom)) {
return;
}
const pathComponents = dir.getPathComponents();
const relativeToComponents = startingFrom.getPathComponents();
let curPath = startingFrom;
for (let i = relativeToComponents.length; i < pathComponents.length; i++) {
curPath = curPath.combinePaths(pathComponents[i]);
if (!fs.existsSync(curPath)) {
fs.mkdirSync(curPath);
}
}
}
export function getFileSize(fs: ReadOnlyFileSystem, uri: Uri) {
const stat = tryStat(fs, uri);
if (stat?.isFile()) {
return stat.size;
}
return 0;
}
export function fileExists(fs: ReadOnlyFileSystem, uri: Uri): boolean {
return fileSystemEntryExists(fs, uri, FileSystemEntryKind.File);
}
export function directoryExists(fs: ReadOnlyFileSystem, uri: Uri): boolean {
return fileSystemEntryExists(fs, uri, FileSystemEntryKind.Directory);
}
export function isDirectory(fs: ReadOnlyFileSystem, uri: Uri): boolean {
return tryStat(fs, uri)?.isDirectory() ?? false;
}
export function isFile(fs: ReadOnlyFileSystem, uri: Uri, treatZipDirectoryAsFile = false): boolean {
const stats = tryStat(fs, uri);
if (stats?.isFile()) {
return true;
}
if (!treatZipDirectoryAsFile) {
return false;
}
return stats?.isZipDirectory?.() ?? false;
}
export function tryStat(fs: ReadOnlyFileSystem, uri: Uri): Stats | undefined {
try {
if (fs.existsSync(uri)) {
return fs.statSync(uri);
}
} catch (e: any) {
return undefined;
}
return undefined;
}
export function tryRealpath(fs: ReadOnlyFileSystem, uri: Uri): Uri | undefined {
try {
return fs.realCasePath(uri);
} catch (e: any) {
return undefined;
}
}
export function getFileSystemEntries(fs: ReadOnlyFileSystem, uri: Uri): FileSystemEntries {
try {
return getFileSystemEntriesFromDirEntries(fs.readdirEntriesSync(uri), fs, uri);
} catch (e: any) {
return { files: [], directories: [] };
}
}
// Sorts the entires into files and directories, including any symbolic links.
export function getFileSystemEntriesFromDirEntries(
dirEntries: Dirent[],
fs: ReadOnlyFileSystem,
uri: Uri
): FileSystemEntries {
const entries = dirEntries.sort((a, b) => {
if (a.name < b.name) {
return -1;
} else if (a.name > b.name) {
return 1;
} else {
return 0;
}
});
const files: Uri[] = [];
const directories: Uri[] = [];
for (const entry of entries) {
// This is necessary because on some file system node fails to exclude
// "." and "..". See https://github.com/nodejs/node/issues/4002
if (entry.name === '.' || entry.name === '..') {
continue;
}
const entryUri = uri.combinePaths(entry.name);
if (entry.isFile()) {
files.push(entryUri);
} else if (entry.isDirectory()) {
directories.push(entryUri);
} else if (entry.isSymbolicLink()) {
const stat = tryStat(fs, entryUri);
if (stat?.isFile()) {
files.push(entryUri);
} else if (stat?.isDirectory()) {
directories.push(entryUri);
}
}
}
return { files, directories };
}
export function setTestingMode(underTest: boolean) {
_underTest = underTest;
}
// Transforms a relative file spec (one that potentially contains
// escape characters **, * or ?) and returns a regular expression
// that can be used for matching against.
export function getWildcardRegexPattern(root: Uri, fileSpec: string): string {
const absolutePath = root.combinePaths(fileSpec);
const pathComponents = Array.from(absolutePath.getPathComponents());
const escapedSeparator = getRegexEscapedSeparator('/');
const doubleAsteriskRegexFragment = `(${escapedSeparator}[^${escapedSeparator}][^${escapedSeparator}]*)*?`;
const reservedCharacterPattern = new RegExp(`[^\\w\\s${escapedSeparator}]`, 'g');
// Strip the directory separator from the root component.
if (pathComponents.length > 0) {
pathComponents[0] = stripTrailingDirectorySeparator(pathComponents[0]);
}
let regExPattern = '';
let firstComponent = true;
for (let component of pathComponents) {
if (component === '**') {
regExPattern += doubleAsteriskRegexFragment;
} else {
if (!firstComponent) {
component = escapedSeparator + component;
}
regExPattern += component.replace(reservedCharacterPattern, (match) => {
if (match === '*') {
return `[^${escapedSeparator}]*`;
} else if (match === '?') {
return `[^${escapedSeparator}]`;
} else {
// escaping anything that is not reserved characters - word/space/separator
return '\\' + match;
}
});
firstComponent = false;
}
}
return regExPattern;
}
// Returns the topmost path that contains no wildcard characters.
export function getWildcardRoot(root: Uri, fileSpec: string): Uri {
const absolutePath = root.combinePaths(fileSpec);
// make a copy of the path components so we can modify them.
const pathComponents = Array.from(absolutePath.getPathComponents());
let wildcardRoot = absolutePath.root;
// Remove the root component.
if (pathComponents.length > 0) {
pathComponents.shift();
}
for (const component of pathComponents) {
if (component === '**') {
break;
} else {
if (/[*?]/.test(component)) {
break;
}
wildcardRoot = wildcardRoot.combinePaths(component);
}
}
return wildcardRoot;
}
export function hasPythonExtension(uri: Uri) {
return uri.hasExtension('.py') || uri.hasExtension('.pyi');
}
export function getFileSpec(root: Uri, fileSpec: string): FileSpec {
let regExPattern = getWildcardRegexPattern(root, fileSpec);
const escapedSeparator = getRegexEscapedSeparator('/');
regExPattern = `^(${regExPattern})($|${escapedSeparator})`;
const regExp = new RegExp(regExPattern, root.isCaseSensitive ? undefined : 'i');
const wildcardRoot = getWildcardRoot(root, fileSpec);
const hasDirectoryWildcard = isDirectoryWildcardPatternPresent(fileSpec);
return {
wildcardRoot,
regExp,
hasDirectoryWildcard,
};
}
const enum FileSystemEntryKind {
File,
Directory,
}
function fileSystemEntryExists(fs: ReadOnlyFileSystem, uri: Uri, entryKind: FileSystemEntryKind): boolean {
try {
const stat = fs.statSync(uri);
switch (entryKind) {
case FileSystemEntryKind.File:
return stat.isFile();
case FileSystemEntryKind.Directory:
return stat.isDirectory();
default:
return false;
}
} catch (e: any) {
return false;
}
}
const isFileSystemCaseSensitiveMap = new WeakMap<FileSystem, boolean>();
export function isFileSystemCaseSensitive(fs: FileSystem, tmp: TempFile | undefined) {
if (!_underTest && _fsCaseSensitivity !== undefined) {
return _fsCaseSensitivity;
}
if (!isFileSystemCaseSensitiveMap.has(fs)) {
_fsCaseSensitivity = tmp ? isFileSystemCaseSensitiveInternal(fs, tmp) : false;
isFileSystemCaseSensitiveMap.set(fs, _fsCaseSensitivity);
}
return !!isFileSystemCaseSensitiveMap.get(fs);
}
export function isFileSystemCaseSensitiveInternal(fs: FileSystem, tmp: TempFile) {
let filePath: Uri | undefined = undefined;
try {
// Make unique file name.
let name: string;
let mangledFilePath: Uri;
do {
name = `${randomBytesHex(21)}-a`;
filePath = tmp.tmpdir().combinePaths(name);
mangledFilePath = tmp.tmpdir().combinePaths(name.toUpperCase());
} while (fs.existsSync(filePath) || fs.existsSync(mangledFilePath));
fs.writeFileSync(filePath, '', 'utf8');
// If file exists, then it is insensitive.
return !fs.existsSync(mangledFilePath);
} catch (e: any) {
return false;
} finally {
if (filePath) {
// remove temp file created
try {
fs.unlinkSync(filePath);
} catch (e: any) {
/* ignored */
}
}
}
}
export function deduplicateFolders(listOfFolders: Uri[][]): Uri[] {
const foldersToWatch = new Map<string, Uri>();
listOfFolders.forEach((folders) => {
folders.forEach((p) => {
if (foldersToWatch.has(p.key)) {
// Bail out on exact match.
return;
}
for (const existing of foldersToWatch) {
// ex) p: "/user/test" existing: "/user"
if (p.startsWith(existing[1])) {
// We already have the parent folder in the watch list
return;
}
// ex) p: "/user" folderToWatch: "/user/test"
if (existing[1].startsWith(p)) {
// We found better one to watch. replace.
foldersToWatch.delete(existing[0]);
foldersToWatch.set(p.key, p);
return;
}
}
foldersToWatch.set(p.key, p);
});
});
return [...foldersToWatch.values()];
}
export function getRootUri(isCaseSensitive: boolean): Uri | undefined {
if ((global as any).__rootDirectory) {
return Uri.file((global as any).__rootDirectory, isCaseSensitive);
}
return undefined;
}

View File

@ -0,0 +1,207 @@
/*
* webUri.ts
* Copyright (c) Microsoft Corporation.
* Licensed under the MIT license.
*
* URI class that represents a URI that isn't 'file' schemed.
* This can be URIs like:
* - http://www.microsoft.com/file.txt
* - untitled:Untitled-1
* - vscode:extension/ms-python.python
* - vscode-vfs://github.com/microsoft/debugpy/debugpy/launcher/debugAdapter.py
*/
import * as debug from '../debug';
import { getRootLength, hasTrailingDirectorySeparator, normalizeSlashes, resolvePaths } from '../pathUtils';
import { BaseUri } from './baseUri';
import { cacheMethodWithNoArgs, cacheProperty, cacheStaticFunc } from './memoization';
import { Uri } from './uri';
export class WebUri extends BaseUri {
private constructor(
key: string,
private readonly _scheme: string,
private readonly _authority: string,
private readonly _path: string,
private readonly _query: string,
private readonly _fragment: string,
private readonly _originalString: string | undefined
) {
super(key);
}
override get scheme(): string {
return this._scheme;
}
get isCaseSensitive(): boolean {
// Web URIs are always case sensitive
return true;
}
@cacheProperty()
override get root(): Uri {
const rootPath = this.getRootPath();
if (rootPath !== this._path) {
return WebUri.createWebUri(this._scheme, this._authority, rootPath, '', '', undefined);
}
return this;
}
@cacheProperty()
override get fileName(): string {
// Path should already be normalized, just get the last on a split of '/'.
const components = this._path.split('/');
return components[components.length - 1];
}
@cacheProperty()
override get lastExtension(): string {
const basename = this.fileName;
const index = basename.lastIndexOf('.');
if (index >= 0) {
return basename.slice(index);
}
return '';
}
@cacheStaticFunc()
static createWebUri(
scheme: string,
authority: string,
path: string,
query: string,
fragment: string,
originalString: string | undefined
): WebUri {
const key = WebUri._createKey(scheme, authority, path, query, fragment);
return new WebUri(key, scheme, authority, path, query, fragment, originalString);
}
override toString(): string {
if (!this._originalString) {
return `${this._scheme}://${this._authority}${this._path}${this._query ? '?' + this._query : ''}${
this._fragment ? '#' + this._fragment : ''
}`;
}
return this._originalString;
}
override toUserVisibleString(): string {
return this.toString();
}
static isWebUri(uri: Uri): uri is WebUri {
return uri.scheme !== 'file' && (uri as any)._scheme !== undefined;
}
override matchesRegex(regex: RegExp): boolean {
return regex.test(this._path);
}
override addPath(extra: string): Uri {
const newPath = this._path + extra;
return WebUri.createWebUri(this._scheme, this._authority, newPath, this._query, this._fragment, undefined);
}
override isRoot(): boolean {
return this._path === this.getRootPath() && this._path.length > 0;
}
override isChild(parent: Uri): boolean {
if (!WebUri.isWebUri(parent)) {
return false;
}
return parent._path.length < this._path.length && this.startsWith(parent);
}
override isLocal(): boolean {
return false;
}
override startsWith(other: Uri | undefined): boolean {
if (!other || !WebUri.isWebUri(other)) {
return false;
}
if (other.isEmpty() !== this.isEmpty()) {
return false;
}
if (this.scheme !== other.scheme) {
return false;
}
if (this._path.length >= other._path.length) {
// Make sure the other ends with a / when comparing longer paths, otherwise we might
// say that /a/food is a child of /a/foo.
const otherPath =
this._path.length > other._path.length && !hasTrailingDirectorySeparator(other._path)
? `${other._path}/`
: other._path;
return this._path.startsWith(otherPath);
}
return false;
}
override getPathLength(): number {
return this._path.length;
}
override getPath(): string {
return this._path;
}
override getFilePath(): string {
debug.fail(`${this} is not a file based URI.`);
}
override combinePaths(...paths: string[]): Uri {
// Resolve and combine paths, never want URIs with '..' in the middle.
let combined = this.normalizeSlashes(resolvePaths(this._path, ...paths));
// Make sure to remove any trailing directory chars.
if (hasTrailingDirectorySeparator(combined) && combined.length > 1) {
combined = combined.slice(0, combined.length - 1);
}
if (combined !== this._path) {
return WebUri.createWebUri(this._scheme, this._authority, combined, '', '', undefined);
}
return this;
}
@cacheMethodWithNoArgs()
override getDirectory(): Uri {
const index = this._path.lastIndexOf('/');
if (index > 0) {
return WebUri.createWebUri(
this._scheme,
this._authority,
this._path.slice(0, index),
this._query,
this._fragment,
undefined
);
} else {
return this;
}
}
protected override getPathComponentsImpl(): string[] {
// Get the root path and the rest of the path components.
const rootPath = this.getRootPath();
const otherPaths = this._path.slice(rootPath.length).split('/');
return this.reducePathComponents([rootPath, ...otherPaths]).map((component) =>
this.normalizeSlashes(component)
);
}
protected override getRootPath(): string {
const rootLength = getRootLength(this._path, '/');
return this._path.slice(0, rootLength);
}
protected override getComparablePath(): string {
return normalizeSlashes(this._path);
}
private static _createKey(scheme: string, authority: string, path: string, query: string, fragment: string) {
return `${scheme}://${authority}${path}${query ? '?' + query : ''}${fragment ? '#' + fragment : ''}`;
}
}

View File

@ -1,62 +0,0 @@
/*
* uriParser.ts
* Copyright (c) Microsoft Corporation.
* Licensed under the MIT license.
*
* URI utility functions.
*/
import { Position } from 'vscode-languageserver';
import { TextDocumentIdentifier } from 'vscode-languageserver-protocol';
import { URI } from 'vscode-uri';
import { isString } from './core';
import { FileSystem } from './fileSystem';
import { convertUriToPath } from './pathUtils';
export interface IUriParser {
decodeTextDocumentPosition(
textDocument: TextDocumentIdentifier,
position: Position
): { filePath: string; position: Position };
decodeTextDocumentUri(uriString: string): string;
isUntitled(uri: URI | string | undefined): boolean;
isLocal(uri: URI | string | undefined): boolean;
}
export class UriParser implements IUriParser {
constructor(protected readonly fs: FileSystem) {}
decodeTextDocumentPosition(textDocument: TextDocumentIdentifier, position: Position) {
const filePath = this.decodeTextDocumentUri(textDocument.uri);
return { filePath, position };
}
decodeTextDocumentUri(uriString: string) {
return convertUriToPath(this.fs, uriString);
}
isUntitled(uri: URI | string | undefined) {
if (!uri) {
return false;
}
if (isString(uri)) {
uri = URI.parse(uri);
}
return uri.scheme === 'untitled';
}
isLocal(uri: URI | string | undefined) {
if (!uri) {
return false;
}
if (isString(uri)) {
uri = URI.parse(uri);
}
return uri.scheme === 'file';
}
}

View File

@ -19,15 +19,14 @@ import {
import { TextDocument } from 'vscode-languageserver-textdocument';
import { AnalyzerService } from '../analyzer/service';
import { FileEditAction, FileEditActions, TextEditAction } from '../common/editAction';
import { convertPathToUri, convertUriToPath } from '../common/pathUtils';
import { createMapFromItems } from './collectionUtils';
import { isArray } from './core';
import { assertNever } from './debug';
import { EditableProgram, SourceFileInfo } from './extensibility';
import { ReadOnlyFileSystem } from './fileSystem';
import { convertRangeToTextRange, convertTextRangeToRange } from './positionUtils';
import { TextRange } from './textRange';
import { TextRangeCollection } from './textRangeCollection';
import { Uri } from './uri/uri';
export function convertToTextEdits(editActions: TextEditAction[]): TextEdit[] {
return editActions.map((editAction) => ({
@ -36,14 +35,13 @@ export function convertToTextEdits(editActions: TextEditAction[]): TextEdit[] {
}));
}
export function convertToFileTextEdits(filePath: string, editActions: TextEditAction[]): FileEditAction[] {
return editActions.map((a) => ({ filePath, ...a }));
export function convertToFileTextEdits(fileUri: Uri, editActions: TextEditAction[]): FileEditAction[] {
return editActions.map((a) => ({ fileUri, ...a }));
}
export function convertToWorkspaceEdit(fs: ReadOnlyFileSystem, edits: FileEditAction[]): WorkspaceEdit;
export function convertToWorkspaceEdit(fs: ReadOnlyFileSystem, edits: FileEditActions): WorkspaceEdit;
export function convertToWorkspaceEdit(edits: FileEditAction[]): WorkspaceEdit;
export function convertToWorkspaceEdit(edits: FileEditActions): WorkspaceEdit;
export function convertToWorkspaceEdit(
fs: ReadOnlyFileSystem,
edits: FileEditActions,
changeAnnotations: {
[id: string]: ChangeAnnotation;
@ -51,7 +49,6 @@ export function convertToWorkspaceEdit(
defaultAnnotationId: string
): WorkspaceEdit;
export function convertToWorkspaceEdit(
fs: ReadOnlyFileSystem,
edits: FileEditActions | FileEditAction[],
changeAnnotations?: {
[id: string]: ChangeAnnotation;
@ -59,17 +56,17 @@ export function convertToWorkspaceEdit(
defaultAnnotationId = 'default'
): WorkspaceEdit {
if (isArray(edits)) {
return _convertToWorkspaceEditWithChanges(fs, edits);
return _convertToWorkspaceEditWithChanges(edits);
}
return _convertToWorkspaceEditWithDocumentChanges(fs, edits, changeAnnotations, defaultAnnotationId);
return _convertToWorkspaceEditWithDocumentChanges(edits, changeAnnotations, defaultAnnotationId);
}
export function appendToWorkspaceEdit(fs: ReadOnlyFileSystem, edits: FileEditAction[], workspaceEdit: WorkspaceEdit) {
export function appendToWorkspaceEdit(edits: FileEditAction[], workspaceEdit: WorkspaceEdit) {
edits.forEach((edit) => {
const uri = convertPathToUri(fs, edit.filePath);
workspaceEdit.changes![uri] = workspaceEdit.changes![uri] || [];
workspaceEdit.changes![uri].push({ range: edit.range, newText: edit.replacementText });
const uri = edit.fileUri;
workspaceEdit.changes![uri.toString()] = workspaceEdit.changes![uri.toString()] || [];
workspaceEdit.changes![uri.toString()].push({ range: edit.range, newText: edit.replacementText });
});
}
@ -101,18 +98,18 @@ export function applyTextEditsToString(
return current;
}
export function applyWorkspaceEdit(program: EditableProgram, edits: WorkspaceEdit, filesChanged: Set<string>) {
export function applyWorkspaceEdit(program: EditableProgram, edits: WorkspaceEdit, filesChanged: Map<string, Uri>) {
if (edits.changes) {
for (const kv of Object.entries(edits.changes)) {
const filePath = convertUriToPath(program.fileSystem, kv[0]);
const fileInfo = program.getSourceFileInfo(filePath);
const fileUri = Uri.parse(kv[0], program.configOptions.projectRoot.isCaseSensitive);
const fileInfo = program.getSourceFileInfo(fileUri);
if (!fileInfo || !fileInfo.isTracked) {
// We don't allow non user file being modified.
continue;
}
applyDocumentChanges(program, fileInfo, kv[1]);
filesChanged.add(filePath);
filesChanged.set(fileUri.key, fileUri);
}
}
@ -120,15 +117,15 @@ export function applyWorkspaceEdit(program: EditableProgram, edits: WorkspaceEdi
if (edits.documentChanges) {
for (const change of edits.documentChanges) {
if (TextDocumentEdit.is(change)) {
const filePath = convertUriToPath(program.fileSystem, change.textDocument.uri);
const fileInfo = program.getSourceFileInfo(filePath);
const fileUri = Uri.parse(change.textDocument.uri, program.configOptions.projectRoot.isCaseSensitive);
const fileInfo = program.getSourceFileInfo(fileUri);
if (!fileInfo || !fileInfo.isTracked) {
// We don't allow non user file being modified.
continue;
}
applyDocumentChanges(program, fileInfo, change.edits);
filesChanged.add(filePath);
filesChanged.set(fileUri.key, fileUri);
}
// For now, we don't support other kinds of text changes.
@ -140,30 +137,29 @@ export function applyWorkspaceEdit(program: EditableProgram, edits: WorkspaceEdi
export function applyDocumentChanges(program: EditableProgram, fileInfo: SourceFileInfo, edits: TextEdit[]) {
if (!fileInfo.isOpenByClient) {
const fileContent = fileInfo.sourceFile.getFileContent();
program.setFileOpened(fileInfo.sourceFile.getFilePath(), 0, fileContent ?? '', {
program.setFileOpened(fileInfo.sourceFile.getUri(), 0, fileContent ?? '', {
isTracked: fileInfo.isTracked,
ipythonMode: fileInfo.sourceFile.getIPythonMode(),
chainedFilePath: fileInfo.chainedSourceFile?.sourceFile.getFilePath(),
realFilePath: fileInfo.sourceFile.getRealFilePath(),
chainedFileUri: fileInfo.chainedSourceFile?.sourceFile.getUri(),
});
}
const version = fileInfo.sourceFile.getClientVersion() ?? 0;
const filePath = fileInfo.sourceFile.getFilePath();
const fileUri = fileInfo.sourceFile.getUri();
const filePath = fileUri.getFilePath();
const sourceDoc = TextDocument.create(filePath, 'python', version, fileInfo.sourceFile.getOpenFileContents() ?? '');
program.setFileOpened(filePath, version + 1, TextDocument.applyEdits(sourceDoc, edits), {
program.setFileOpened(fileUri, version + 1, TextDocument.applyEdits(sourceDoc, edits), {
isTracked: fileInfo.isTracked,
ipythonMode: fileInfo.sourceFile.getIPythonMode(),
chainedFilePath: fileInfo.chainedSourceFile?.sourceFile.getFilePath(),
realFilePath: fileInfo.sourceFile.getRealFilePath(),
chainedFileUri: fileInfo.chainedSourceFile?.sourceFile.getUri(),
});
}
export function generateWorkspaceEdit(
originalService: AnalyzerService,
clonedService: AnalyzerService,
filesChanged: Set<string>
filesChanged: Map<string, Uri>
) {
// For now, we won't do text diff to find out minimal text changes. instead, we will
// consider whole text of the files are changed. In future, we could consider
@ -171,9 +167,9 @@ export function generateWorkspaceEdit(
// to support annotation.
const edits: WorkspaceEdit = { changes: {} };
for (const filePath of filesChanged) {
const original = originalService.backgroundAnalysisProgram.program.getBoundSourceFile(filePath);
const final = clonedService.backgroundAnalysisProgram.program.getBoundSourceFile(filePath);
for (const uri of filesChanged.values()) {
const original = originalService.backgroundAnalysisProgram.program.getBoundSourceFile(uri);
const final = clonedService.backgroundAnalysisProgram.program.getBoundSourceFile(uri);
if (!original || !final) {
// Both must exist.
continue;
@ -184,7 +180,7 @@ export function generateWorkspaceEdit(
continue;
}
edits.changes![convertPathToUri(originalService.fs, filePath)] = [
edits.changes![uri.toString()] = [
{
range: convertTextRangeToRange(parseResults.parseTree, parseResults.tokenizerOutput.lines),
newText: final.getFileContent() ?? '',
@ -195,17 +191,16 @@ export function generateWorkspaceEdit(
return edits;
}
function _convertToWorkspaceEditWithChanges(fs: ReadOnlyFileSystem, edits: FileEditAction[]) {
function _convertToWorkspaceEditWithChanges(edits: FileEditAction[]) {
const workspaceEdit: WorkspaceEdit = {
changes: {},
};
appendToWorkspaceEdit(fs, edits, workspaceEdit);
appendToWorkspaceEdit(edits, workspaceEdit);
return workspaceEdit;
}
function _convertToWorkspaceEditWithDocumentChanges(
fs: ReadOnlyFileSystem,
editActions: FileEditActions,
changeAnnotations?: {
[id: string]: ChangeAnnotation;
@ -223,11 +218,7 @@ function _convertToWorkspaceEditWithDocumentChanges(
switch (operation.kind) {
case 'create':
workspaceEdit.documentChanges!.push(
CreateFile.create(
convertPathToUri(fs, operation.filePath),
/* options */ undefined,
defaultAnnotationId
)
CreateFile.create(operation.fileUri.toString(), /* options */ undefined, defaultAnnotationId)
);
break;
case 'rename':
@ -239,11 +230,11 @@ function _convertToWorkspaceEditWithDocumentChanges(
}
// 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);
const mapPerFile = createMapFromItems(editActions.edits, (e) => e.fileUri.key);
for (const [key, value] of mapPerFile) {
workspaceEdit.documentChanges!.push(
TextDocumentEdit.create(
{ uri: convertPathToUri(fs, key), version: null },
{ uri: key, version: null },
Array.from(
value.map((v) => ({
range: v.range,
@ -262,8 +253,8 @@ function _convertToWorkspaceEditWithDocumentChanges(
case 'rename':
workspaceEdit.documentChanges!.push(
RenameFile.create(
convertPathToUri(fs, operation.oldFilePath),
convertPathToUri(fs, operation.newFilePath),
operation.oldFileUri.toString(),
operation.newFileUri.toString(),
/* options */ undefined,
defaultAnnotationId
)
@ -271,11 +262,7 @@ function _convertToWorkspaceEditWithDocumentChanges(
break;
case 'delete':
workspaceEdit.documentChanges!.push(
DeleteFile.create(
convertPathToUri(fs, operation.filePath),
/* options */ undefined,
defaultAnnotationId
)
DeleteFile.create(operation.fileUri.toString(), /* options */ undefined, defaultAnnotationId)
);
break;
default:

View File

@ -107,18 +107,11 @@ import { FileSystem, ReadOnlyFileSystem } from './common/fileSystem';
import { FileWatcherEventType, FileWatcherHandler } from './common/fileWatcher';
import { Host } from './common/host';
import { fromLSPAny } from './common/lspUtils';
import {
convertPathToUri,
convertUriToPath,
deduplicateFolders,
getDirectoryPath,
getFileName,
isFile,
} from './common/pathUtils';
import { ProgressReportTracker, ProgressReporter } from './common/progressReporter';
import { ServiceProvider } from './common/serviceProvider';
import { DocumentRange, Position, Range } from './common/textRange';
import { UriParser } from './common/uriParser';
import { Uri } from './common/uri/uri';
import { deduplicateFolders, isFile } from './common/uri/uriUtils';
import { AnalyzerServiceExecutor } from './languageService/analyzerServiceExecutor';
import { CallHierarchyProvider } from './languageService/callHierarchyProvider';
import { CompletionItemData, CompletionProvider } from './languageService/completionProvider';
@ -137,17 +130,17 @@ import { ParseResults } from './parser/parser';
import { InitStatus, WellKnownWorkspaceKinds, Workspace, WorkspaceFactory } from './workspaceFactory';
export interface ServerSettings {
venvPath?: string | undefined;
pythonPath?: string | undefined;
typeshedPath?: string | undefined;
stubPath?: string | undefined;
venvPath?: Uri | undefined;
pythonPath?: Uri | undefined;
typeshedPath?: Uri | undefined;
stubPath?: Uri | undefined;
openFilesOnly?: boolean | undefined;
typeCheckingMode?: string | undefined;
useLibraryCodeForTypes?: boolean | undefined;
disableLanguageServices?: boolean | undefined;
disableOrganizeImports?: boolean | undefined;
autoSearchPaths?: boolean | undefined;
extraPaths?: string[] | undefined;
extraPaths?: Uri[] | undefined;
watchForSourceChanges?: boolean | undefined;
watchForLibraryChanges?: boolean | undefined;
watchForConfigChanges?: boolean | undefined;
@ -181,23 +174,22 @@ export interface WindowInterface {
}
export interface LanguageServerInterface {
readonly rootPath: string;
readonly rootUri: Uri;
readonly console: ConsoleInterface;
readonly window: WindowInterface;
readonly supportAdvancedEdits: boolean;
getWorkspaces(): Promise<Workspace[]>;
getWorkspaceForFile(filePath: string): Promise<Workspace>;
getWorkspaceForFile(fileUri: Uri): Promise<Workspace>;
getSettings(workspace: Workspace): Promise<ServerSettings>;
createBackgroundAnalysis(serviceId: string): BackgroundAnalysisBase | undefined;
reanalyze(): void;
restart(): void;
decodeTextDocumentUri(uriString: string): string;
}
export interface ServerOptions {
productName: string;
rootDirectory: string;
rootDirectory: Uri;
version: string;
cancellationProvider: CancellationProvider;
serviceProvider: ServiceProvider;
@ -323,7 +315,7 @@ export abstract class LanguageServerBase implements LanguageServerInterface, Dis
private _workspaceFoldersChangedDisposable: Disposable | undefined;
// Global root path - the basis for all global settings.
rootPath = '';
rootUri = Uri.empty();
protected client: ClientCapabilities = {
hasConfigurationCapability: false,
@ -358,16 +350,10 @@ export abstract class LanguageServerBase implements LanguageServerInterface, Dis
// The URIs for which diagnostics are reported
protected readonly documentsWithDiagnostics = new Set<string>();
readonly uriParser: UriParser;
constructor(
protected serverOptions: ServerOptions,
protected connection: Connection,
uriParserFactory = (fs: FileSystem) => new UriParser(fs)
) {
constructor(protected serverOptions: ServerOptions, protected connection: Connection) {
// Stash the base directory into a global variable.
// This must happen before fs.getModulePath().
(global as any).__rootDirectory = serverOptions.rootDirectory;
(global as any).__rootDirectory = serverOptions.rootDirectory.getFilePath();
this.console.info(
`${serverOptions.productName} language server ${
@ -379,16 +365,14 @@ export abstract class LanguageServerBase implements LanguageServerInterface, Dis
this.fs = this.serverOptions.serviceProvider.fs();
this.uriParser = uriParserFactory(this.fs);
this.workspaceFactory = new WorkspaceFactory(
this.console,
this.uriParser,
/* isWeb */ false,
this.createAnalyzerServiceForWorkspace.bind(this),
this.isPythonPathImmutable.bind(this),
this.onWorkspaceCreated.bind(this),
this.onWorkspaceRemoved.bind(this)
this.onWorkspaceRemoved.bind(this),
this.fs.isCaseSensitive
);
// Set the working directory to a known location within
@ -427,11 +411,6 @@ export abstract class LanguageServerBase implements LanguageServerInterface, Dis
this._workspaceFoldersChangedDisposable?.dispose();
}
// Convert uri to path
decodeTextDocumentUri(uriString: string): string {
return this.uriParser.decodeTextDocumentUri(uriString);
}
abstract createBackgroundAnalysis(serviceId: string): BackgroundAnalysisBase | undefined;
abstract getSettings(workspace: Workspace): Promise<ServerSettings>;
@ -472,12 +451,12 @@ export abstract class LanguageServerBase implements LanguageServerInterface, Dis
return workspaces;
}
async getWorkspaceForFile(filePath: string, pythonPath?: string): Promise<Workspace> {
return this.workspaceFactory.getWorkspaceForFile(filePath, pythonPath);
async getWorkspaceForFile(fileUri: Uri, pythonPath?: Uri): Promise<Workspace> {
return this.workspaceFactory.getWorkspaceForFile(fileUri, pythonPath);
}
async getContainingWorkspacesForFile(filePath: string): Promise<Workspace[]> {
return this.workspaceFactory.getContainingWorkspacesForFile(filePath);
async getContainingWorkspacesForFile(fileUri: Uri): Promise<Workspace[]> {
return this.workspaceFactory.getContainingWorkspacesForFile(fileUri);
}
reanalyze() {
@ -521,7 +500,10 @@ export abstract class LanguageServerBase implements LanguageServerInterface, Dis
(this.console as ConsoleWithLogLevel).level = serverSettings.logLevel ?? LogLevel.Info;
// Apply the new path to the workspace (before restarting the service).
serverSettings.pythonPath = this.workspaceFactory.applyPythonPath(workspace, serverSettings.pythonPath);
serverSettings.pythonPath = this.workspaceFactory.applyPythonPath(
workspace,
serverSettings.pythonPath ? serverSettings.pythonPath : undefined
);
// Then use the updated settings to restart the service.
this.updateOptionsAndRestartService(workspace, serverSettings);
@ -540,8 +522,8 @@ export abstract class LanguageServerBase implements LanguageServerInterface, Dis
serverSettings: ServerSettings,
typeStubTargetImportName?: string
) {
AnalyzerServiceExecutor.runWithOptions(this.rootPath, workspace, serverSettings, typeStubTargetImportName);
workspace.searchPathsToWatch = workspace.service.librarySearchPathsToWatch ?? [];
AnalyzerServiceExecutor.runWithOptions(this.rootUri, workspace, serverSettings, typeStubTargetImportName);
workspace.searchPathsToWatch = workspace.service.librarySearchUrisToWatch ?? [];
}
protected abstract executeCommand(params: ExecuteCommandParams, token: CancellationToken): Promise<any>;
@ -553,18 +535,18 @@ export abstract class LanguageServerBase implements LanguageServerInterface, Dis
token: CancellationToken
): Promise<(Command | CodeAction)[] | undefined | null>;
protected isPythonPathImmutable(filePath: string): boolean {
protected isPythonPathImmutable(fileUri: Uri): boolean {
// This function is called to determine if the file is using
// a special pythonPath separate from a workspace or not.
// The default is no.
return false;
}
protected async getConfiguration(scopeUri: string | undefined, section: string) {
protected async getConfiguration(scopeUri: Uri | undefined, section: string) {
if (this.client.hasConfigurationCapability) {
const item: ConfigurationItem = {};
if (scopeUri !== undefined) {
item.scopeUri = scopeUri;
item.scopeUri = scopeUri.toString();
}
if (section !== undefined) {
item.section = section;
@ -691,7 +673,7 @@ export abstract class LanguageServerBase implements LanguageServerInterface, Dis
setLocaleOverride(params.locale);
}
this.rootPath = params.rootPath || '';
this.rootUri = params.rootUri ? Uri.parse(params.rootUri, this.fs.isCaseSensitive) : Uri.empty();
const capabilities = params.capabilities;
this.client.hasConfigurationCapability = !!capabilities.workspace?.configuration;
@ -862,7 +844,7 @@ export abstract class LanguageServerBase implements LanguageServerInterface, Dis
filter: DefinitionFilter,
getDefinitionsFunc: (
workspace: Workspace,
filePath: string,
fileUri: Uri,
position: Position,
filter: DefinitionFilter,
token: CancellationToken
@ -870,20 +852,20 @@ export abstract class LanguageServerBase implements LanguageServerInterface, Dis
) {
this.recordUserInteractionTime();
const { filePath, position } = this.uriParser.decodeTextDocumentPosition(params.textDocument, params.position);
const uri = Uri.parse(params.textDocument.uri, this.fs.isCaseSensitive);
const workspace = await this.getWorkspaceForFile(filePath);
const workspace = await this.getWorkspaceForFile(uri);
if (workspace.disableLanguageServices) {
return undefined;
}
const locations = getDefinitionsFunc(workspace, filePath, position, filter, token);
const locations = getDefinitionsFunc(workspace, uri, params.position, filter, token);
if (!locations) {
return undefined;
}
return locations
.filter((loc) => this.canNavigateToFile(loc.path, workspace.service.fs))
.map((loc) => Location.create(convertPathToUri(workspace.service.fs, loc.path), loc.range));
.filter((loc) => this.canNavigateToFile(loc.uri, workspace.service.fs))
.map((loc) => Location.create(loc.uri.toString(), loc.range));
}
protected async onReferences(
@ -891,7 +873,7 @@ export abstract class LanguageServerBase implements LanguageServerInterface, Dis
token: CancellationToken,
workDoneReporter: WorkDoneProgressReporter,
resultReporter: ResultProgressReporter<Location[]> | undefined,
createDocumentRange?: (filePath: string, result: CollectionResult, parseResults: ParseResults) => DocumentRange,
createDocumentRange?: (uri: Uri, result: CollectionResult, parseResults: ParseResults) => DocumentRange,
convertToLocation?: (fs: ReadOnlyFileSystem, ranges: DocumentRange) => Location | undefined
): Promise<Location[] | null | undefined> {
if (this._pendingFindAllRefsCancellationSource) {
@ -912,12 +894,9 @@ export abstract class LanguageServerBase implements LanguageServerInterface, Dis
this._pendingFindAllRefsCancellationSource = source;
try {
const { filePath, position } = this.uriParser.decodeTextDocumentPosition(
params.textDocument,
params.position
);
const uri = Uri.parse(params.textDocument.uri, this.fs.isCaseSensitive);
const workspace = await this.getWorkspaceForFile(filePath);
const workspace = await this.getWorkspaceForFile(uri);
if (workspace.disableLanguageServices) {
return;
}
@ -928,7 +907,7 @@ export abstract class LanguageServerBase implements LanguageServerInterface, Dis
source.token,
createDocumentRange,
convertToLocation
).reportReferences(filePath, position, params.context.includeDeclaration, resultReporter);
).reportReferences(uri, params.position, params.context.includeDeclaration, resultReporter);
}, token);
} finally {
progress.reporter.done();
@ -942,8 +921,8 @@ export abstract class LanguageServerBase implements LanguageServerInterface, Dis
): Promise<DocumentSymbol[] | SymbolInformation[] | null | undefined> {
this.recordUserInteractionTime();
const filePath = this.uriParser.decodeTextDocumentUri(params.textDocument.uri);
const workspace = await this.getWorkspaceForFile(filePath);
const uri = Uri.parse(params.textDocument.uri, this.fs.isCaseSensitive);
const workspace = await this.getWorkspaceForFile(uri);
if (workspace.disableLanguageServices) {
return undefined;
}
@ -951,7 +930,7 @@ export abstract class LanguageServerBase implements LanguageServerInterface, Dis
return workspace.service.run((program) => {
return new DocumentSymbolProvider(
program,
filePath,
uri,
this.client.hasHierarchicalDocumentSymbolCapability,
token
).getSymbols();
@ -974,11 +953,11 @@ export abstract class LanguageServerBase implements LanguageServerInterface, Dis
}
protected async onHover(params: HoverParams, token: CancellationToken) {
const { filePath, position } = this.uriParser.decodeTextDocumentPosition(params.textDocument, params.position);
const workspace = await this.getWorkspaceForFile(filePath);
const uri = Uri.parse(params.textDocument.uri, this.fs.isCaseSensitive);
const workspace = await this.getWorkspaceForFile(uri);
return workspace.service.run((program) => {
return new HoverProvider(program, filePath, position, this.client.hoverContentFormat, token).getHover();
return new HoverProvider(program, uri, params.position, this.client.hoverContentFormat, token).getHover();
}, token);
}
@ -986,11 +965,11 @@ export abstract class LanguageServerBase implements LanguageServerInterface, Dis
params: DocumentHighlightParams,
token: CancellationToken
): Promise<DocumentHighlight[] | null | undefined> {
const { filePath, position } = this.uriParser.decodeTextDocumentPosition(params.textDocument, params.position);
const workspace = await this.getWorkspaceForFile(filePath);
const uri = Uri.parse(params.textDocument.uri, this.fs.isCaseSensitive);
const workspace = await this.getWorkspaceForFile(uri);
return workspace.service.run((program) => {
return new DocumentHighlightProvider(program, filePath, position, token).getDocumentHighlight();
return new DocumentHighlightProvider(program, uri, params.position, token).getDocumentHighlight();
}, token);
}
@ -998,9 +977,9 @@ export abstract class LanguageServerBase implements LanguageServerInterface, Dis
params: SignatureHelpParams,
token: CancellationToken
): Promise<SignatureHelp | undefined | null> {
const { filePath, position } = this.uriParser.decodeTextDocumentPosition(params.textDocument, params.position);
const uri = Uri.parse(params.textDocument.uri, this.fs.isCaseSensitive);
const workspace = await this.getWorkspaceForFile(filePath);
const workspace = await this.getWorkspaceForFile(uri);
if (workspace.disableLanguageServices) {
return;
}
@ -1008,8 +987,8 @@ export abstract class LanguageServerBase implements LanguageServerInterface, Dis
return workspace.service.run((program) => {
return new SignatureHelpProvider(
program,
filePath,
position,
uri,
params.position,
this.client.signatureDocFormat,
this.client.hasSignatureLabelOffsetCapability,
this.client.hasActiveParameterCapability,
@ -1040,8 +1019,8 @@ export abstract class LanguageServerBase implements LanguageServerInterface, Dis
}
protected async onCompletion(params: CompletionParams, token: CancellationToken): Promise<CompletionList | null> {
const { filePath, position } = this.uriParser.decodeTextDocumentPosition(params.textDocument, params.position);
const workspace = await this.getWorkspaceForFile(filePath);
const uri = Uri.parse(params.textDocument.uri, this.fs.isCaseSensitive);
const workspace = await this.getWorkspaceForFile(uri);
if (workspace.disableLanguageServices) {
return null;
}
@ -1049,9 +1028,9 @@ export abstract class LanguageServerBase implements LanguageServerInterface, Dis
return workspace.service.run((program) => {
const completions = new CompletionProvider(
program,
workspace.rootPath,
filePath,
position,
workspace.rootUri,
uri,
params.position,
{
format: this.client.completionDocFormat,
snippet: this.client.completionSupportsSnippet,
@ -1074,13 +1053,14 @@ export abstract class LanguageServerBase implements LanguageServerInterface, Dis
// cache that result and never call us back.
protected async onCompletionResolve(params: CompletionItem, token: CancellationToken): Promise<CompletionItem> {
const completionItemData = fromLSPAny<CompletionItemData>(params.data);
if (completionItemData && completionItemData.filePath) {
const workspace = await this.getWorkspaceForFile(completionItemData.filePath);
if (completionItemData && completionItemData.uri) {
const uri = Uri.parse(completionItemData.uri, this.fs.isCaseSensitive);
const workspace = await this.getWorkspaceForFile(uri);
workspace.service.run((program) => {
return new CompletionProvider(
program,
workspace.rootPath,
completionItemData.filePath,
workspace.rootUri,
uri,
completionItemData.position,
{
format: this.client.completionDocFormat,
@ -1098,16 +1078,16 @@ export abstract class LanguageServerBase implements LanguageServerInterface, Dis
params: PrepareRenameParams,
token: CancellationToken
): Promise<Range | { range: Range; placeholder: string } | null> {
const { filePath, position } = this.uriParser.decodeTextDocumentPosition(params.textDocument, params.position);
const isUntitled = this.uriParser.isUntitled(params.textDocument.uri);
const uri = Uri.parse(params.textDocument.uri, this.fs.isCaseSensitive);
const isUntitled = uri.isUntitled();
const workspace = await this.getWorkspaceForFile(filePath);
const workspace = await this.getWorkspaceForFile(uri);
if (workspace.disableLanguageServices) {
return null;
}
return workspace.service.run((program) => {
return new RenameProvider(program, filePath, position, token).canRenameSymbol(
return new RenameProvider(program, uri, params.position, token).canRenameSymbol(
workspace.kinds.includes(WellKnownWorkspaceKinds.Default),
isUntitled
);
@ -1118,16 +1098,16 @@ export abstract class LanguageServerBase implements LanguageServerInterface, Dis
params: RenameParams,
token: CancellationToken
): Promise<WorkspaceEdit | null | undefined> {
const { filePath, position } = this.uriParser.decodeTextDocumentPosition(params.textDocument, params.position);
const isUntitled = this.uriParser.isUntitled(params.textDocument.uri);
const uri = Uri.parse(params.textDocument.uri, this.fs.isCaseSensitive);
const isUntitled = uri.isUntitled();
const workspace = await this.getWorkspaceForFile(filePath);
const workspace = await this.getWorkspaceForFile(uri);
if (workspace.disableLanguageServices) {
return;
}
return workspace.service.run((program) => {
return new RenameProvider(program, filePath, position, token).renameSymbol(
return new RenameProvider(program, uri, params.position, token).renameSymbol(
params.newName,
workspace.kinds.includes(WellKnownWorkspaceKinds.Default),
isUntitled
@ -1139,28 +1119,28 @@ export abstract class LanguageServerBase implements LanguageServerInterface, Dis
params: CallHierarchyPrepareParams,
token: CancellationToken
): Promise<CallHierarchyItem[] | null> {
const { filePath, position } = this.uriParser.decodeTextDocumentPosition(params.textDocument, params.position);
const uri = Uri.parse(params.textDocument.uri, this.fs.isCaseSensitive);
const workspace = await this.getWorkspaceForFile(filePath);
const workspace = await this.getWorkspaceForFile(uri);
if (workspace.disableLanguageServices) {
return null;
}
return workspace.service.run((program) => {
return new CallHierarchyProvider(program, filePath, position, token).onPrepare();
return new CallHierarchyProvider(program, uri, params.position, token).onPrepare();
}, token);
}
protected async onCallHierarchyIncomingCalls(params: CallHierarchyIncomingCallsParams, token: CancellationToken) {
const { filePath, position } = this.uriParser.decodeTextDocumentPosition(params.item, params.item.range.start);
const uri = Uri.parse(params.item.uri, this.fs.isCaseSensitive);
const workspace = await this.getWorkspaceForFile(filePath);
const workspace = await this.getWorkspaceForFile(uri);
if (workspace.disableLanguageServices) {
return null;
}
return workspace.service.run((program) => {
return new CallHierarchyProvider(program, filePath, position, token).getIncomingCalls();
return new CallHierarchyProvider(program, uri, params.item.range.start, token).getIncomingCalls();
}, token);
}
@ -1168,46 +1148,51 @@ export abstract class LanguageServerBase implements LanguageServerInterface, Dis
params: CallHierarchyOutgoingCallsParams,
token: CancellationToken
): Promise<CallHierarchyOutgoingCall[] | null> {
const { filePath, position } = this.uriParser.decodeTextDocumentPosition(params.item, params.item.range.start);
const uri = Uri.parse(params.item.uri, this.fs.isCaseSensitive);
const workspace = await this.getWorkspaceForFile(filePath);
const workspace = await this.getWorkspaceForFile(uri);
if (workspace.disableLanguageServices) {
return null;
}
return workspace.service.run((program) => {
return new CallHierarchyProvider(program, filePath, position, token).getOutgoingCalls();
return new CallHierarchyProvider(program, uri, params.item.range.start, token).getOutgoingCalls();
}, token);
}
protected async onDidOpenTextDocument(params: DidOpenTextDocumentParams, ipythonMode = IPythonMode.None) {
const filePath = this.uriParser.decodeTextDocumentUri(params.textDocument.uri);
const uri = Uri.parse(params.textDocument.uri, this.fs.isCaseSensitive);
let doc = this.openFileMap.get(filePath);
let doc = this.openFileMap.get(uri.key);
if (doc) {
// We shouldn't get an open text document request for an already-opened doc.
this.console.error(`Received redundant open text document command for ${filePath}`);
this.console.error(`Received redundant open text document command for ${uri}`);
TextDocument.update(doc, [{ text: params.textDocument.text }], params.textDocument.version);
} else {
doc = TextDocument.create(filePath, 'python', params.textDocument.version, params.textDocument.text);
doc = TextDocument.create(
params.textDocument.uri,
'python',
params.textDocument.version,
params.textDocument.text
);
}
this.openFileMap.set(filePath, doc);
this.openFileMap.set(uri.key, doc);
// Send this open to all the workspaces that might contain this file.
const workspaces = await this.getContainingWorkspacesForFile(filePath);
const workspaces = await this.getContainingWorkspacesForFile(uri);
workspaces.forEach((w) => {
w.service.setFileOpened(filePath, params.textDocument.version, params.textDocument.text, ipythonMode);
w.service.setFileOpened(uri, params.textDocument.version, params.textDocument.text, ipythonMode);
});
}
protected async onDidChangeTextDocument(params: DidChangeTextDocumentParams, ipythonMode = IPythonMode.None) {
this.recordUserInteractionTime();
const filePath = this.uriParser.decodeTextDocumentUri(params.textDocument.uri);
const doc = this.openFileMap.get(filePath);
const uri = Uri.parse(params.textDocument.uri, this.fs.isCaseSensitive);
const doc = this.openFileMap.get(uri.key);
if (!doc) {
// We shouldn't get a change text request for a closed doc.
this.console.error(`Received change text document command for closed file ${filePath}`);
this.console.error(`Received change text document command for closed file ${uri}`);
return;
}
@ -1215,27 +1200,27 @@ export abstract class LanguageServerBase implements LanguageServerInterface, Dis
const newContents = doc.getText();
// Send this change to all the workspaces that might contain this file.
const workspaces = await this.getContainingWorkspacesForFile(filePath);
const workspaces = await this.getContainingWorkspacesForFile(uri);
workspaces.forEach((w) => {
w.service.updateOpenFileContents(filePath, params.textDocument.version, newContents, ipythonMode);
w.service.updateOpenFileContents(uri, params.textDocument.version, newContents, ipythonMode);
});
}
protected async onDidCloseTextDocument(params: DidCloseTextDocumentParams) {
const filePath = this.uriParser.decodeTextDocumentUri(params.textDocument.uri);
const uri = Uri.parse(params.textDocument.uri, this.fs.isCaseSensitive);
// Send this close to all the workspaces that might contain this file.
const workspaces = await this.getContainingWorkspacesForFile(filePath);
const workspaces = await this.getContainingWorkspacesForFile(uri);
workspaces.forEach((w) => {
w.service.setFileClosed(filePath);
w.service.setFileClosed(uri);
});
this.openFileMap.delete(filePath);
this.openFileMap.delete(uri.key);
}
protected onDidChangeWatchedFiles(params: DidChangeWatchedFilesParams) {
params.changes.forEach((change) => {
const filePath = this.fs.realCasePath(this.uriParser.decodeTextDocumentUri(change.uri));
const filePath = this.fs.realCasePath(Uri.parse(change.uri, this.fs.isCaseSensitive));
const eventType: FileWatcherEventType = change.type === 1 ? 'add' : 'change';
this.serverOptions.fileWatcherHandler.onFileChange(eventType, filePath);
});
@ -1302,7 +1287,7 @@ export abstract class LanguageServerBase implements LanguageServerInterface, Dis
protected convertDiagnostics(fs: FileSystem, fileDiagnostics: FileDiagnostics): PublishDiagnosticsParams[] {
return [
{
uri: convertPathToUri(fs, fileDiagnostics.filePath),
uri: fileDiagnostics.fileUri.toString(),
version: fileDiagnostics.version,
diagnostics: this._convertDiagnostics(fs, fileDiagnostics.diagnostics),
},
@ -1316,7 +1301,7 @@ export abstract class LanguageServerBase implements LanguageServerInterface, Dis
protected onAnalysisCompletedHandler(fs: FileSystem, results: AnalysisResults): void {
// Send the computed diagnostics to the client.
results.diagnostics.forEach((fileDiag) => {
if (!this.canNavigateToFile(fileDiag.filePath, fs)) {
if (!this.canNavigateToFile(fileDiag.fileUri, fs)) {
return;
}
@ -1361,11 +1346,11 @@ export abstract class LanguageServerBase implements LanguageServerInterface, Dis
const otherWorkspaces = this.workspaceFactory.items().filter((w) => w !== workspace);
for (const uri of documentsWithDiagnosticsList) {
const filePath = convertUriToPath(workspace.service.fs, uri);
const fileUri = Uri.parse(uri, this.fs.isCaseSensitive);
if (workspace.service.isTracked(filePath)) {
if (workspace.service.isTracked(fileUri)) {
// Do not clean up diagnostics for files tracked by multiple workspaces
if (otherWorkspaces.some((w) => w.service.isTracked(filePath))) {
if (otherWorkspaces.some((w) => w.service.isTracked(fileUri))) {
continue;
}
this.sendDiagnostics([
@ -1380,8 +1365,7 @@ export abstract class LanguageServerBase implements LanguageServerInterface, Dis
protected createAnalyzerServiceForWorkspace(
name: string,
_rootPath: string,
_uri: string,
uri: Uri,
kinds: string[],
services?: WorkspaceServices
): AnalyzerService {
@ -1422,7 +1406,7 @@ export abstract class LanguageServerBase implements LanguageServerInterface, Dis
protected abstract createProgressReporter(): ProgressReporter;
protected canNavigateToFile(path: string, fs: FileSystem): boolean {
protected canNavigateToFile(path: Uri, fs: FileSystem): boolean {
return canNavigateToFile(fs, path);
}
@ -1481,13 +1465,13 @@ export abstract class LanguageServerBase implements LanguageServerInterface, Dis
const foldersToWatch = deduplicateFolders(
this.workspaceFactory
.getNonDefaultWorkspaces()
.map((w) => w.searchPathsToWatch.filter((p) => !p.startsWith(w.rootPath)))
.map((w) => w.searchPathsToWatch.filter((p) => !p.startsWith(w.rootUri)))
);
foldersToWatch.forEach((p) => {
const globPattern = isFile(this.fs, p, /* treatZipDirectoryAsFile */ true)
? { baseUri: convertPathToUri(this.fs, getDirectoryPath(p)), pattern: getFileName(p) }
: { baseUri: convertPathToUri(this.fs, p), pattern: '**' };
? { baseUri: p.getDirectory().toString(), pattern: p.fileName }
: { baseUri: p.toString(), pattern: '**' };
watchers.push({ globPattern, kind: watchKind });
});
@ -1582,10 +1566,10 @@ export abstract class LanguageServerBase implements LanguageServerInterface, Dis
const relatedInfo = diag.getRelatedInfo();
if (relatedInfo.length > 0) {
vsDiag.relatedInformation = relatedInfo
.filter((info) => this.canNavigateToFile(info.filePath, fs))
.filter((info) => this.canNavigateToFile(info.uri, fs))
.map((info) =>
DiagnosticRelatedInformation.create(
Location.create(convertPathToUri(fs, info.filePath), info.range),
Location.create(info.uri.toString(), info.range),
info.message
)
);

View File

@ -13,7 +13,7 @@ import { AnalyzerService, getNextServiceId } from '../analyzer/service';
import { CommandLineOptions } from '../common/commandLineOptions';
import { LogLevel } from '../common/console';
import { FileSystem } from '../common/fileSystem';
import { combinePaths } from '../common/pathUtils';
import { Uri } from '../common/uri/uri';
import { LanguageServerInterface, ServerSettings } from '../languageServerBase';
import { WellKnownWorkspaceKinds, Workspace, createInitStatus } from '../workspaceFactory';
@ -25,15 +25,15 @@ export interface CloneOptions {
export class AnalyzerServiceExecutor {
static runWithOptions(
languageServiceRootPath: string,
languageServiceRootUri: Uri,
workspace: Workspace,
serverSettings: ServerSettings,
typeStubTargetImportName?: string,
trackFiles = true
): void {
const commandLineOptions = getEffectiveCommandLineOptions(
languageServiceRootPath,
workspace.rootPath,
languageServiceRootUri.getFilePath(),
workspace.rootUri.getFilePath(),
serverSettings,
trackFiles,
typeStubTargetImportName,
@ -58,8 +58,7 @@ export class AnalyzerServiceExecutor {
const tempWorkspace: Workspace = {
...workspace,
workspaceName: `temp workspace for cloned service`,
rootPath: workspace.rootPath,
uri: workspace.uri,
rootUri: workspace.rootUri,
pythonPath: workspace.pythonPath,
pythonPathKind: workspace.pythonPathKind,
kinds: [...workspace.kinds, WellKnownWorkspaceKinds.Cloned],
@ -78,7 +77,7 @@ export class AnalyzerServiceExecutor {
const serverSettings = await ls.getSettings(workspace);
AnalyzerServiceExecutor.runWithOptions(
ls.rootPath,
ls.rootUri,
tempWorkspace,
serverSettings,
options.typeStubTargetImportName,
@ -120,21 +119,15 @@ function getEffectiveCommandLineOptions(
}
if (serverSettings.venvPath) {
commandLineOptions.venvPath = combinePaths(
workspaceRootPath || languageServiceRootPath,
serverSettings.venvPath
);
commandLineOptions.venvPath = serverSettings.venvPath.getFilePath();
}
if (serverSettings.pythonPath) {
// The Python VS Code extension treats the value "python" specially. This means
// the local python interpreter should be used rather than interpreting the
// setting value as a path to the interpreter. We'll simply ignore it in this case.
if (!isPythonBinary(serverSettings.pythonPath)) {
commandLineOptions.pythonPath = combinePaths(
workspaceRootPath || languageServiceRootPath,
serverSettings.pythonPath
);
if (!isPythonBinary(serverSettings.pythonPath.getFilePath())) {
commandLineOptions.pythonPath = serverSettings.pythonPath.getFilePath();
}
}
@ -142,11 +135,11 @@ function getEffectiveCommandLineOptions(
// Pyright supports only one typeshed path currently, whereas the
// official VS Code Python extension supports multiple typeshed paths.
// We'll use the first one specified and ignore the rest.
commandLineOptions.typeshedPath = serverSettings.typeshedPath;
commandLineOptions.typeshedPath = serverSettings.typeshedPath.getFilePath();
}
if (serverSettings.stubPath) {
commandLineOptions.stubPath = serverSettings.stubPath;
commandLineOptions.stubPath = serverSettings.stubPath.getFilePath();
}
if (serverSettings.logLevel === LogLevel.Log) {
@ -160,7 +153,7 @@ function getEffectiveCommandLineOptions(
}
commandLineOptions.autoSearchPaths = serverSettings.autoSearchPaths;
commandLineOptions.extraPaths = serverSettings.extraPaths;
commandLineOptions.extraPaths = serverSettings.extraPaths?.map((e) => e.getFilePath()) ?? [];
commandLineOptions.diagnosticSeverityOverrides = serverSettings.diagnosticSeverityOverrides;
commandLineOptions.includeFileSpecs = serverSettings.includeFileSpecs ?? [];

View File

@ -12,15 +12,15 @@ import { DeclarationType } from '../analyzer/declaration';
import { ImportResolver, ModuleNameAndType } from '../analyzer/importResolver';
import { ImportType } from '../analyzer/importResult';
import {
ImportGroup,
ImportNameInfo,
ImportStatements,
ModuleNameInfo,
getImportGroup,
getImportGroupFromModuleNameAndType,
getTextEditsForAutoImportInsertion,
getTextEditsForAutoImportSymbolAddition,
getTopLevelImports,
ImportGroup,
ImportNameInfo,
ImportStatements,
ModuleNameInfo,
} from '../analyzer/importStatementUtils';
import { isUserCode } from '../analyzer/sourceFileInfoUtils';
import { Symbol } from '../analyzer/symbol';
@ -30,13 +30,14 @@ import { throwIfCancellationRequested } from '../common/cancellationUtils';
import { appendArray } from '../common/collectionUtils';
import { ExecutionEnvironment } from '../common/configOptions';
import { TextEditAction } from '../common/editAction';
import { combinePaths, getDirectoryPath, getFileName, stripFileExtension } from '../common/pathUtils';
import { SourceFileInfo } from '../common/extensibility';
import { stripFileExtension } from '../common/pathUtils';
import * as StringUtils from '../common/stringUtils';
import { Position } from '../common/textRange';
import { Uri } from '../common/uri/uri';
import { ParseNodeType } from '../parser/parseNodes';
import { ParseResults } from '../parser/parser';
import { CompletionMap } from './completionProvider';
import { SourceFileInfo } from '../common/extensibility';
import { IndexAliasData } from './symbolIndexer';
export interface AutoImportSymbol {
@ -47,6 +48,7 @@ export interface AutoImportSymbol {
}
export interface ModuleSymbolTable {
uri: Uri;
forEach(callbackfn: (symbol: AutoImportSymbol, name: string, library: boolean) => void): void;
}
@ -54,8 +56,8 @@ export type ModuleSymbolMap = Map<string, ModuleSymbolTable>;
export interface AutoImportResult {
readonly name: string;
readonly declPath: string;
readonly originalDeclPath: string;
readonly declUri: Uri;
readonly originalDeclUri: Uri;
readonly insertionText: string;
readonly symbol?: Symbol;
readonly source?: string;
@ -74,7 +76,7 @@ export interface ImportParts {
readonly importName: string;
readonly symbolName?: string;
readonly importFrom?: string;
readonly filePath: string;
readonly fileUri: Uri;
readonly dotCount: number;
readonly moduleNameAndType: ModuleNameAndType;
}
@ -85,7 +87,7 @@ export interface ImportAliasData {
readonly symbol?: Symbol;
readonly kind?: SymbolKind;
readonly itemKind?: CompletionItemKind;
readonly filePath: string;
readonly fileUri: Uri;
}
export type AutoImportResultMap = Map<string, AutoImportResult[]>;
@ -106,13 +108,13 @@ export function addModuleSymbolsMap(files: readonly SourceFileInfo[], moduleSymb
return;
}
const filePath = file.sourceFile.getFilePath();
const uri = file.sourceFile.getUri();
const symbolTable = file.sourceFile.getModuleSymbolTable();
if (!symbolTable) {
return;
}
const fileName = stripFileExtension(getFileName(filePath));
const fileName = stripFileExtension(uri.fileName);
// Don't offer imports from files that are named with private
// naming semantics like "_ast.py".
@ -120,7 +122,8 @@ export function addModuleSymbolsMap(files: readonly SourceFileInfo[], moduleSymb
return;
}
moduleSymbolMap.set(filePath, {
moduleSymbolMap.set(uri.key, {
uri,
forEach(callbackfn: (value: AutoImportSymbol, key: string, library: boolean) => void): void {
symbolTable.forEach((symbol, name) => {
if (!isVisibleExternally(symbol)) {
@ -206,12 +209,12 @@ export class AutoImporter {
results: AutoImportResultMap,
token: CancellationToken
) {
this.moduleSymbolMap.forEach((topLevelSymbols, filePath) => {
this.moduleSymbolMap.forEach((topLevelSymbols, key) => {
// See if this file should be offered as an implicit import.
const isStubFileOrHasInit = this.isStubFileOrHasInit(this.moduleSymbolMap!, filePath);
const isStubFileOrHasInit = this.isStubFileOrHasInit(this.moduleSymbolMap!, topLevelSymbols.uri);
this.processModuleSymbolTable(
topLevelSymbols,
filePath,
topLevelSymbols.uri,
word,
similarityLimit,
isStubFileOrHasInit,
@ -244,7 +247,7 @@ export class AutoImporter {
// If import statement for the module already exist, then bail out.
// ex) import module[.submodule] or from module[.submodule] import symbol
if (this._importStatements.mapByFilePath.has(importAliasData.importParts.filePath)) {
if (this._importStatements.mapByFilePath.has(importAliasData.importParts.fileUri.key)) {
return;
}
@ -281,7 +284,7 @@ export class AutoImporter {
},
importAliasData.importParts.importName,
importAliasData.importGroup,
importAliasData.importParts.filePath
importAliasData.importParts.fileUri
);
this._addResult(results, {
@ -292,8 +295,8 @@ export class AutoImporter {
source: importAliasData.importParts.importFrom,
insertionText: autoImportTextEdits.insertionText,
edits: autoImportTextEdits.edits,
declPath: importAliasData.importParts.filePath,
originalDeclPath: importAliasData.filePath,
declUri: importAliasData.importParts.fileUri,
originalDeclUri: importAliasData.fileUri,
});
});
});
@ -301,7 +304,7 @@ export class AutoImporter {
protected processModuleSymbolTable(
topLevelSymbols: ModuleSymbolTable,
moduleFilePath: string,
moduleUri: Uri,
word: string,
similarityLimit: number,
isStubOrHasInit: { isStub: boolean; hasInit: boolean },
@ -312,7 +315,7 @@ export class AutoImporter {
) {
throwIfCancellationRequested(token);
const [importSource, importGroup, moduleNameAndType] = this._getImportPartsForSymbols(moduleFilePath);
const [importSource, importGroup, moduleNameAndType] = this._getImportPartsForSymbols(moduleUri);
if (!importSource) {
return;
}
@ -345,7 +348,7 @@ export class AutoImporter {
symbolName: name,
importName: name,
importFrom: importSource,
filePath: moduleFilePath,
fileUri: moduleUri,
dotCount,
moduleNameAndType,
},
@ -353,20 +356,20 @@ export class AutoImporter {
symbol: autoImportSymbol.symbol,
kind: autoImportSymbol.importAlias.kind,
itemKind: autoImportSymbol.importAlias.itemKind,
filePath: autoImportSymbol.importAlias.modulePath,
fileUri: autoImportSymbol.importAlias.moduleUri,
},
importAliasMap
);
return;
}
const nameForImportFrom = this.getNameForImportFrom(library, moduleFilePath);
const nameForImportFrom = this.getNameForImportFrom(library, moduleUri);
const autoImportTextEdits = this._getTextEditsForAutoImportByFilePath(
{ name, alias: abbrFromUsers },
{ name: importSource, nameForImportFrom },
name,
importGroup,
moduleFilePath
moduleUri
);
this._addResult(results, {
@ -377,8 +380,8 @@ export class AutoImporter {
kind: autoImportSymbol.itemKind ?? convertSymbolKindToCompletionItemKind(autoImportSymbol.kind),
insertionText: autoImportTextEdits.insertionText,
edits: autoImportTextEdits.edits,
declPath: moduleFilePath,
originalDeclPath: moduleFilePath,
declUri: moduleUri,
originalDeclUri: moduleUri,
});
});
@ -389,7 +392,7 @@ export class AutoImporter {
return;
}
const importParts = this._getImportParts(moduleFilePath);
const importParts = this._getImportParts(moduleUri);
if (!importParts) {
return;
}
@ -406,7 +409,7 @@ export class AutoImporter {
this._addToImportAliasMap(
{
modulePath: moduleFilePath,
moduleUri,
originalName: importParts.importName,
kind: SymbolKind.Module,
itemKind: CompletionItemKind.Module,
@ -416,22 +419,22 @@ export class AutoImporter {
importGroup,
kind: SymbolKind.Module,
itemKind: CompletionItemKind.Module,
filePath: moduleFilePath,
fileUri: moduleUri,
},
importAliasMap
);
}
protected getNameForImportFrom(library: boolean, moduleFilePath: string): string | undefined {
protected getNameForImportFrom(library: boolean, moduleUri: Uri): string | undefined {
return undefined;
}
protected isStubFileOrHasInit<T>(map: Map<string, T>, filePath: string) {
const fileDir = getDirectoryPath(filePath);
const initPathPy = combinePaths(fileDir, '__init__.py');
const initPathPyi = initPathPy + 'i';
const isStub = filePath.endsWith('.pyi');
const hasInit = map.has(initPathPy) || map.has(initPathPyi);
protected isStubFileOrHasInit<T>(map: Map<string, T>, uri: Uri) {
const fileDir = uri.getDirectory();
const initPathPy = fileDir.combinePaths('__init__.py');
const initPathPyi = initPathPy.addPath('i');
const isStub = uri.pathEndsWith('.pyi');
const hasInit = map.has(initPathPy.key) || map.has(initPathPyi.key);
return { isStub, hasInit };
}
@ -462,14 +465,14 @@ export class AutoImporter {
// Since we don't resolve alias declaration using type evaluator, there is still a chance
// where we show multiple aliases for same symbols. but this should still reduce number of
// such cases.
if (!importAliasMap.has(alias.modulePath)) {
if (!importAliasMap.has(alias.moduleUri.key)) {
const map = new Map<string, ImportAliasData>();
map.set(alias.originalName, data);
importAliasMap.set(alias.modulePath, map);
importAliasMap.set(alias.moduleUri.key, map);
return;
}
const map = importAliasMap.get(alias.modulePath)!;
const map = importAliasMap.get(alias.moduleUri.key)!;
if (!map.has(alias.originalName)) {
map.set(alias.originalName, data);
return;
@ -508,8 +511,8 @@ export class AutoImporter {
return StringUtils.getStringComparer()(left.importParts.importName, right.importParts.importName);
}
private _getImportPartsForSymbols(filePath: string): [string | undefined, ImportGroup, ModuleNameAndType] {
const localImport = this._importStatements.mapByFilePath.get(filePath);
private _getImportPartsForSymbols(uri: Uri): [string | undefined, ImportGroup, ModuleNameAndType] {
const localImport = this._importStatements.mapByFilePath.get(uri.key);
if (localImport) {
return [
localImport.moduleName,
@ -521,7 +524,7 @@ export class AutoImporter {
},
];
} else {
const moduleNameAndType = this._getModuleNameAndTypeFromFilePath(filePath);
const moduleNameAndType = this._getModuleNameAndTypeFromFilePath(uri);
return [
moduleNameAndType.moduleName,
getImportGroupFromModuleNameAndType(moduleNameAndType),
@ -530,15 +533,15 @@ export class AutoImporter {
}
}
private _getImportParts(filePath: string) {
const name = stripFileExtension(getFileName(filePath));
private _getImportParts(uri: Uri) {
const name = stripFileExtension(uri.fileName);
// See if we can import module as "import xxx"
if (name === '__init__') {
return createImportParts(this._getModuleNameAndTypeFromFilePath(getDirectoryPath(filePath)));
return createImportParts(this._getModuleNameAndTypeFromFilePath(uri.getDirectory()));
}
return createImportParts(this._getModuleNameAndTypeFromFilePath(filePath));
return createImportParts(this._getModuleNameAndTypeFromFilePath(uri));
function createImportParts(module: ModuleNameAndType): ImportParts | undefined {
const moduleName = module.moduleName;
@ -553,7 +556,7 @@ export class AutoImporter {
symbolName: importNamePart,
importName: importNamePart ?? moduleName,
importFrom,
filePath,
fileUri: uri,
dotCount: StringUtils.getCharacterCount(moduleName, '.'),
moduleNameAndType: module,
};
@ -601,8 +604,8 @@ export class AutoImporter {
// Given the file path of a module that we want to import,
// convert to a module name that can be used in an
// 'import from' statement.
private _getModuleNameAndTypeFromFilePath(filePath: string): ModuleNameAndType {
return this.importResolver.getModuleNameForImport(filePath, this.execEnvironment);
private _getModuleNameAndTypeFromFilePath(uri: Uri): ModuleNameAndType {
return this.importResolver.getModuleNameForImport(uri, this.execEnvironment);
}
private _getTextEditsForAutoImportByFilePath(
@ -610,10 +613,10 @@ export class AutoImporter {
moduleNameInfo: ModuleNameInfo,
insertionText: string,
importGroup: ImportGroup,
filePath: string
fileUri: Uri
): { insertionText: string; edits?: TextEditAction[] | undefined } {
// If there is no symbolName, there can't be existing import statement.
const importStatement = this._importStatements.mapByFilePath.get(filePath);
const importStatement = this._importStatements.mapByFilePath.get(fileUri.key);
if (importStatement) {
// Found import for given module. See whether we can use the module as it is.
if (importStatement.node.nodeType === ParseNodeType.Import) {
@ -699,7 +702,7 @@ export class AutoImporter {
}
// Check whether it is one of implicit imports
const importFrom = this._importStatements.implicitImports?.get(filePath);
const importFrom = this._importStatements.implicitImports?.get(fileUri.key);
if (importFrom) {
// For now, we don't check whether alias or moduleName got overwritten at
// given position

View File

@ -29,10 +29,10 @@ import { appendArray } from '../common/collectionUtils';
import { isDefined } from '../common/core';
import { ProgramView, ReferenceUseCase, SymbolUsageProvider } from '../common/extensibility';
import { getSymbolKind } from '../common/lspUtils';
import { convertPathToUri, getFileName } from '../common/pathUtils';
import { convertOffsetsToRange } from '../common/positionUtils';
import { ServiceKeys } from '../common/serviceProviderExtensions';
import { Position, rangesAreEqual } from '../common/textRange';
import { Uri } from '../common/uri/uri';
import { ReferencesProvider, ReferencesResult } from '../languageService/referencesProvider';
import { CallNode, MemberAccessNode, NameNode, ParseNode, ParseNodeType } from '../parser/parseNodes';
import { ParseResults } from '../parser/parser';
@ -44,11 +44,11 @@ export class CallHierarchyProvider {
constructor(
private _program: ProgramView,
private _filePath: string,
private _fileUri: Uri,
private _position: Position,
private _token: CancellationToken
) {
this._parseResults = this._program.getParseResults(this._filePath);
this._parseResults = this._program.getParseResults(this._fileUri);
}
onPrepare(): CallHierarchyItem[] | null {
@ -86,18 +86,15 @@ export class CallHierarchyProvider {
const callItem: CallHierarchyItem = {
name: symbolName,
kind: getSymbolKind(targetDecl, this._evaluator, symbolName) ?? SymbolKind.Module,
uri: callItemUri,
uri: callItemUri.toString(),
range: targetDecl.range,
selectionRange: targetDecl.range,
};
if (!canNavigateToFile(this._program.fileSystem, callItem.uri)) {
if (!canNavigateToFile(this._program.fileSystem, Uri.parse(callItem.uri, this._fileUri.isCaseSensitive))) {
return null;
}
// Convert the file path in the item to proper URI.
callItem.uri = convertPathToUri(this._program.fileSystem, callItem.uri);
return [callItem];
}
@ -117,11 +114,11 @@ export class CallHierarchyProvider {
const items: CallHierarchyIncomingCall[] = [];
const sourceFiles =
targetDecl.type === DeclarationType.Alias
? [this._program.getSourceFileInfo(this._filePath)!]
? [this._program.getSourceFileInfo(this._fileUri)!]
: this._program.getSourceFileInfoList();
for (const curSourceFileInfo of sourceFiles) {
if (isUserCode(curSourceFileInfo) || curSourceFileInfo.isOpenByClient) {
const filePath = curSourceFileInfo.sourceFile.getFilePath();
const filePath = curSourceFileInfo.sourceFile.getUri();
const itemsToAdd = this._getIncomingCallsForDeclaration(filePath, symbolName, targetDecl);
if (itemsToAdd) {
@ -138,14 +135,9 @@ export class CallHierarchyProvider {
return null;
}
const callItems = items.filter((item) => canNavigateToFile(this._program.fileSystem, item.from.uri));
// Convert the file paths in the items to proper URIs.
callItems.forEach((item) => {
item.from.uri = convertPathToUri(this._program.fileSystem, item.from.uri);
});
return callItems;
return items.filter((item) =>
canNavigateToFile(this._program.fileSystem, Uri.parse(item.from.uri, this._fileUri.isCaseSensitive))
);
}
getOutgoingCalls(): CallHierarchyOutgoingCall[] | null {
@ -209,14 +201,9 @@ export class CallHierarchyProvider {
return null;
}
const callItems = outgoingCalls.filter((item) => canNavigateToFile(this._program.fileSystem, item.to.uri));
// Convert the file paths in the items to proper URIs.
callItems.forEach((item) => {
item.to.uri = convertPathToUri(this._program.fileSystem, item.to.uri);
});
return callItems;
return outgoingCalls.filter((item) =>
canNavigateToFile(this._program.fileSystem, Uri.parse(item.to.uri, this._fileUri.isCaseSensitive))
);
}
private get _evaluator(): TypeEvaluator {
@ -225,7 +212,7 @@ export class CallHierarchyProvider {
private _getTargetDeclaration(referencesResult: ReferencesResult): {
targetDecl: Declaration;
callItemUri: string;
callItemUri: Uri;
symbolName: string;
} {
// If there's more than one declaration, pick the target one.
@ -253,32 +240,26 @@ export class CallHierarchyProvider {
// Although the LSP specification requires a URI, we are using a file path
// here because it is converted to the proper URI by the caller.
// This simplifies our code and ensures compatibility with the LSP specification.
let callItemUri;
let callItemUri: Uri;
if (targetDecl.type === DeclarationType.Alias) {
symbolName = (referencesResult.nodeAtOffset as NameNode).value;
callItemUri = this._filePath;
callItemUri = this._fileUri;
} else {
symbolName = DeclarationUtils.getNameFromDeclaration(targetDecl) || referencesResult.symbolNames[0];
callItemUri = targetDecl.path;
callItemUri = targetDecl.uri;
}
return { targetDecl, callItemUri, symbolName };
}
private _getIncomingCallsForDeclaration(
filePath: string,
fileUri: Uri,
symbolName: string,
declaration: Declaration
): CallHierarchyIncomingCall[] | undefined {
throwIfCancellationRequested(this._token);
const callFinder = new FindIncomingCallTreeWalker(
this._program,
filePath,
symbolName,
declaration,
this._token
);
const callFinder = new FindIncomingCallTreeWalker(this._program, fileUri, symbolName, declaration, this._token);
const incomingCalls = callFinder.findCalls();
return incomingCalls.length > 0 ? incomingCalls : undefined;
@ -287,7 +268,7 @@ export class CallHierarchyProvider {
private _getDeclaration(): ReferencesResult | undefined {
return ReferencesProvider.getDeclarationForPosition(
this._program,
this._filePath,
this._fileUri,
this._position,
/* reporter */ undefined,
ReferenceUseCase.References,
@ -394,7 +375,7 @@ class FindOutgoingCallTreeWalker extends ParseTreeWalker {
const callDest: CallHierarchyItem = {
name: nameNode.value,
kind: getSymbolKind(resolvedDecl, this._evaluator, nameNode.value) ?? SymbolKind.Module,
uri: resolvedDecl.path,
uri: resolvedDecl.uri.toString(),
range: resolvedDecl.range,
selectionRange: resolvedDecl.range,
};
@ -437,14 +418,14 @@ class FindIncomingCallTreeWalker extends ParseTreeWalker {
constructor(
private readonly _program: ProgramView,
private readonly _filePath: string,
private readonly _fileUri: Uri,
private readonly _symbolName: string,
private readonly _targetDeclaration: Declaration,
private readonly _cancellationToken: CancellationToken
) {
super();
this._parseResults = this._program.getParseResults(this._filePath)!;
this._parseResults = this._program.getParseResults(this._fileUri)!;
this._usageProviders = (this._program.serviceProvider.tryGet(ServiceKeys.symbolUsageProviderFactory) ?? [])
.map((f) =>
f.tryCreateProvider(ReferenceUseCase.References, [this._targetDeclaration], this._cancellationToken)
@ -570,12 +551,12 @@ class FindIncomingCallTreeWalker extends ParseTreeWalker {
let callSource: CallHierarchyItem;
if (executionNode.nodeType === ParseNodeType.Module) {
const moduleRange = convertOffsetsToRange(0, 0, this._parseResults.tokenizerOutput.lines);
const fileName = getFileName(this._filePath);
const fileName = this._fileUri.fileName;
callSource = {
name: `(module) ${fileName}`,
kind: SymbolKind.Module,
uri: this._filePath,
uri: this._fileUri.toString(),
range: moduleRange,
selectionRange: moduleRange,
};
@ -589,7 +570,7 @@ class FindIncomingCallTreeWalker extends ParseTreeWalker {
callSource = {
name: '(lambda)',
kind: SymbolKind.Function,
uri: this._filePath,
uri: this._fileUri.toString(),
range: lambdaRange,
selectionRange: lambdaRange,
};
@ -603,7 +584,7 @@ class FindIncomingCallTreeWalker extends ParseTreeWalker {
callSource = {
name: executionNode.name.value,
kind: SymbolKind.Function,
uri: this._filePath,
uri: this._fileUri.toString(),
range: functionRange,
selectionRange: functionRange,
};

View File

@ -12,8 +12,8 @@ import { Commands } from '../commands/commands';
import { throwIfCancellationRequested } from '../common/cancellationUtils';
import { ActionKind, CreateTypeStubFileAction, RenameShadowedFileAction } from '../common/diagnostic';
import { FileEditActions } from '../common/editAction';
import { getShortenedFileName } from '../common/pathUtils';
import { Range } from '../common/textRange';
import { Uri } from '../common/uri/uri';
import { convertToWorkspaceEdit } from '../common/workspaceEditUtils';
import { Localizer } from '../localization/localize';
import { Workspace } from '../workspaceFactory';
@ -29,7 +29,7 @@ export class CodeActionProvider {
}
static async getCodeActionsForPosition(
workspace: Workspace,
filePath: string,
fileUri: Uri,
range: Range,
kinds: CodeActionKind[] | undefined,
token: CancellationToken
@ -44,7 +44,7 @@ export class CodeActionProvider {
}
if (!workspace.disableLanguageServices) {
const diags = await workspace.service.getDiagnosticsForRange(filePath, range, token);
const diags = await workspace.service.getDiagnosticsForRange(fileUri, range, token);
const typeStubDiag = diags.find((d) => {
const actions = d.getActions();
return actions && actions.find((a) => a.action === Commands.createTypeStub);
@ -60,9 +60,9 @@ export class CodeActionProvider {
Command.create(
Localizer.CodeAction.createTypeStub(),
Commands.createTypeStub,
workspace.rootPath,
workspace.rootUri.toString(),
action.moduleName,
filePath
fileUri.toString()
),
CodeActionKind.QuickFix
);
@ -80,21 +80,20 @@ export class CodeActionProvider {
.find((a) => a.action === ActionKind.RenameShadowedFileAction) as RenameShadowedFileAction;
if (action) {
const title = Localizer.CodeAction.renameShadowedFile().format({
oldFile: getShortenedFileName(action.oldFile),
newFile: getShortenedFileName(action.newFile),
oldFile: action.oldUri.getShortenedFileName(),
newFile: action.newUri.getShortenedFileName(),
});
const fs = workspace.service.getImportResolver().fileSystem;
const editActions: FileEditActions = {
edits: [],
fileOperations: [
{
kind: 'rename',
oldFilePath: action.oldFile,
newFilePath: action.newFile,
oldFileUri: action.oldUri,
newFileUri: action.newUri,
},
],
};
const workspaceEdit = convertToWorkspaceEdit(fs, editActions);
const workspaceEdit = convertToWorkspaceEdit(editActions);
const renameAction = CodeAction.create(title, workspaceEdit, CodeActionKind.QuickFix);
codeActions.push(renameAction);
}

View File

@ -41,7 +41,7 @@ import { Symbol, SymbolTable } from '../analyzer/symbol';
import * as SymbolNameUtils from '../analyzer/symbolNameUtils';
import { getLastTypedDeclaredForSymbol, isVisibleExternally } from '../analyzer/symbolUtils';
import { getTypedDictMembersForClass } from '../analyzer/typedDicts';
import { getModuleDocStringFromPaths } from '../analyzer/typeDocStringUtils';
import { getModuleDocStringFromUris } from '../analyzer/typeDocStringUtils';
import { CallSignatureInfo, TypeEvaluator } from '../analyzer/typeEvaluatorTypes';
import { printLiteralValue } from '../analyzer/typePrinter';
import {
@ -83,6 +83,7 @@ import { PythonVersion } from '../common/pythonVersion';
import * as StringUtils from '../common/stringUtils';
import { comparePositions, Position, TextRange } from '../common/textRange';
import { TextRangeCollection } from '../common/textRangeCollection';
import { Uri } from '../common/uri/uri';
import { convertToTextEdits } from '../common/workspaceEditUtils';
import { Localizer } from '../localization/localize';
import {
@ -231,13 +232,13 @@ enum SortCategory {
// This data allows the resolve handling to disambiguate
// which item was selected.
export interface CompletionItemData {
filePath: string;
workspacePath: string;
uri: string; // Have to be strings because this data is passed across the LSP boundary.
workspaceUri: string;
position: Position;
autoImportText?: string;
symbolLabel?: string;
funcParensDisabled?: boolean;
modulePath?: string;
moduleUri?: string;
}
export interface CompletionOptions {
@ -287,20 +288,20 @@ export class CompletionProvider {
constructor(
protected readonly program: ProgramView,
private readonly _workspacePath: string,
protected readonly filePath: string,
private readonly _workspaceRootUri: Uri,
protected readonly fileUri: Uri,
protected readonly position: Position,
protected readonly options: CompletionOptions,
protected readonly cancellationToken: CancellationToken
) {
this.execEnv = this.configOptions.findExecEnvironment(this.filePath);
this.execEnv = this.configOptions.findExecEnvironment(this.fileUri);
this.parseResults = this.program.getParseResults(this.filePath)!;
this.sourceMapper = this.program.getSourceMapper(this.filePath, this.cancellationToken, /* mapCompiled */ true);
this.parseResults = this.program.getParseResults(this.fileUri)!;
this.sourceMapper = this.program.getSourceMapper(this.fileUri, this.cancellationToken, /* mapCompiled */ true);
}
getCompletions(): CompletionList | null {
if (!this.program.getSourceFileInfo(this.filePath)) {
if (!this.program.getSourceFileInfo(this.fileUri)) {
return null;
}
@ -347,10 +348,15 @@ export class CompletionProvider {
}
if (
completionItemData.modulePath &&
ImportResolver.isSupportedImportSourceFile(completionItemData.modulePath)
completionItemData.moduleUri &&
ImportResolver.isSupportedImportSourceFile(
Uri.parse(completionItemData.moduleUri, this.importResolver.fileSystem.isCaseSensitive)
)
) {
const documentation = getModuleDocStringFromPaths([completionItemData.modulePath], this.sourceMapper);
const documentation = getModuleDocStringFromUris(
[Uri.parse(completionItemData.moduleUri, this.importResolver.fileSystem.isCaseSensitive)],
this.sourceMapper
);
if (!documentation) {
return;
}
@ -499,7 +505,7 @@ export class CompletionProvider {
const methodSignature = this._printMethodSignature(classResults.classType, decl);
let text: string;
if (isStubFile(this.filePath)) {
if (isStubFile(this.fileUri)) {
text = `${methodSignature}: ...`;
} else {
const methodBody = this.printOverriddenMethodBody(
@ -826,7 +832,7 @@ export class CompletionProvider {
return;
}
const currentFile = this.program.getSourceFileInfo(this.filePath);
const currentFile = this.program.getSourceFileInfo(this.fileUri);
const moduleSymbolMap = buildModuleSymbolsMap(
this.program.getSourceFileInfoList().filter((s) => s !== currentFile)
);
@ -923,8 +929,8 @@ export class CompletionProvider {
}
const completionItemData: CompletionItemData = {
workspacePath: this._workspacePath,
filePath: this.filePath,
workspaceUri: this._workspaceRootUri.toString(),
uri: this.fileUri.toString(),
position: this.position,
};
@ -932,8 +938,8 @@ export class CompletionProvider {
completionItemData.funcParensDisabled = true;
}
if (detail?.modulePath) {
completionItemData.modulePath = detail.modulePath;
if (detail?.moduleUri) {
completionItemData.moduleUri = detail.moduleUri.toString();
}
completionItem.data = toLSPAny(completionItemData);
@ -1689,7 +1695,7 @@ export class CompletionProvider {
return;
}
const printFlags = isStubFile(this.filePath)
const printFlags = isStubFile(this.fileUri)
? ParseTreeUtils.PrintExpressionFlags.ForwardDeclarations |
ParseTreeUtils.PrintExpressionFlags.DoNotLimitStringLength
: ParseTreeUtils.PrintExpressionFlags.DoNotLimitStringLength;
@ -1822,7 +1828,7 @@ export class CompletionProvider {
const node = decl.node;
let ellipsisForDefault: boolean | undefined;
if (isStubFile(this.filePath)) {
if (isStubFile(this.fileUri)) {
// In stubs, always use "...".
ellipsisForDefault = true;
} else if (classType.details.moduleName === decl.moduleName) {
@ -1830,7 +1836,7 @@ export class CompletionProvider {
ellipsisForDefault = false;
}
const printFlags = isStubFile(this.filePath)
const printFlags = isStubFile(this.fileUri)
? ParseTreeUtils.PrintExpressionFlags.ForwardDeclarations |
ParseTreeUtils.PrintExpressionFlags.DoNotLimitStringLength
: ParseTreeUtils.PrintExpressionFlags.DoNotLimitStringLength;
@ -2190,7 +2196,7 @@ export class CompletionProvider {
return [];
}
if (declaration.path !== this.filePath) {
if (!declaration.uri.equals(this.fileUri)) {
return [];
}
@ -2200,8 +2206,8 @@ export class CompletionProvider {
// Find the lowest tree to search the symbol.
if (
ParseTreeUtils.getFileInfoFromNode(startingNode)?.filePath ===
ParseTreeUtils.getFileInfoFromNode(scopeRoot)?.filePath
ParseTreeUtils.getFileInfoFromNode(startingNode)?.fileUri ===
ParseTreeUtils.getFileInfoFromNode(scopeRoot)?.fileUri
) {
startingNode = scopeRoot;
}
@ -2731,7 +2737,9 @@ export class CompletionProvider {
const completionMap = new CompletionMap();
const resolvedPath =
importInfo.resolvedPaths.length > 0 ? importInfo.resolvedPaths[importInfo.resolvedPaths.length - 1] : '';
importInfo.resolvedUris.length > 0
? importInfo.resolvedUris[importInfo.resolvedUris.length - 1]
: Uri.empty();
const parseResults = this.program.getParseResults(resolvedPath);
if (!parseResults) {
@ -2775,7 +2783,7 @@ export class CompletionProvider {
importInfo.implicitImports.forEach((implImport) => {
if (!importFromNode.imports.find((imp) => imp.name.value === implImport.name)) {
this.addNameToCompletions(implImport.name, CompletionItemKind.Module, priorWord, completionMap, {
modulePath: implImport.path,
moduleUri: implImport.uri,
});
}
});
@ -2817,8 +2825,8 @@ export class CompletionProvider {
completionItem.kind = CompletionItemKind.Variable;
const completionItemData: CompletionItemData = {
workspacePath: this._workspacePath,
filePath: this.filePath,
workspaceUri: this._workspaceRootUri.toString(),
uri: this.fileUri.toString(),
position: this.position,
};
completionItem.data = toLSPAny(completionItemData);
@ -2914,8 +2922,7 @@ export class CompletionProvider {
// exported from this scope, don't include it in the
// suggestion list unless we are in the same file.
const hidden =
!isVisibleExternally(symbol) &&
!symbol.getDeclarations().some((d) => isDefinedInFile(d, this.filePath));
!isVisibleExternally(symbol) && !symbol.getDeclarations().some((d) => isDefinedInFile(d, this.fileUri));
if (!hidden && includeSymbolCallback(symbol, name)) {
// Don't add a symbol more than once. It may have already been
// added from an inner scope's symbol table.
@ -3098,7 +3105,7 @@ export class CompletionProvider {
importedSymbols: new Set<string>(),
};
const completions = this.importResolver.getCompletionSuggestions(this.filePath, this.execEnv, moduleDescriptor);
const completions = this.importResolver.getCompletionSuggestions(this.fileUri, this.execEnv, moduleDescriptor);
const completionMap = new CompletionMap();
@ -3120,7 +3127,7 @@ export class CompletionProvider {
completions.forEach((modulePath, completionName) => {
this.addNameToCompletions(completionName, CompletionItemKind.Module, '', completionMap, {
sortText: this._makeSortText(SortCategory.ImportModuleName, completionName),
modulePath,
moduleUri: modulePath,
});
});

View File

@ -11,21 +11,22 @@ import { InsertTextFormat, MarkupContent, MarkupKind, TextEdit } from 'vscode-la
import { Declaration, DeclarationType } from '../analyzer/declaration';
import { convertDocStringToMarkdown, convertDocStringToPlainText } from '../analyzer/docStringConversion';
import { TypeEvaluator } from '../analyzer/typeEvaluatorTypes';
import { isProperty } from '../analyzer/typeUtils';
import {
ClassType,
Type,
TypeBase,
TypeCategory,
UnknownType,
getTypeAliasInfo,
isClassInstance,
isFunction,
isModule,
isOverloadedFunction,
Type,
TypeBase,
TypeCategory,
UnknownType,
} from '../analyzer/types';
import { isProperty } from '../analyzer/typeUtils';
import { SignatureDisplayType } from '../common/configOptions';
import { TextEditAction } from '../common/editAction';
import { Uri } from '../common/uri/uri';
import { getToolTipForType } from './tooltipUtils';
export interface Edits {
@ -55,7 +56,7 @@ export interface CompletionDetail extends CommonDetail {
};
sortText?: string;
itemDetail?: string;
modulePath?: string;
moduleUri?: Uri;
}
export function getTypeDetail(

View File

@ -24,10 +24,11 @@ import { appendArray } from '../common/collectionUtils';
import { isDefined } from '../common/core';
import { ProgramView, ServiceProvider } from '../common/extensibility';
import { convertPositionToOffset } from '../common/positionUtils';
import { ServiceKeys } from '../common/serviceProviderExtensions';
import { DocumentRange, Position, rangesAreEqual } from '../common/textRange';
import { Uri } from '../common/uri/uri';
import { ParseNode, ParseNodeType } from '../parser/parseNodes';
import { ParseResults } from '../parser/parser';
import { ServiceKeys } from '../common/serviceProviderExtensions';
export enum DefinitionFilter {
All = 'all',
@ -50,7 +51,7 @@ export function addDeclarationsToDefinitions(
allowExternallyHiddenAccess: true,
});
if (!resolvedDecl || !resolvedDecl.path) {
if (!resolvedDecl || resolvedDecl.uri.isEmpty()) {
return;
}
@ -66,13 +67,13 @@ export function addDeclarationsToDefinitions(
resolvedDecl.type === DeclarationType.Alias &&
resolvedDecl.symbolName &&
resolvedDecl.submoduleFallback &&
resolvedDecl.submoduleFallback.path
!resolvedDecl.submoduleFallback.uri.isEmpty()
) {
resolvedDecl = resolvedDecl.submoduleFallback;
}
_addIfUnique(definitions, {
path: resolvedDecl.path,
uri: resolvedDecl.uri,
range: resolvedDecl.range,
});
@ -82,22 +83,22 @@ export function addDeclarationsToDefinitions(
if (functionType && isOverloadedFunction(functionType)) {
for (const overloadDecl of functionType.overloads.map((o) => o.details.declaration).filter(isDefined)) {
_addIfUnique(definitions, {
path: overloadDecl.path,
uri: overloadDecl.uri,
range: overloadDecl.range,
});
}
}
}
if (!isStubFile(resolvedDecl.path)) {
if (!isStubFile(resolvedDecl.uri)) {
return;
}
if (resolvedDecl.type === DeclarationType.Alias) {
// Add matching source module
sourceMapper
.findModules(resolvedDecl.path)
.map((m) => getFileInfo(m)?.filePath)
.findModules(resolvedDecl.uri)
.map((m) => getFileInfo(m)?.fileUri)
.filter(isDefined)
.forEach((f) => _addIfUnique(definitions, _createModuleEntry(f)));
return;
@ -105,9 +106,9 @@ export function addDeclarationsToDefinitions(
const implDecls = sourceMapper.findDeclarations(resolvedDecl);
for (const implDecl of implDecls) {
if (implDecl && implDecl.path) {
if (implDecl && !implDecl.uri.isEmpty()) {
_addIfUnique(definitions, {
path: implDecl.path,
uri: implDecl.uri,
range: implDecl.range,
});
}
@ -123,7 +124,7 @@ export function filterDefinitions(filter: DefinitionFilter, definitions: Documen
// If go-to-declaration is supported, attempt to only show only pyi files in go-to-declaration
// and none in go-to-definition, unless filtering would produce an empty list.
const preferStubs = filter === DefinitionFilter.PreferStubs;
const wantedFile = (v: DocumentRange) => preferStubs === isStubFile(v.path);
const wantedFile = (v: DocumentRange) => preferStubs === isStubFile(v.uri);
if (definitions.find(wantedFile)) {
return definitions.filter(wantedFile);
}
@ -181,13 +182,13 @@ class DefinitionProviderBase {
export class DefinitionProvider extends DefinitionProviderBase {
constructor(
program: ProgramView,
filePath: string,
fileUri: Uri,
position: Position,
filter: DefinitionFilter,
token: CancellationToken
) {
const sourceMapper = program.getSourceMapper(filePath, token);
const parseResults = program.getParseResults(filePath);
const sourceMapper = program.getSourceMapper(fileUri, token);
const parseResults = program.getParseResults(fileUri);
const { node, offset } = _tryGetNode(parseResults, position);
super(sourceMapper, program.evaluator!, program.serviceProvider, node, offset, filter, token);
@ -222,15 +223,15 @@ export class DefinitionProvider extends DefinitionProviderBase {
}
export class TypeDefinitionProvider extends DefinitionProviderBase {
private readonly _filePath: string;
private readonly _fileUri: Uri;
constructor(program: ProgramView, filePath: string, position: Position, token: CancellationToken) {
const sourceMapper = program.getSourceMapper(filePath, token, /*mapCompiled*/ false, /*preferStubs*/ true);
const parseResults = program.getParseResults(filePath);
constructor(program: ProgramView, fileUri: Uri, position: Position, token: CancellationToken) {
const sourceMapper = program.getSourceMapper(fileUri, token, /*mapCompiled*/ false, /*preferStubs*/ true);
const parseResults = program.getParseResults(fileUri);
const { node, offset } = _tryGetNode(parseResults, position);
super(sourceMapper, program.evaluator!, program.serviceProvider, node, offset, DefinitionFilter.All, token);
this._filePath = filePath;
this._fileUri = fileUri;
}
getDefinitions(): DocumentRange[] | undefined {
@ -251,7 +252,7 @@ export class TypeDefinitionProvider extends DefinitionProviderBase {
if (subtype?.category === TypeCategory.Class) {
appendArray(
declarations,
this.sourceMapper.findClassDeclarationsByType(this._filePath, subtype)
this.sourceMapper.findClassDeclarationsByType(this._fileUri, subtype)
);
}
});
@ -290,9 +291,9 @@ function _tryGetNode(parseResults: ParseResults | undefined, position: Position)
return { node: ParseTreeUtils.findNodeByOffset(parseResults.parseTree, offset), offset };
}
function _createModuleEntry(filePath: string): DocumentRange {
function _createModuleEntry(uri: Uri): DocumentRange {
return {
path: filePath,
uri,
range: {
start: { line: 0, character: 0 },
end: { line: 0, character: 0 },
@ -302,7 +303,7 @@ function _createModuleEntry(filePath: string): DocumentRange {
function _addIfUnique(definitions: DocumentRange[], itemToAdd: DocumentRange) {
for (const def of definitions) {
if (def.path === itemToAdd.path && rangesAreEqual(def.range, itemToAdd.range)) {
if (def.uri.equals(itemToAdd.uri) && rangesAreEqual(def.range, itemToAdd.range)) {
return;
}
}

View File

@ -15,6 +15,7 @@ import { throwIfCancellationRequested } from '../common/cancellationUtils';
import { ProgramView, ReferenceUseCase } from '../common/extensibility';
import { convertOffsetsToRange, convertPositionToOffset } from '../common/positionUtils';
import { Position, TextRange } from '../common/textRange';
import { Uri } from '../common/uri/uri';
import { ParseNodeType } from '../parser/parseNodes';
import { ParseResults } from '../parser/parser';
import { DocumentSymbolCollector } from './documentSymbolCollector';
@ -24,11 +25,11 @@ export class DocumentHighlightProvider {
constructor(
private _program: ProgramView,
private _filePath: string,
private _fileUri: Uri,
private _position: Position,
private _token: CancellationToken
) {
this._parseResults = this._program.getParseResults(this._filePath);
this._parseResults = this._program.getParseResults(this._fileUri);
}
getDocumentHighlight(): DocumentHighlight[] | undefined {

View File

@ -29,12 +29,12 @@ import { TypeEvaluator } from '../analyzer/typeEvaluatorTypes';
import { TypeCategory } from '../analyzer/types';
import { throwIfCancellationRequested } from '../common/cancellationUtils';
import { appendArray } from '../common/collectionUtils';
import { isDefined } from '../common/core';
import { assert } from '../common/debug';
import { ProgramView, ReferenceUseCase, SymbolUsageProvider } from '../common/extensibility';
import { ServiceKeys } from '../common/serviceProviderExtensions';
import { TextRange } from '../common/textRange';
import { ImportAsNode, NameNode, ParseNode, ParseNodeType, StringListNode, StringNode } from '../parser/parseNodes';
import { ServiceKeys } from '../common/serviceProviderExtensions';
import { isDefined } from '../common/core';
export type CollectionResult = {
node: NameNode | StringNode;
@ -184,17 +184,18 @@ export class DocumentSymbolCollector extends ParseTreeWalker {
const declarations = getDeclarationsForNameNode(evaluator, node, /* skipUnreachableCode */ false);
const fileInfo = AnalyzerNodeInfo.getFileInfo(node);
const fileUri = fileInfo.fileUri;
const resolvedDeclarations: Declaration[] = [];
const sourceMapper = program.getSourceMapper(fileInfo.filePath, token);
const sourceMapper = program.getSourceMapper(fileUri, token);
declarations.forEach((decl) => {
const resolvedDecl = evaluator.resolveAliasDeclaration(decl, resolveLocalName);
if (resolvedDecl) {
addDeclarationIfUnique(resolvedDeclarations, resolvedDecl);
if (sourceMapper && isStubFile(resolvedDecl.path)) {
if (sourceMapper && isStubFile(resolvedDecl.uri)) {
const implDecls = sourceMapper.findDeclarations(resolvedDecl);
for (const implDecl of implDecls) {
if (implDecl && implDecl.path) {
if (implDecl && !implDecl.uri.isEmpty()) {
addDeclarationIfUnique(resolvedDeclarations, implDecl);
}
}
@ -202,7 +203,7 @@ export class DocumentSymbolCollector extends ParseTreeWalker {
}
});
const sourceFileInfo = program.getSourceFileInfo(fileInfo.filePath);
const sourceFileInfo = program.getSourceFileInfo(fileUri);
if (sourceFileInfo && sourceFileInfo.sourceFile.getIPythonMode() === IPythonMode.CellDocs) {
// Add declarations from chained source files
let builtinsScope = fileInfo.builtinsScope;
@ -215,7 +216,7 @@ export class DocumentSymbolCollector extends ParseTreeWalker {
// Add declarations from files that implicitly import the target file.
const implicitlyImportedBy = collectImportedByCells(program, sourceFileInfo);
implicitlyImportedBy.forEach((implicitImport) => {
const parseTree = program.getParseResults(implicitImport.sourceFile.getFilePath())?.parseTree;
const parseTree = program.getParseResults(implicitImport.sourceFile.getUri())?.parseTree;
if (parseTree) {
const scope = AnalyzerNodeInfo.getScope(parseTree);
const symbol = scope?.lookUpSymbol(node.value);
@ -449,7 +450,7 @@ function _getDeclarationsForNonModuleNameNode(
const type = evaluator.getType(node);
if (type?.category === TypeCategory.Module) {
// Synthesize decl for the module.
return [createSynthesizedAliasDeclaration(type.filePath)];
return [createSynthesizedAliasDeclaration(type.fileUri)];
}
}

View File

@ -10,23 +10,22 @@
import { CancellationToken, DocumentSymbol, Location, SymbolInformation } from 'vscode-languageserver';
import { getFileInfo } from '../analyzer/analyzerNodeInfo';
import { throwIfCancellationRequested } from '../common/cancellationUtils';
import { ProgramView } from '../common/extensibility';
import { Uri } from '../common/uri/uri';
import { ParseResults } from '../parser/parser';
import { IndexSymbolData, SymbolIndexer } from './symbolIndexer';
import { ProgramView } from '../common/extensibility';
import { getFileInfo } from '../analyzer/analyzerNodeInfo';
import { convertPathToUri } from '../common/pathUtils';
export function convertToFlatSymbols(
program: ProgramView,
filePath: string,
uri: Uri,
symbolList: DocumentSymbol[]
): SymbolInformation[] {
const flatSymbols: SymbolInformation[] = [];
const documentUri = convertPathToUri(program.fileSystem, filePath);
for (const symbol of symbolList) {
_appendToFlatSymbolsRecursive(flatSymbols, documentUri, symbol);
_appendToFlatSymbolsRecursive(flatSymbols, uri, symbol);
}
return flatSymbols;
@ -37,11 +36,11 @@ export class DocumentSymbolProvider {
constructor(
protected readonly program: ProgramView,
protected readonly filePath: string,
protected readonly uri: Uri,
private readonly _supportHierarchicalDocumentSymbol: boolean,
private readonly _token: CancellationToken
) {
this._parseResults = this.program.getParseResults(this.filePath);
this._parseResults = this.program.getParseResults(this.uri);
}
getSymbols(): DocumentSymbol[] | SymbolInformation[] {
@ -54,12 +53,12 @@ export class DocumentSymbolProvider {
return symbolList;
}
return convertToFlatSymbols(this.program, this.filePath, symbolList);
return convertToFlatSymbols(this.program, this.uri, symbolList);
}
protected getHierarchicalSymbols() {
const symbolList: DocumentSymbol[] = [];
const parseResults = this.program.getParseResults(this.filePath);
const parseResults = this.program.getParseResults(this.uri);
if (!parseResults) {
return symbolList;
}
@ -115,14 +114,14 @@ export class DocumentSymbolProvider {
function _appendToFlatSymbolsRecursive(
flatSymbols: SymbolInformation[],
documentUri: string,
documentUri: Uri,
symbol: DocumentSymbol,
parent?: DocumentSymbol
) {
const flatSymbol: SymbolInformation = {
name: symbol.name,
kind: symbol.kind,
location: Location.create(documentUri, symbol.range),
location: Location.create(documentUri.toString(), symbol.range),
};
if (symbol.tags) {

View File

@ -30,10 +30,12 @@ import {
isTypeVar,
} from '../analyzer/types';
import { throwIfCancellationRequested } from '../common/cancellationUtils';
import { SignatureDisplayType } from '../common/configOptions';
import { assertNever, fail } from '../common/debug';
import { ProgramView } from '../common/extensibility';
import { convertOffsetToPosition, convertPositionToOffset } from '../common/positionUtils';
import { Position, Range, TextRange } from '../common/textRange';
import { Uri } from '../common/uri/uri';
import { ExpressionNode, NameNode, ParseNode, ParseNodeType, StringNode } from '../parser/parseNodes';
import { ParseResults } from '../parser/parser';
import {
@ -43,7 +45,6 @@ import {
getToolTipForType,
getTypeForToolTip,
} from './tooltipUtils';
import { SignatureDisplayType } from '../common/configOptions';
export interface HoverTextPart {
python?: boolean;
@ -151,13 +152,13 @@ export class HoverProvider {
constructor(
private readonly _program: ProgramView,
private readonly _filePath: string,
private readonly _fileUri: Uri,
private readonly _position: Position,
private readonly _format: MarkupKind,
private readonly _token: CancellationToken
) {
this._parseResults = this._program.getParseResults(this._filePath);
this._sourceMapper = this._program.getSourceMapper(this._filePath, this._token, /* mapCompiled */ true);
this._parseResults = this._program.getParseResults(this._fileUri);
this._sourceMapper = this._program.getSourceMapper(this._fileUri, this._token, /* mapCompiled */ true);
}
getHover(): Hover | null {

View File

@ -7,10 +7,10 @@
*/
import { Location } from 'vscode-languageserver-types';
import { ReadOnlyFileSystem } from '../common/fileSystem';
import { convertPathToUri } from '../common/pathUtils';
import { DocumentRange } from '../common/textRange';
import { Uri } from '../common/uri/uri';
export function canNavigateToFile(fs: ReadOnlyFileSystem, path: string): boolean {
export function canNavigateToFile(fs: ReadOnlyFileSystem, path: Uri): boolean {
return !fs.isInZip(path);
}
@ -23,9 +23,9 @@ export function convertDocumentRangesToLocation(
}
export function convertDocumentRangeToLocation(fs: ReadOnlyFileSystem, range: DocumentRange): Location | undefined {
if (!canNavigateToFile(fs, range.path)) {
if (!canNavigateToFile(fs, range.uri)) {
return undefined;
}
return Location.create(convertPathToUri(fs, range.path), range.range);
return Location.create(range.uri.toString(), range.range);
}

View File

@ -11,16 +11,17 @@ import { CancellationToken } from 'vscode-languageserver';
import { Commands } from '../commands/commands';
import { ProgramView } from '../common/extensibility';
import { Uri } from '../common/uri/uri';
import { ImportSorter } from './importSorter';
export function performQuickAction(
programView: ProgramView,
filePath: string,
uri: Uri,
command: string,
args: any[],
token: CancellationToken
) {
const sourceFileInfo = programView.getSourceFileInfo(filePath);
const sourceFileInfo = programView.getSourceFileInfo(uri);
// This command should be called only for open files, in which
// case we should have the file contents already loaded.
@ -29,7 +30,7 @@ export function performQuickAction(
}
// If we have no completed analysis job, there's nothing to do.
const parseResults = programView.getParseResults(filePath);
const parseResults = programView.getParseResults(uri);
if (!parseResults) {
return [];
}

View File

@ -19,18 +19,19 @@ import { isVisibleExternally } from '../analyzer/symbolUtils';
import { TypeEvaluator } from '../analyzer/typeEvaluatorTypes';
import { maxTypeRecursionCount } from '../analyzer/types';
import { throwIfCancellationRequested } from '../common/cancellationUtils';
import { isDefined } from '../common/core';
import { appendArray } from '../common/collectionUtils';
import { isDefined } from '../common/core';
import { assertNever } from '../common/debug';
import { ProgramView, ReferenceUseCase, SymbolUsageProvider } from '../common/extensibility';
import { ReadOnlyFileSystem } from '../common/fileSystem';
import { convertOffsetToPosition, convertPositionToOffset } from '../common/positionUtils';
import { ServiceKeys } from '../common/serviceProviderExtensions';
import { DocumentRange, Position, TextRange, doesRangeContain } from '../common/textRange';
import { Uri } from '../common/uri/uri';
import { NameNode, ParseNode, ParseNodeType } from '../parser/parseNodes';
import { ParseResults } from '../parser/parser';
import { CollectionResult, DocumentSymbolCollector } from './documentSymbolCollector';
import { ReadOnlyFileSystem } from '../common/fileSystem';
import { convertDocumentRangesToLocation } from './navigationUtils';
import { ServiceKeys } from '../common/serviceProviderExtensions';
export type ReferenceCallback = (locations: DocumentRange[]) => void;
@ -103,17 +104,17 @@ export class FindReferencesTreeWalker {
constructor(
private _program: ProgramView,
private _filePath: string,
private _fileUri: Uri,
private _referencesResult: ReferencesResult,
private _includeDeclaration: boolean,
private _cancellationToken: CancellationToken,
private readonly _createDocumentRange: (
filePath: string,
fileUri: Uri,
result: CollectionResult,
parseResults: ParseResults
) => DocumentRange = FindReferencesTreeWalker.createDocumentRange
) {
this._parseResults = this._program.getParseResults(this._filePath);
this._parseResults = this._program.getParseResults(this._fileUri);
}
findReferences(rootNode = this._parseResults?.parseTree) {
@ -139,16 +140,16 @@ export class FindReferencesTreeWalker {
for (const result of collector.collect()) {
// Is it the same symbol?
if (this._includeDeclaration || result.node !== this._referencesResult.nodeAtOffset) {
results.push(this._createDocumentRange(this._filePath, result, this._parseResults));
results.push(this._createDocumentRange(this._fileUri, result, this._parseResults));
}
}
return results;
}
static createDocumentRange(filePath: string, result: CollectionResult, parseResults: ParseResults): DocumentRange {
static createDocumentRange(fileUri: Uri, result: CollectionResult, parseResults: ParseResults): DocumentRange {
return {
path: filePath,
uri: fileUri,
range: {
start: convertOffsetToPosition(result.range.start, parseResults.tokenizerOutput.lines),
end: convertOffsetToPosition(TextRange.getEnd(result.range), parseResults.tokenizerOutput.lines),
@ -162,7 +163,7 @@ export class ReferencesProvider {
private _program: ProgramView,
private _token: CancellationToken,
private readonly _createDocumentRange?: (
filePath: string,
fileUri: Uri,
result: CollectionResult,
parseResults: ParseResults
) => DocumentRange,
@ -172,17 +173,17 @@ export class ReferencesProvider {
}
reportReferences(
filePath: string,
fileUri: Uri,
position: Position,
includeDeclaration: boolean,
resultReporter?: ResultProgressReporter<Location[]>
) {
const sourceFileInfo = this._program.getSourceFileInfo(filePath);
const sourceFileInfo = this._program.getSourceFileInfo(fileUri);
if (!sourceFileInfo) {
return;
}
const parseResults = this._program.getParseResults(filePath);
const parseResults = this._program.getParseResults(fileUri);
if (!parseResults) {
return;
}
@ -202,7 +203,7 @@ export class ReferencesProvider {
const invokedFromUserFile = isUserCode(sourceFileInfo);
const referencesResult = ReferencesProvider.getDeclarationForPosition(
this._program,
filePath,
fileUri,
position,
reporter,
ReferenceUseCase.References,
@ -214,7 +215,7 @@ export class ReferencesProvider {
// Do we need to do a global search as well?
if (!referencesResult.requiresGlobalSearch) {
this.addReferencesToResult(sourceFileInfo.sourceFile.getFilePath(), includeDeclaration, referencesResult);
this.addReferencesToResult(sourceFileInfo.sourceFile.getUri(), includeDeclaration, referencesResult);
}
for (const curSourceFileInfo of this._program.getSourceFileInfoList()) {
@ -228,7 +229,7 @@ export class ReferencesProvider {
const fileContents = curSourceFileInfo.sourceFile.getFileContent();
if (!fileContents || referencesResult.symbolNames.some((s) => fileContents.search(s) >= 0)) {
this.addReferencesToResult(
curSourceFileInfo.sourceFile.getFilePath(),
curSourceFileInfo.sourceFile.getUri(),
includeDeclaration,
referencesResult
);
@ -246,12 +247,12 @@ export class ReferencesProvider {
for (const decl of referencesResult.declarations) {
throwIfCancellationRequested(this._token);
if (referencesResult.locations.some((l) => l.path === decl.path)) {
if (referencesResult.locations.some((l) => l.uri.equals(decl.uri))) {
// Already included.
continue;
}
const declFileInfo = this._program.getSourceFileInfo(decl.path);
const declFileInfo = this._program.getSourceFileInfo(decl.uri);
if (!declFileInfo) {
// The file the declaration belongs to doesn't belong to the program.
continue;
@ -266,10 +267,10 @@ export class ReferencesProvider {
referencesResult.providers
);
this.addReferencesToResult(declFileInfo.sourceFile.getFilePath(), includeDeclaration, tempResult);
this.addReferencesToResult(declFileInfo.sourceFile.getUri(), includeDeclaration, tempResult);
for (const loc of tempResult.locations) {
// Include declarations only. And throw away any references
if (loc.path === decl.path && doesRangeContain(decl.range, loc.range)) {
if (loc.uri.equals(decl.uri) && doesRangeContain(decl.range, loc.range)) {
referencesResult.addLocations(loc);
}
}
@ -279,15 +280,15 @@ export class ReferencesProvider {
return locations;
}
addReferencesToResult(filePath: string, includeDeclaration: boolean, referencesResult: ReferencesResult): void {
const parseResults = this._program.getParseResults(filePath);
addReferencesToResult(fileUri: Uri, includeDeclaration: boolean, referencesResult: ReferencesResult): void {
const parseResults = this._program.getParseResults(fileUri);
if (!parseResults) {
return;
}
const refTreeWalker = new FindReferencesTreeWalker(
this._program,
filePath,
fileUri,
referencesResult,
includeDeclaration,
this._token,
@ -299,7 +300,7 @@ export class ReferencesProvider {
static getDeclarationForNode(
program: ProgramView,
filePath: string,
fileUri: Uri,
node: NameNode,
reporter: ReferenceCallback | undefined,
useCase: ReferenceUseCase,
@ -318,7 +319,7 @@ export class ReferencesProvider {
return undefined;
}
const requiresGlobalSearch = isVisibleOutside(program.evaluator!, filePath, node, declarations);
const requiresGlobalSearch = isVisibleOutside(program.evaluator!, fileUri, node, declarations);
const symbolNames = new Set<string>(declarations.map((d) => getNameFromDeclaration(d)!).filter((n) => !!n));
symbolNames.add(node.value);
@ -345,14 +346,14 @@ export class ReferencesProvider {
static getDeclarationForPosition(
program: ProgramView,
filePath: string,
fileUri: Uri,
position: Position,
reporter: ReferenceCallback | undefined,
useCase: ReferenceUseCase,
token: CancellationToken
): ReferencesResult | undefined {
throwIfCancellationRequested(token);
const parseResults = program.getParseResults(filePath);
const parseResults = program.getParseResults(fileUri);
if (!parseResults) {
return undefined;
}
@ -372,16 +373,11 @@ export class ReferencesProvider {
return undefined;
}
return this.getDeclarationForNode(program, filePath, node, reporter, useCase, token);
return this.getDeclarationForNode(program, fileUri, node, reporter, useCase, token);
}
}
function isVisibleOutside(
evaluator: TypeEvaluator,
currentFilePath: string,
node: NameNode,
declarations: Declaration[]
) {
function isVisibleOutside(evaluator: TypeEvaluator, currentUri: Uri, node: NameNode, declarations: Declaration[]) {
const result = evaluator.lookUpSymbolRecursive(node, node.value, /* honorCodeFlow */ false);
if (result && !isExternallyVisible(result.symbol)) {
return false;
@ -394,7 +390,7 @@ function isVisibleOutside(
// that is within the current file and cannot be imported directly from other modules.
return declarations.some((decl) => {
// If the declaration is outside of this file, a global search is needed.
if (decl.path !== currentFilePath) {
if (!decl.uri.equals(currentUri)) {
return true;
}

View File

@ -9,27 +9,28 @@
import { CancellationToken, WorkspaceEdit } from 'vscode-languageserver';
import { isUserCode } from '../analyzer/sourceFileInfoUtils';
import { throwIfCancellationRequested } from '../common/cancellationUtils';
import { assertNever } from '../common/debug';
import { FileEditAction } from '../common/editAction';
import { ProgramView, ReferenceUseCase } from '../common/extensibility';
import { convertTextRangeToRange } from '../common/positionUtils';
import { Position, Range } from '../common/textRange';
import { ReferencesProvider, ReferencesResult } from '../languageService/referencesProvider';
import { isUserCode } from '../analyzer/sourceFileInfoUtils';
import { throwIfCancellationRequested } from '../common/cancellationUtils';
import { ParseResults } from '../parser/parser';
import { Uri } from '../common/uri/uri';
import { convertToWorkspaceEdit } from '../common/workspaceEditUtils';
import { ReferencesProvider, ReferencesResult } from '../languageService/referencesProvider';
import { ParseResults } from '../parser/parser';
export class RenameProvider {
private readonly _parseResults: ParseResults | undefined;
constructor(
private _program: ProgramView,
private _filePath: string,
private _fileUri: Uri,
private _position: Position,
private _token: CancellationToken
) {
this._parseResults = this._program.getParseResults(this._filePath);
this._parseResults = this._program.getParseResults(this._fileUri);
}
canRenameSymbol(isDefaultWorkspace: boolean, isUntitled: boolean): Range | null {
@ -45,7 +46,7 @@ export class RenameProvider {
const renameMode = RenameProvider.getRenameSymbolMode(
this._program,
this._filePath,
this._fileUri,
referencesResult,
isDefaultWorkspace,
isUntitled
@ -72,7 +73,7 @@ export class RenameProvider {
const referenceProvider = new ReferencesProvider(this._program, this._token);
const renameMode = RenameProvider.getRenameSymbolMode(
this._program,
this._filePath,
this._fileUri,
referencesResult,
isDefaultWorkspace,
isUntitled
@ -80,11 +81,7 @@ export class RenameProvider {
switch (renameMode) {
case 'singleFileMode':
referenceProvider.addReferencesToResult(
this._filePath,
/* includeDeclaration */ true,
referencesResult
);
referenceProvider.addReferencesToResult(this._fileUri, /* includeDeclaration */ true, referencesResult);
break;
case 'multiFileMode': {
@ -99,7 +96,7 @@ export class RenameProvider {
}
referenceProvider.addReferencesToResult(
curSourceFileInfo.sourceFile.getFilePath(),
curSourceFileInfo.sourceFile.getUri(),
/* includeDeclaration */ true,
referencesResult
);
@ -124,23 +121,23 @@ export class RenameProvider {
const edits: FileEditAction[] = [];
referencesResult.locations.forEach((loc) => {
edits.push({
filePath: loc.path,
fileUri: loc.uri,
range: loc.range,
replacementText: newName,
});
});
return convertToWorkspaceEdit(this._program.fileSystem, { edits, fileOperations: [] });
return convertToWorkspaceEdit({ edits, fileOperations: [] });
}
static getRenameSymbolMode(
program: ProgramView,
filePath: string,
fileUri: Uri,
referencesResult: ReferencesResult,
isDefaultWorkspace: boolean,
isUntitled: boolean
) {
const sourceFileInfo = program.getSourceFileInfo(filePath)!;
const sourceFileInfo = program.getSourceFileInfo(fileUri)!;
// We have 2 different cases
// Single file mode.
@ -156,12 +153,12 @@ export class RenameProvider {
(userFile && !referencesResult.requiresGlobalSearch) ||
(!userFile &&
sourceFileInfo.isOpenByClient &&
referencesResult.declarations.every((d) => program.getSourceFileInfo(d.path) === sourceFileInfo))
referencesResult.declarations.every((d) => program.getSourceFileInfo(d.uri) === sourceFileInfo))
) {
return 'singleFileMode';
}
if (!isUntitled && referencesResult.declarations.every((d) => isUserCode(program.getSourceFileInfo(d.path)))) {
if (!isUntitled && referencesResult.declarations.every((d) => isUserCode(program.getSourceFileInfo(d.uri)))) {
return 'multiFileMode';
}
@ -173,7 +170,7 @@ export class RenameProvider {
private _getReferenceResult() {
const referencesResult = ReferencesProvider.getDeclarationForPosition(
this._program,
this._filePath,
this._fileUri,
this._position,
/* reporter */ undefined,
ReferenceUseCase.Rename,

View File

@ -31,6 +31,7 @@ import { throwIfCancellationRequested } from '../common/cancellationUtils';
import { ProgramView } from '../common/extensibility';
import { convertPositionToOffset } from '../common/positionUtils';
import { Position } from '../common/textRange';
import { Uri } from '../common/uri/uri';
import { CallNode, NameNode, ParseNodeType } from '../parser/parseNodes';
import { ParseResults } from '../parser/parser';
import { getDocumentationPartsForTypeAndDecl, getFunctionDocStringFromType } from './tooltipUtils';
@ -41,7 +42,7 @@ export class SignatureHelpProvider {
constructor(
private _program: ProgramView,
private _filePath: string,
private _fileUri: Uri,
private _position: Position,
private _format: MarkupKind,
private _hasSignatureLabelOffsetCapability: boolean,
@ -49,8 +50,8 @@ export class SignatureHelpProvider {
private _context: SignatureHelpContext | undefined,
private _token: CancellationToken
) {
this._parseResults = this._program.getParseResults(this._filePath);
this._sourceMapper = this._program.getSourceMapper(this._filePath, this._token, /* mapCompiled */ true);
this._parseResults = this._program.getParseResults(this._fileUri);
this._sourceMapper = this._program.getSourceMapper(this._fileUri, this._token, /* mapCompiled */ true);
}
getSignatureHelp(): SignatureHelp | undefined {

View File

@ -13,15 +13,16 @@ import * as AnalyzerNodeInfo from '../analyzer/analyzerNodeInfo';
import { Declaration, DeclarationType } from '../analyzer/declaration';
import { getLastTypedDeclaredForSymbol, isVisibleExternally } from '../analyzer/symbolUtils';
import { throwIfCancellationRequested } from '../common/cancellationUtils';
import { getSymbolKind } from '../common/lspUtils';
import { convertOffsetsToRange } from '../common/positionUtils';
import { Range } from '../common/textRange';
import { Uri } from '../common/uri/uri';
import { ParseResults } from '../parser/parser';
import { convertSymbolKindToCompletionItemKind } from './autoImporter';
import { getSymbolKind } from '../common/lspUtils';
export interface IndexAliasData {
readonly originalName: string;
readonly modulePath: string;
readonly moduleUri: Uri;
readonly kind: SymbolKind;
readonly itemKind?: CompletionItemKind | undefined;
}

View File

@ -16,7 +16,7 @@ import {
getClassDocString,
getFunctionDocStringInherited,
getModuleDocString,
getModuleDocStringFromPaths,
getModuleDocStringFromUris,
getOverloadedFunctionDocStringsInherited,
getPropertyDocStringInherited,
getVariableDocString,
@ -323,7 +323,7 @@ export function getDocumentationPartsForTypeAndDecl(
}
}
typeDoc = getModuleDocStringFromPaths([resolvedDecl.path], sourceMapper);
typeDoc = getModuleDocStringFromUris([resolvedDecl.uri], sourceMapper);
}
typeDoc =

View File

@ -7,15 +7,15 @@
*/
import { CancellationToken, Location, ResultProgressReporter, SymbolInformation } from 'vscode-languageserver';
import { throwIfCancellationRequested } from '../common/cancellationUtils';
import * as StringUtils from '../common/stringUtils';
import { IndexSymbolData, SymbolIndexer } from './symbolIndexer';
import { ProgramView } from '../common/extensibility';
import { isUserCode } from '../analyzer/sourceFileInfoUtils';
import { getFileInfo } from '../analyzer/analyzerNodeInfo';
import { convertPathToUri } from '../common/pathUtils';
import { Workspace } from '../workspaceFactory';
import { isUserCode } from '../analyzer/sourceFileInfoUtils';
import { throwIfCancellationRequested } from '../common/cancellationUtils';
import { appendArray } from '../common/collectionUtils';
import { ProgramView } from '../common/extensibility';
import * as StringUtils from '../common/stringUtils';
import { Uri } from '../common/uri/uri';
import { Workspace } from '../workspaceFactory';
import { IndexSymbolData, SymbolIndexer } from './symbolIndexer';
type WorkspaceSymbolCallback = (symbols: SymbolInformation[]) => void;
@ -55,10 +55,10 @@ export class WorkspaceSymbolProvider {
return this._allSymbols;
}
protected getSymbolsForDocument(program: ProgramView, filePath: string): SymbolInformation[] {
protected getSymbolsForDocument(program: ProgramView, fileUri: Uri): SymbolInformation[] {
const symbolList: SymbolInformation[] = [];
const parseResults = program.getParseResults(filePath);
const parseResults = program.getParseResults(fileUri);
if (!parseResults) {
return symbolList;
}
@ -69,7 +69,7 @@ export class WorkspaceSymbolProvider {
}
const indexSymbolData = SymbolIndexer.indexSymbols(fileInfo, parseResults, this._token);
this.appendWorkspaceSymbolsRecursive(indexSymbolData, program, filePath, '', symbolList);
this.appendWorkspaceSymbolsRecursive(indexSymbolData, program, fileUri, '', symbolList);
return symbolList;
}
@ -77,7 +77,7 @@ export class WorkspaceSymbolProvider {
protected appendWorkspaceSymbolsRecursive(
indexSymbolData: IndexSymbolData[] | undefined,
program: ProgramView,
filePath: string,
fileUri: Uri,
container: string,
symbolList: SymbolInformation[]
) {
@ -94,7 +94,7 @@ export class WorkspaceSymbolProvider {
if (StringUtils.isPatternInSymbol(this._query, symbolData.name)) {
const location: Location = {
uri: convertPathToUri(program.fileSystem, filePath),
uri: fileUri.toString(),
range: symbolData.selectionRange!,
};
@ -114,7 +114,7 @@ export class WorkspaceSymbolProvider {
this.appendWorkspaceSymbolsRecursive(
symbolData.children,
program,
filePath,
fileUri,
this._getContainerName(container, symbolData.name),
symbolList
);
@ -134,7 +134,7 @@ export class WorkspaceSymbolProvider {
continue;
}
const symbolList = this.getSymbolsForDocument(program, sourceFileInfo.sourceFile.getFilePath());
const symbolList = this.getSymbolsForDocument(program, sourceFileInfo.sourceFile.getUri());
if (symbolList.length > 0) {
this._reporter(symbolList);
}

View File

@ -28,12 +28,14 @@ import { createDeferred } from './common/deferred';
import { Diagnostic, DiagnosticCategory } from './common/diagnostic';
import { FileDiagnostics } from './common/diagnosticSink';
import { FullAccessHost } from './common/fullAccessHost';
import { combinePaths, getFileSpec, normalizePath, tryStat } from './common/pathUtils';
import { combinePaths, normalizePath } from './common/pathUtils';
import { versionFromString } from './common/pythonVersion';
import { RealTempFile, createFromRealFileSystem } from './common/realFileSystem';
import { ServiceProvider } from './common/serviceProvider';
import { createServiceProvider } from './common/serviceProviderExtensions';
import { Range, isEmptyRange } from './common/textRange';
import { Uri } from './common/uri/uri';
import { getFileSpec, tryStat } from './common/uri/uriUtils';
import { PyrightFileSystem } from './pyrightFileSystem';
const toolName = 'pyright';
@ -64,11 +66,11 @@ interface PyrightSymbolCount {
interface PyrightTypeCompletenessReport {
packageName: string;
packageRootDirectory?: string | undefined;
packageRootDirectory?: Uri | undefined;
moduleName: string;
moduleRootDirectory?: string | undefined;
moduleRootDirectory?: Uri | undefined;
ignoreUnknownTypesFromImports: boolean;
pyTypedPath?: string | undefined;
pyTypedPath?: Uri | undefined;
exportedSymbolCounts: PyrightSymbolCount;
otherSymbolCounts: PyrightSymbolCount;
missingFunctionDocStringCount: number;
@ -95,7 +97,7 @@ interface PyrightPublicSymbolReport {
}
interface PyrightJsonDiagnostic {
file: string;
uri: Uri;
severity: SeverityLevel;
message: string;
range?: Range | undefined;
@ -244,10 +246,9 @@ async function processArgs(): Promise<ExitStatus> {
// Verify the specified file specs to make sure their wildcard roots exist.
const tempFileSystem = new PyrightFileSystem(createFromRealFileSystem());
const tempServiceProvider = createServiceProvider(tempFileSystem, console);
for (const fileDesc of options.includeFileSpecsOverride) {
const includeSpec = getFileSpec(tempServiceProvider, '', fileDesc);
const includeSpec = getFileSpec(Uri.file(process.cwd(), tempFileSystem.isCaseSensitive), fileDesc);
try {
const stat = tryStat(tempFileSystem, includeSpec.wildcardRoot);
if (!stat) {
@ -361,7 +362,7 @@ async function processArgs(): Promise<ExitStatus> {
// up the JSON output, which goes to stdout.
const output = args.outputjson ? new StderrConsole(logLevel) : new StandardConsole(logLevel);
const fileSystem = new PyrightFileSystem(createFromRealFileSystem(output, new ChokidarFileWatcherProvider(output)));
const tempFile = new RealTempFile();
const tempFile = new RealTempFile(fileSystem.isCaseSensitive);
const serviceProvider = createServiceProvider(fileSystem, output, tempFile);
// The package type verification uses a different path.
@ -386,7 +387,7 @@ async function processArgs(): Promise<ExitStatus> {
// Refresh service after 2 seconds after the last library file change is detected.
const service = new AnalyzerService('<default>', serviceProvider, {
console: output,
hostFactory: () => new FullAccessHost(fileSystem),
hostFactory: () => new FullAccessHost(serviceProvider),
libraryReanalysisTimeProvider: () => 2 * 1000,
});
const exitStatus = createDeferred<ExitStatus>();
@ -540,7 +541,7 @@ function buildTypeCompletenessReport(
// Add the general diagnostics.
completenessReport.generalDiagnostics.forEach((diag) => {
const jsonDiag = convertDiagnosticToJson('', diag);
const jsonDiag = convertDiagnosticToJson(Uri.empty(), diag);
if (isDiagnosticIncluded(jsonDiag.severity, minSeverityLevel)) {
report.generalDiagnostics.push(jsonDiag);
}
@ -549,11 +550,11 @@ function buildTypeCompletenessReport(
report.typeCompleteness = {
packageName,
packageRootDirectory: completenessReport.packageRootDirectory,
packageRootDirectory: completenessReport.packageRootDirectoryUri,
moduleName: completenessReport.moduleName,
moduleRootDirectory: completenessReport.moduleRootDirectory,
moduleRootDirectory: completenessReport.moduleRootDirectoryUri,
ignoreUnknownTypesFromImports: completenessReport.ignoreExternal,
pyTypedPath: completenessReport.pyTypedPath,
pyTypedPath: completenessReport.pyTypedPathUri,
exportedSymbolCounts: {
withKnownType: 0,
withAmbiguousType: 0,
@ -587,7 +588,7 @@ function buildTypeCompletenessReport(
// Convert and filter the diagnostics.
symbol.diagnostics.forEach((diag) => {
const jsonDiag = convertDiagnosticToJson(diag.filePath, diag.diagnostic);
const jsonDiag = convertDiagnosticToJson(diag.uri, diag.diagnostic);
if (isDiagnosticIncluded(jsonDiag.severity, minSeverityLevel)) {
diagnostics.push(jsonDiag);
}
@ -811,7 +812,7 @@ function reportDiagnosticsAsJson(
diag.category === DiagnosticCategory.Warning ||
diag.category === DiagnosticCategory.Information
) {
const jsonDiag = convertDiagnosticToJson(fileDiag.filePath, diag);
const jsonDiag = convertDiagnosticToJson(fileDiag.fileUri, diag);
if (isDiagnosticIncluded(jsonDiag.severity, minSeverityLevel)) {
report.generalDiagnostics.push(jsonDiag);
}
@ -862,9 +863,9 @@ function convertDiagnosticCategoryToSeverity(category: DiagnosticCategory): Seve
}
}
function convertDiagnosticToJson(filePath: string, diag: Diagnostic): PyrightJsonDiagnostic {
function convertDiagnosticToJson(uri: Uri, diag: Diagnostic): PyrightJsonDiagnostic {
return {
file: filePath,
uri,
severity: convertDiagnosticCategoryToSeverity(diag.category),
message: diag.message,
range: isEmptyRange(diag.range) ? undefined : diag.range,
@ -891,9 +892,9 @@ function reportDiagnosticsAsText(
);
if (fileErrorsAndWarnings.length > 0) {
console.info(`${fileDiagnostics.filePath}`);
console.info(`${fileDiagnostics.fileUri.toUserVisibleString()}`);
fileErrorsAndWarnings.forEach((diag) => {
const jsonDiag = convertDiagnosticToJson(fileDiagnostics.filePath, diag);
const jsonDiag = convertDiagnosticToJson(fileDiagnostics.fileUri, diag);
logDiagnosticToConsole(jsonDiag);
if (diag.category === DiagnosticCategory.Error) {
@ -923,8 +924,8 @@ function reportDiagnosticsAsText(
function logDiagnosticToConsole(diag: PyrightJsonDiagnostic, prefix = ' ') {
let message = prefix;
if (diag.file) {
message += `${diag.file}:`;
if (!diag.uri.isEmpty()) {
message += `${diag.uri.toUserVisibleString()}:`;
}
if (diag.range && !isEmptyRange(diag.range)) {
message +=

View File

@ -15,13 +15,14 @@ import { getPyTypedInfo } from './analyzer/pyTypedUtils';
import { ExecutionEnvironment } from './common/configOptions';
import { FileSystem, MkDirOptions } from './common/fileSystem';
import { stubsSuffix } from './common/pathConsts';
import { combinePaths, ensureTrailingDirectorySeparator, isDirectory, tryStat } from './common/pathUtils';
import { Uri } from './common/uri/uri';
import { isDirectory, tryStat } from './common/uri/uriUtils';
import { ReadOnlyAugmentedFileSystem } from './readonlyAugmentedFileSystem';
export interface SupportPartialStubs {
isPartialStubPackagesScanned(execEnv: ExecutionEnvironment): boolean;
isPathScanned(path: string): boolean;
processPartialStubPackages(paths: string[], roots: string[], bundledStubPath?: string): void;
isPathScanned(path: Uri): boolean;
processPartialStubPackages(paths: Uri[], roots: Uri[], bundledStubPath?: Uri): void;
clearPartialStubs(): void;
}
@ -49,49 +50,45 @@ export class PyrightFileSystem extends ReadOnlyAugmentedFileSystem implements IP
super(realFS);
}
override mkdirSync(path: string, options?: MkDirOptions): void {
this.realFS.mkdirSync(path, options);
override mkdirSync(uri: Uri, options?: MkDirOptions): void {
this.realFS.mkdirSync(uri, options);
}
override chdir(path: string): void {
this.realFS.chdir(path);
override chdir(uri: Uri): void {
this.realFS.chdir(uri);
}
override writeFileSync(path: string, data: string | Buffer, encoding: BufferEncoding | null): void {
this.realFS.writeFileSync(this.getOriginalPath(path), data, encoding);
override writeFileSync(uri: Uri, data: string | Buffer, encoding: BufferEncoding | null): void {
this.realFS.writeFileSync(this.getOriginalPath(uri), data, encoding);
}
override rmdirSync(path: string): void {
this.realFS.rmdirSync(this.getOriginalPath(path));
override rmdirSync(uri: Uri): void {
this.realFS.rmdirSync(this.getOriginalPath(uri));
}
override unlinkSync(path: string): void {
this.realFS.unlinkSync(this.getOriginalPath(path));
override unlinkSync(uri: Uri): void {
this.realFS.unlinkSync(this.getOriginalPath(uri));
}
override createWriteStream(path: string): fs.WriteStream {
return this.realFS.createWriteStream(this.getOriginalPath(path));
override createWriteStream(uri: Uri): fs.WriteStream {
return this.realFS.createWriteStream(this.getOriginalPath(uri));
}
override copyFileSync(src: string, dst: string): void {
override copyFileSync(src: Uri, dst: Uri): void {
this.realFS.copyFileSync(this.getOriginalPath(src), this.getOriginalPath(dst));
}
override getUri(originalPath: string): string {
return this.realFS.getUri(originalPath);
}
isPartialStubPackagesScanned(execEnv: ExecutionEnvironment): boolean {
return this.isPathScanned(execEnv.root ?? '');
return execEnv.root ? this.isPathScanned(execEnv.root) : false;
}
isPathScanned(path: string): boolean {
return this._rootSearched.has(path);
isPathScanned(uri: Uri): boolean {
return this._rootSearched.has(uri.key);
}
processPartialStubPackages(paths: string[], roots: string[], bundledStubPath?: string) {
processPartialStubPackages(paths: Uri[], roots: Uri[], bundledStubPath?: Uri) {
for (const path of paths) {
this._rootSearched.add(path);
this._rootSearched.add(path.key);
if (!this.realFS.existsSync(path) || !isDirectory(this.realFS, path)) {
continue;
@ -105,9 +102,9 @@ export class PyrightFileSystem extends ReadOnlyAugmentedFileSystem implements IP
// Leave empty set of dir entries to process.
}
const isBundledStub = path === bundledStubPath;
const isBundledStub = path.equals(bundledStubPath);
for (const entry of dirEntries) {
const partialStubPackagePath = combinePaths(path, entry.name);
const partialStubPackagePath = path.combinePaths(entry.name);
const isDirectory = !entry.isSymbolicLink()
? entry.isDirectory()
: !!tryStat(this.realFS, partialStubPackagePath)?.isDirectory();
@ -123,13 +120,13 @@ export class PyrightFileSystem extends ReadOnlyAugmentedFileSystem implements IP
}
// We found partially typed stub-packages.
this._partialStubPackagePaths.add(partialStubPackagePath);
this._partialStubPackagePaths.add(partialStubPackagePath.key);
// Search the root to see whether we have matching package installed.
let partialStubs: string[] | undefined;
const packageName = entry.name.substr(0, entry.name.length - stubsSuffix.length);
for (const root of roots) {
const packagePath = combinePaths(root, packageName);
const packagePath = root.combinePaths(packageName);
try {
const stat = tryStat(this.realFS, packagePath);
if (!stat?.isDirectory()) {
@ -149,8 +146,8 @@ export class PyrightFileSystem extends ReadOnlyAugmentedFileSystem implements IP
// Merge partial stub packages to the library.
partialStubs = partialStubs ?? this._getRelativePathPartialStubs(partialStubPackagePath);
for (const partialStub of partialStubs) {
const originalPyiFile = combinePaths(partialStubPackagePath, partialStub);
const mappedPyiFile = combinePaths(packagePath, partialStub);
const originalPyiFile = partialStubPackagePath.combinePaths(partialStub);
const mappedPyiFile = packagePath.combinePaths(partialStub);
this.recordMovedEntry(mappedPyiFile, originalPyiFile, packagePath);
}
} catch {
@ -168,17 +165,15 @@ export class PyrightFileSystem extends ReadOnlyAugmentedFileSystem implements IP
this._partialStubPackagePaths.clear();
}
protected override isMovedEntry(path: string) {
return this._partialStubPackagePaths.has(path) || super.isMovedEntry(path);
protected override isMovedEntry(uri: Uri) {
return this._partialStubPackagePaths.has(uri.key) || super.isMovedEntry(uri);
}
private _getRelativePathPartialStubs(path: string) {
const paths: string[] = [];
const partialStubPathLength = ensureTrailingDirectorySeparator(path).length;
const searchAllStubs = (path: string) => {
for (const entry of this.realFS.readdirEntriesSync(path)) {
const filePath = combinePaths(path, entry.name);
private _getRelativePathPartialStubs(partialStubPath: Uri) {
const relativePaths: string[] = [];
const searchAllStubs = (uri: Uri) => {
for (const entry of this.realFS.readdirEntriesSync(uri)) {
const filePath = uri.combinePaths(entry.name);
let isDirectory = entry.isDirectory();
let isFile = entry.isFile();
@ -195,15 +190,15 @@ export class PyrightFileSystem extends ReadOnlyAugmentedFileSystem implements IP
}
if (isFile && entry.name.endsWith('.pyi')) {
const relative = filePath.substring(partialStubPathLength);
const relative = partialStubPath.getRelativePathComponents(filePath).join('/');
if (relative) {
paths.push(relative);
relativePaths.push(relative);
}
}
}
};
searchAllStubs(path);
return paths;
searchAllStubs(partialStubPath);
return relativePaths;
}
}

View File

@ -12,57 +12,53 @@ import type * as fs from 'fs';
import { appendArray, getOrAdd } from './common/collectionUtils';
import { FileSystem, MkDirOptions, Stats, VirtualDirent } from './common/fileSystem';
import { FileWatcher, FileWatcherEventHandler } from './common/fileWatcher';
import {
combinePaths,
ensureTrailingDirectorySeparator,
getDirectoryPath,
getFileName,
getRelativePathComponentsFromDirectory,
} from './common/pathUtils';
import { Uri } from './common/uri/uri';
export class ReadOnlyAugmentedFileSystem implements FileSystem {
// Mapped file to original file map
private readonly _entryMap = new Map<string, string>();
private readonly _entryMap = new Map<string, Uri>();
// Original file to mapped file map
private readonly _reverseEntryMap = new Map<string, string>();
private readonly _reverseEntryMap = new Map<string, Uri>();
// Mapped files per a containing folder map
private readonly _folderMap = new Map<string, { name: string; isFile: boolean }[]>();
constructor(protected realFS: FileSystem) {}
existsSync(path: string): boolean {
if (this.isMovedEntry(path)) {
get isCaseSensitive(): boolean {
return this.realFS.isCaseSensitive;
}
existsSync(uri: Uri): boolean {
if (this.isMovedEntry(uri)) {
// Pretend partial stub folder and its files not exist
return false;
}
return this.realFS.existsSync(this.getOriginalPath(path));
return this.realFS.existsSync(this.getOriginalPath(uri));
}
mkdirSync(path: string, options?: MkDirOptions): void {
mkdirSync(uri: Uri, options?: MkDirOptions): void {
throw new Error('Operation is not allowed.');
}
chdir(path: string): void {
chdir(uri: Uri): void {
throw new Error('Operation is not allowed.');
}
readdirEntriesSync(path: string): fs.Dirent[] {
const maybeDirectory = ensureTrailingDirectorySeparator(path);
readdirEntriesSync(uri: Uri): fs.Dirent[] {
const entries: fs.Dirent[] = [];
const movedEntries = this._folderMap.get(maybeDirectory);
if (!movedEntries || this.realFS.existsSync(path)) {
const movedEntries = this._folderMap.get(uri.key);
if (!movedEntries || this.realFS.existsSync(uri)) {
appendArray(
entries,
this.realFS.readdirEntriesSync(path).filter((item) => {
this.realFS.readdirEntriesSync(uri).filter((item) => {
// Filter out the stub package directory and any
// entries that will be overwritten by stub package
// virtual items.
return (
!this.isMovedEntry(combinePaths(path, item.name)) &&
!this.isMovedEntry(uri.combinePaths(item.name)) &&
!movedEntries?.some((movedEntry) => movedEntry.name === item.name)
);
})
@ -76,129 +72,124 @@ export class ReadOnlyAugmentedFileSystem implements FileSystem {
return entries.concat(movedEntries.map((e) => new VirtualDirent(e.name, e.isFile)));
}
readdirSync(path: string): string[] {
return this.readdirEntriesSync(path).map((p) => p.name);
readdirSync(uri: Uri): string[] {
return this.readdirEntriesSync(uri).map((p) => p.name);
}
readFileSync(path: string, encoding?: null): Buffer;
readFileSync(path: string, encoding: BufferEncoding): string;
readFileSync(path: string, encoding?: BufferEncoding | null): string | Buffer {
return this.realFS.readFileSync(this.getOriginalPath(path), encoding);
readFileSync(uri: Uri, encoding?: null): Buffer;
readFileSync(uri: Uri, encoding: BufferEncoding): string;
readFileSync(uri: Uri, encoding?: BufferEncoding | null): string | Buffer {
return this.realFS.readFileSync(this.getOriginalPath(uri), encoding);
}
writeFileSync(path: string, data: string | Buffer, encoding: BufferEncoding | null): void {
writeFileSync(uri: Uri, data: string | Buffer, encoding: BufferEncoding | null): void {
throw new Error('Operation is not allowed.');
}
statSync(path: string): Stats {
return this.realFS.statSync(this.getOriginalPath(path));
statSync(uri: Uri): Stats {
return this.realFS.statSync(this.getOriginalPath(uri));
}
rmdirSync(path: string): void {
rmdirSync(uri: Uri): void {
throw new Error('Operation is not allowed.');
}
unlinkSync(path: string): void {
unlinkSync(uri: Uri): void {
throw new Error('Operation is not allowed.');
}
realpathSync(path: string): string {
if (this._entryMap.has(path)) {
return path;
realpathSync(uri: Uri): Uri {
if (this._entryMap.has(uri.key)) {
return uri;
}
return this.realFS.realpathSync(path);
return this.realFS.realpathSync(uri);
}
getModulePath(): string {
getModulePath(): Uri {
return this.realFS.getModulePath();
}
createFileSystemWatcher(paths: string[], listener: FileWatcherEventHandler): FileWatcher {
createFileSystemWatcher(paths: Uri[], listener: FileWatcherEventHandler): FileWatcher {
return this.realFS.createFileSystemWatcher(paths, listener);
}
createReadStream(path: string): fs.ReadStream {
return this.realFS.createReadStream(this.getOriginalPath(path));
createReadStream(uri: Uri): fs.ReadStream {
return this.realFS.createReadStream(this.getOriginalPath(uri));
}
createWriteStream(path: string): fs.WriteStream {
createWriteStream(uri: Uri): fs.WriteStream {
throw new Error('Operation is not allowed.');
}
copyFileSync(src: string, dst: string): void {
copyFileSync(src: Uri, dst: Uri): void {
throw new Error('Operation is not allowed.');
}
// Async I/O
readFile(path: string): Promise<Buffer> {
return this.realFS.readFile(this.getOriginalPath(path));
readFile(uri: Uri): Promise<Buffer> {
return this.realFS.readFile(this.getOriginalPath(uri));
}
readFileText(path: string, encoding?: BufferEncoding): Promise<string> {
return this.realFS.readFileText(this.getOriginalPath(path), encoding);
readFileText(uri: Uri, encoding?: BufferEncoding): Promise<string> {
return this.realFS.readFileText(this.getOriginalPath(uri), encoding);
}
realCasePath(path: string): string {
return this.realFS.realCasePath(path);
}
getUri(originalPath: string): string {
return this.realFS.getUri(originalPath);
realCasePath(uri: Uri): Uri {
return this.realFS.realCasePath(uri);
}
// See whether the file is mapped to another location.
isMappedFilePath(filepath: string): boolean {
return this._entryMap.has(filepath) || this.realFS.isMappedFilePath(filepath);
isMappedUri(fileUri: Uri): boolean {
return this._entryMap.has(fileUri.key) || this.realFS.isMappedUri(fileUri);
}
// Get original filepath if the given filepath is mapped.
getOriginalFilePath(mappedFilePath: string) {
return this.realFS.getOriginalFilePath(this.getOriginalPath(mappedFilePath));
getOriginalUri(mappedFileUri: Uri) {
return this.realFS.getOriginalUri(this.getOriginalPath(mappedFileUri));
}
// Get mapped filepath if the given filepath is mapped.
getMappedFilePath(originalFilepath: string) {
const mappedFilePath = this.realFS.getMappedFilePath(originalFilepath);
return this._reverseEntryMap.get(mappedFilePath) ?? mappedFilePath;
getMappedUri(originalFileUri: Uri) {
const mappedFileUri = this.realFS.getMappedUri(originalFileUri);
return this._reverseEntryMap.get(mappedFileUri.key) ?? mappedFileUri;
}
isInZip(path: string): boolean {
return this.realFS.isInZip(path);
isInZip(uri: Uri): boolean {
return this.realFS.isInZip(uri);
}
protected recordMovedEntry(mappedPath: string, originalPath: string, rootPath: string) {
this._entryMap.set(mappedPath, originalPath);
this._reverseEntryMap.set(originalPath, mappedPath);
protected recordMovedEntry(mappedUri: Uri, originalUri: Uri, rootPath: Uri) {
this._entryMap.set(mappedUri.key, originalUri);
this._reverseEntryMap.set(originalUri.key, mappedUri);
const directory = ensureTrailingDirectorySeparator(getDirectoryPath(mappedPath));
const folderInfo = getOrAdd(this._folderMap, directory, () => []);
const directory = mappedUri.getDirectory();
const folderInfo = getOrAdd(this._folderMap, directory.key, () => []);
const name = getFileName(mappedPath);
const name = mappedUri.fileName;
if (!folderInfo.some((entry) => entry.name === name)) {
folderInfo.push({ name, isFile: true });
}
// Add the directory entries for the sub paths as well. We should ignoreCase here because
// the paths are just combining of already known paths.
const subPathEntries = getRelativePathComponentsFromDirectory(rootPath, directory, /* ignoreCase */ false);
for (let i = 1; i < subPathEntries.length; i++) {
const subdir = combinePaths(rootPath, ...subPathEntries.slice(1, i + 1));
const parent = ensureTrailingDirectorySeparator(getDirectoryPath(subdir));
// Add the directory entries for the sub paths as well.
const subPathEntries = rootPath.getRelativePathComponents(directory);
for (let i = 0; i < subPathEntries.length; i++) {
const subdir = rootPath.combinePaths(...subPathEntries.slice(0, i + 1));
const parent = subdir.getDirectory().key;
const dirInfo = getOrAdd(this._folderMap, parent, () => []);
const dirName = getFileName(subdir);
const dirName = subdir.fileName;
if (!dirInfo.some((entry) => entry.name === dirName)) {
dirInfo.push({ name: dirName, isFile: false });
}
}
}
protected getOriginalPath(mappedFilePath: string) {
return this._entryMap.get(mappedFilePath) ?? mappedFilePath;
protected getOriginalPath(mappedFileUri: Uri) {
return this._entryMap.get(mappedFileUri.key) ?? mappedFileUri;
}
protected isMovedEntry(path: string) {
return this._reverseEntryMap.has(path);
protected isMovedEntry(uri: Uri) {
return this._reverseEntryMap.has(uri.key);
}
protected clear() {

View File

@ -30,11 +30,12 @@ import { expandPathVariables } from './common/envVarUtils';
import { FileBasedCancellationProvider } from './common/fileBasedCancellationUtils';
import { FullAccessHost } from './common/fullAccessHost';
import { Host } from './common/host';
import { realCasePath, resolvePaths } from './common/pathUtils';
import { ProgressReporter } from './common/progressReporter';
import { RealTempFile, WorkspaceFileWatcherProvider, createFromRealFileSystem } from './common/realFileSystem';
import { ServiceProvider } from './common/serviceProvider';
import { createServiceProvider } from './common/serviceProviderExtensions';
import { Uri } from './common/uri/uri';
import { getRootUri } from './common/uri/uriUtils';
import { LanguageServerBase, ServerSettings } from './languageServerBase';
import { CodeActionProvider } from './languageService/codeActionProvider';
import { PyrightFileSystem } from './pyrightFileSystem';
@ -49,20 +50,21 @@ export class PyrightServer extends LanguageServerBase {
// eslint-disable-next-line @typescript-eslint/no-var-requires
const version = require('../package.json').version || '';
// When executed from CLI command (pyright-langserver), __rootDirectory is
// already defined. When executed from VSCode extension, rootDirectory should
// be __dirname.
const rootDirectory = (global as any).__rootDirectory || __dirname;
const console = new ConsoleWithLogLevel(connection.console);
const fileWatcherProvider = new WorkspaceFileWatcherProvider();
const fileSystem = createFromRealFileSystem(console, fileWatcherProvider);
const pyrightFs = new PyrightFileSystem(fileSystem);
const tempFile = new RealTempFile();
const tempFile = new RealTempFile(pyrightFs.isCaseSensitive);
const cacheManager = new CacheManager();
const serviceProvider = createServiceProvider(pyrightFs, tempFile, console, cacheManager);
const realPathRoot = realCasePath(rootDirectory, pyrightFs);
// When executed from CLI command (pyright-langserver), __rootDirectory is
// already defined. When executed from VSCode extension, rootDirectory should
// be __dirname.
const rootDirectory: Uri =
getRootUri(pyrightFs.isCaseSensitive) || Uri.file(__dirname, pyrightFs.isCaseSensitive);
const realPathRoot = pyrightFs.realCasePath(rootDirectory);
super(
{
@ -98,44 +100,40 @@ export class PyrightServer extends LanguageServerBase {
};
try {
const pythonSection = await this.getConfiguration(workspace.uri, 'python');
const pythonSection = await this.getConfiguration(workspace.rootUri, 'python');
if (pythonSection) {
const pythonPath = pythonSection.pythonPath;
if (pythonPath && isString(pythonPath) && !isPythonBinary(pythonPath)) {
serverSettings.pythonPath = resolvePaths(
workspace.rootPath,
expandPathVariables(workspace.rootPath, pythonPath)
serverSettings.pythonPath = workspace.rootUri.combinePaths(
expandPathVariables(workspace.rootUri, pythonPath)
);
}
const venvPath = pythonSection.venvPath;
if (venvPath && isString(venvPath)) {
serverSettings.venvPath = resolvePaths(
workspace.rootPath,
expandPathVariables(workspace.rootPath, venvPath)
serverSettings.venvPath = workspace.rootUri.combinePaths(
expandPathVariables(workspace.rootUri, venvPath)
);
}
}
const pythonAnalysisSection = await this.getConfiguration(workspace.uri, 'python.analysis');
const pythonAnalysisSection = await this.getConfiguration(workspace.rootUri, 'python.analysis');
if (pythonAnalysisSection) {
const typeshedPaths = pythonAnalysisSection.typeshedPaths;
if (typeshedPaths && Array.isArray(typeshedPaths) && typeshedPaths.length > 0) {
const typeshedPath = typeshedPaths[0];
if (typeshedPath && isString(typeshedPath)) {
serverSettings.typeshedPath = resolvePaths(
workspace.rootPath,
expandPathVariables(workspace.rootPath, typeshedPath)
serverSettings.typeshedPath = workspace.rootUri.combinePaths(
expandPathVariables(workspace.rootUri, typeshedPath)
);
}
}
const stubPath = pythonAnalysisSection.stubPath;
if (stubPath && isString(stubPath)) {
serverSettings.stubPath = resolvePaths(
workspace.rootPath,
expandPathVariables(workspace.rootPath, stubPath)
serverSettings.stubPath = workspace.rootUri.combinePaths(
expandPathVariables(workspace.rootUri, stubPath)
);
}
@ -167,7 +165,7 @@ export class PyrightServer extends LanguageServerBase {
if (extraPaths && Array.isArray(extraPaths) && extraPaths.length > 0) {
serverSettings.extraPaths = extraPaths
.filter((p) => p && isString(p))
.map((p) => resolvePaths(workspace.rootPath, expandPathVariables(workspace.rootPath, p)));
.map((p) => workspace.rootUri.combinePaths(expandPathVariables(workspace.rootUri, p)));
}
serverSettings.includeFileSpecs = this._getStringValues(pythonAnalysisSection.include);
@ -196,7 +194,7 @@ export class PyrightServer extends LanguageServerBase {
serverSettings.autoSearchPaths = true;
}
const pyrightSection = await this.getConfiguration(workspace.uri, 'pyright');
const pyrightSection = await this.getConfiguration(workspace.rootUri, 'pyright');
if (pyrightSection) {
if (pyrightSection.openFilesOnly !== undefined) {
serverSettings.openFilesOnly = !!pyrightSection.openFilesOnly;
@ -227,11 +225,11 @@ export class PyrightServer extends LanguageServerBase {
return undefined;
}
return new BackgroundAnalysis(this.console);
return new BackgroundAnalysis(this.serverOptions.serviceProvider);
}
protected override createHost() {
return new FullAccessHost(this.fs);
return new FullAccessHost(this.serverOptions.serviceProvider);
}
protected override createImportResolver(
@ -262,15 +260,9 @@ export class PyrightServer extends LanguageServerBase {
): Promise<(Command | CodeAction)[] | undefined | null> {
this.recordUserInteractionTime();
const filePath = this.uriParser.decodeTextDocumentUri(params.textDocument.uri);
const workspace = await this.getWorkspaceForFile(filePath);
return CodeActionProvider.getCodeActionsForPosition(
workspace,
filePath,
params.range,
params.context.only,
token
);
const uri = Uri.parse(params.textDocument.uri, this.serverOptions.serviceProvider.fs().isCaseSensitive);
const workspace = await this.getWorkspaceForFile(uri);
return CodeActionProvider.getCodeActionsForPosition(workspace, uri, params.range, params.context.only, token);
}
protected createProgressReporter(): ProgressReporter {

View File

@ -17,13 +17,14 @@ import { ConfigOptions } from '../common/configOptions';
import { NullConsole } from '../common/console';
import { normalizeSlashes } from '../common/pathUtils';
import { convertOffsetsToRange, convertOffsetToPosition } from '../common/positionUtils';
import { ServiceProvider } from '../common/serviceProvider';
import { Uri } from '../common/uri/uri';
import { CompletionProvider } from '../languageService/completionProvider';
import { parseTestData } from './harness/fourslash/fourSlashParser';
import { TestAccessHost } from './harness/testAccessHost';
import * as host from './harness/testHost';
import { createFromFileSystem, distlibFolder, libFolder } from './harness/vfs/factory';
import * as vfs from './harness/vfs/filesystem';
import { CompletionProvider } from '../languageService/completionProvider';
import { ServiceProvider } from '../common/serviceProvider';
test('check chained files', () => {
const code = `
@ -40,16 +41,17 @@ test('check chained files', () => {
//// [|foo/*marker*/|]
`;
const basePath = normalizeSlashes('/');
const basePath = Uri.file(normalizeSlashes('/'));
const { data, service } = createServiceWithChainedSourceFiles(basePath, code);
const marker = data.markerPositions.get('marker')!;
const markerUri = Uri.file(marker.fileName);
const parseResult = service.getParseResult(marker.fileName)!;
const parseResult = service.getParseResult(markerUri)!;
const result = new CompletionProvider(
service.test_program,
basePath,
marker.fileName,
markerUri,
convertOffsetToPosition(marker.position, parseResult.tokenizerOutput.lines),
{
format: MarkupKind.Markdown,
@ -80,15 +82,16 @@ test('modify chained files', () => {
//// [|foo/*marker*/|]
`;
const basePath = normalizeSlashes('/');
const basePath = Uri.file(normalizeSlashes('/'));
const { data, service } = createServiceWithChainedSourceFiles(basePath, code);
// Make sure files are all realized.
const marker = data.markerPositions.get('marker')!;
const parseResult = service.getParseResult(marker.fileName)!;
const markerUri = Uri.file(marker.fileName);
const parseResult = service.getParseResult(markerUri)!;
// Close file in the middle of the chain
service.setFileClosed(data.markerPositions.get('delete')!.fileName);
service.setFileClosed(Uri.file(data.markerPositions.get('delete')!.fileName));
// Make sure we don't get suggestion from auto import but from chained files.
service.test_program.configOptions.autoImportCompletions = false;
@ -96,7 +99,7 @@ test('modify chained files', () => {
const result = new CompletionProvider(
service.test_program,
basePath,
marker.fileName,
markerUri,
convertOffsetToPosition(marker.position, parseResult.tokenizerOutput.lines),
{
format: MarkupKind.Markdown,
@ -129,18 +132,19 @@ test('modify chained files', async () => {
//// [|/*marker*/foo1()|]
`;
const basePath = normalizeSlashes('/');
const basePath = Uri.file(normalizeSlashes('/'));
const { data, service } = createServiceWithChainedSourceFiles(basePath, code);
const marker = data.markerPositions.get('marker')!;
const markerUri = Uri.file(marker.fileName);
const range = data.ranges.find((r) => r.marker === marker)!;
const parseResults = service.getParseResult(marker.fileName)!;
const parseResults = service.getParseResult(markerUri)!;
analyze(service.test_program);
// Initially, there should be no error.
const initialDiags = await service.getDiagnosticsForRange(
marker.fileName,
markerUri,
convertOffsetsToRange(range.pos, range.end, parseResults.tokenizerOutput.lines),
CancellationToken.None
);
@ -148,11 +152,11 @@ test('modify chained files', async () => {
assert.strictEqual(initialDiags.length, 0);
// Change test1 content
service.updateOpenFileContents(data.markerPositions.get('changed')!.fileName, 2, 'def foo5(): pass');
service.updateOpenFileContents(Uri.file(data.markerPositions.get('changed')!.fileName), 2, 'def foo5(): pass');
analyze(service.test_program);
const finalDiags = await service.getDiagnosticsForRange(
marker.fileName,
markerUri,
convertOffsetsToRange(range.pos, range.end, parseResults.tokenizerOutput.lines),
CancellationToken.None
);
@ -178,17 +182,18 @@ test('chained files with 1000s of files', async () => {
//// [|/*marker*/foo1()|]
`;
const code = generateChainedFiles(1000, lastFile);
const basePath = normalizeSlashes('/');
const basePath = Uri.file(normalizeSlashes('/'));
const { data, service } = createServiceWithChainedSourceFiles(basePath, code);
const marker = data.markerPositions.get('marker')!;
const markerUri = Uri.file(marker.fileName);
const range = data.ranges.find((r) => r.marker === marker)!;
const parseResults = service.getParseResult(marker.fileName)!;
const parseResults = service.getParseResult(markerUri)!;
analyze(service.test_program);
// There should be no error as it should find the foo1 in the first chained file.
const initialDiags = await service.getDiagnosticsForRange(
marker.fileName,
markerUri,
convertOffsetsToRange(range.pos, range.end, parseResults.tokenizerOutput.lines),
CancellationToken.None
);
@ -205,16 +210,17 @@ test('imported by files', async () => {
//// os.path.join()
`;
const basePath = normalizeSlashes('/');
const basePath = Uri.file(normalizeSlashes('/'));
const { data, service } = createServiceWithChainedSourceFiles(basePath, code);
analyze(service.test_program);
const marker = data.markerPositions.get('marker')!;
const markerUri = Uri.file(marker.fileName);
const range = data.ranges.find((r) => r.marker === marker)!;
const parseResults = service.getParseResult(marker.fileName)!;
const parseResults = service.getParseResult(markerUri)!;
const diagnostics = await service.getDiagnosticsForRange(
marker.fileName,
markerUri,
convertOffsetsToRange(range.pos, range.end, parseResults.tokenizerOutput.lines),
CancellationToken.None
);
@ -231,22 +237,24 @@ test('re ordering cells', async () => {
//// /*bottom*/os.path.join()
`;
const basePath = normalizeSlashes('/');
const basePath = Uri.file(normalizeSlashes('/'));
const { data, service } = createServiceWithChainedSourceFiles(basePath, code);
analyze(service.test_program);
const marker = data.markerPositions.get('marker')!;
const markerUri = Uri.file(marker.fileName);
const range = data.ranges.find((r) => r.marker === marker)!;
const bottom = data.markerPositions.get('bottom')!;
const bottomUri = Uri.file(bottom.fileName);
service.updateChainedFilePath(bottom.fileName, undefined);
service.updateChainedFilePath(marker.fileName, bottom.fileName);
service.updateChainedUri(bottomUri, undefined);
service.updateChainedUri(markerUri, bottomUri);
analyze(service.test_program);
const parseResults = service.getParseResult(marker.fileName)!;
const parseResults = service.getParseResult(markerUri)!;
const diagnostics = await service.getDiagnosticsForRange(
marker.fileName,
markerUri,
convertOffsetsToRange(range.pos, range.end, parseResults.tokenizerOutput.lines),
CancellationToken.None
);
@ -254,22 +262,23 @@ test('re ordering cells', async () => {
assert.strictEqual(diagnostics.length, 1);
});
function createServiceWithChainedSourceFiles(basePath: string, code: string) {
const fs = createFromFileSystem(host.HOST, /*ignoreCase*/ false, { cwd: basePath });
function createServiceWithChainedSourceFiles(basePath: Uri, code: string) {
const fs = createFromFileSystem(host.HOST, /*ignoreCase*/ false, { cwd: basePath.getFilePath() });
const service = new AnalyzerService('test service', new ServiceProvider(), {
console: new NullConsole(),
hostFactory: () => new TestAccessHost(vfs.MODULE_PATH, [libFolder, distlibFolder]),
hostFactory: () => new TestAccessHost(Uri.file(vfs.MODULE_PATH), [libFolder, distlibFolder]),
importResolverFactory: AnalyzerService.createImportResolver,
configOptions: new ConfigOptions(basePath),
fileSystem: fs,
});
const data = parseTestData(basePath, code, '');
const data = parseTestData(basePath.getFilePath(), code, '');
let chainedFilePath: string | undefined;
let chainedFilePath: Uri | undefined;
for (const file of data.files) {
service.setFileOpened(file.fileName, 1, file.content, IPythonMode.CellDocs, chainedFilePath);
chainedFilePath = file.fileName;
const uri = Uri.file(file.fileName);
service.setFileOpened(uri, 1, file.content, IPythonMode.CellDocs, chainedFilePath);
chainedFilePath = uri;
}
return { data, service };
}

View File

@ -11,6 +11,7 @@
import { ConfigOptions } from '../common/configOptions';
import { PythonVersion } from '../common/pythonVersion';
import { Uri } from '../common/uri/uri';
import * as TestUtils from './testUtils';
test('BadToken1', () => {
@ -34,7 +35,7 @@ test('CircularBaseClass', () => {
});
test('Private1', () => {
const configOptions = new ConfigOptions('.');
const configOptions = new ConfigOptions(Uri.empty());
// By default, optional diagnostics are ignored.
let analysisResults = TestUtils.typeAnalyzeSampleFiles(['private1.py'], configOptions);
@ -47,7 +48,7 @@ test('Private1', () => {
});
test('Constant1', () => {
const configOptions = new ConfigOptions('.');
const configOptions = new ConfigOptions(Uri.empty());
// By default, optional diagnostics are ignored.
let analysisResults = TestUtils.typeAnalyzeSampleFiles(['constant1.py'], configOptions);
@ -168,7 +169,7 @@ test('With3', () => {
});
test('With4', () => {
const configOptions = new ConfigOptions('.');
const configOptions = new ConfigOptions(Uri.empty());
configOptions.defaultPythonVersion = PythonVersion.V3_8;
const analysisResults1 = TestUtils.typeAnalyzeSampleFiles(['with4.py'], configOptions);
@ -216,7 +217,7 @@ test('Mro4', () => {
});
test('DefaultInitializer1', () => {
const configOptions = new ConfigOptions('.');
const configOptions = new ConfigOptions(Uri.empty());
// By default, the reportCallInDefaultInitializer is disabled.
let analysisResults = TestUtils.typeAnalyzeSampleFiles(['defaultInitializer1.py'], configOptions);
@ -229,7 +230,7 @@ test('DefaultInitializer1', () => {
});
test('UnnecessaryIsInstance1', () => {
const configOptions = new ConfigOptions('.');
const configOptions = new ConfigOptions(Uri.empty());
let analysisResults = TestUtils.typeAnalyzeSampleFiles(['unnecessaryIsInstance1.py'], configOptions);
TestUtils.validateResults(analysisResults, 1);
@ -241,7 +242,7 @@ test('UnnecessaryIsInstance1', () => {
});
test('UnnecessaryIsSubclass1', () => {
const configOptions = new ConfigOptions('.');
const configOptions = new ConfigOptions(Uri.empty());
let analysisResults = TestUtils.typeAnalyzeSampleFiles(['unnecessaryIsSubclass1.py'], configOptions);
TestUtils.validateResults(analysisResults, 0);
@ -253,7 +254,7 @@ test('UnnecessaryIsSubclass1', () => {
});
test('UnnecessaryCast1', () => {
const configOptions = new ConfigOptions('.');
const configOptions = new ConfigOptions(Uri.empty());
let analysisResults = TestUtils.typeAnalyzeSampleFiles(['unnecessaryCast1.py'], configOptions);
TestUtils.validateResults(analysisResults, 0);
@ -265,7 +266,7 @@ test('UnnecessaryCast1', () => {
});
test('UnnecessaryContains1', () => {
const configOptions = new ConfigOptions('.');
const configOptions = new ConfigOptions(Uri.empty());
let analysisResults = TestUtils.typeAnalyzeSampleFiles(['unnecessaryContains1.py'], configOptions);
TestUtils.validateResults(analysisResults, 0);
@ -277,7 +278,7 @@ test('UnnecessaryContains1', () => {
});
test('TypeIgnore1', () => {
const configOptions = new ConfigOptions('.');
const configOptions = new ConfigOptions(Uri.empty());
let analysisResults = TestUtils.typeAnalyzeSampleFiles(['typeIgnore1.py'], configOptions);
TestUtils.validateResults(analysisResults, 0);
@ -289,7 +290,7 @@ test('TypeIgnore1', () => {
});
test('TypeIgnore2', () => {
const configOptions = new ConfigOptions('.');
const configOptions = new ConfigOptions(Uri.empty());
let analysisResults = TestUtils.typeAnalyzeSampleFiles(['typeIgnore2.py'], configOptions);
TestUtils.validateResults(analysisResults, 0);
@ -301,7 +302,7 @@ test('TypeIgnore2', () => {
});
test('TypeIgnore3', () => {
const configOptions = new ConfigOptions('.');
const configOptions = new ConfigOptions(Uri.empty());
let analysisResults = TestUtils.typeAnalyzeSampleFiles(['typeIgnore3.py'], configOptions);
TestUtils.validateResults(analysisResults, 0);
@ -313,7 +314,7 @@ test('TypeIgnore3', () => {
});
test('TypeIgnore4', () => {
const configOptions = new ConfigOptions('.');
const configOptions = new ConfigOptions(Uri.empty());
let analysisResults = TestUtils.typeAnalyzeSampleFiles(['typeIgnore4.py'], configOptions);
TestUtils.validateResults(analysisResults, 0);
@ -324,7 +325,7 @@ test('TypeIgnore4', () => {
});
test('TypeIgnore5', () => {
const configOptions = new ConfigOptions('.');
const configOptions = new ConfigOptions(Uri.empty());
let analysisResults = TestUtils.typeAnalyzeSampleFiles(['typeIgnore5.py'], configOptions);
TestUtils.validateResults(analysisResults, 0);
@ -335,14 +336,14 @@ test('TypeIgnore5', () => {
});
test('PyrightIgnore1', () => {
const configOptions = new ConfigOptions('.');
const configOptions = new ConfigOptions(Uri.empty());
const analysisResults = TestUtils.typeAnalyzeSampleFiles(['pyrightIgnore1.py'], configOptions);
TestUtils.validateResults(analysisResults, 1);
});
test('PyrightIgnore2', () => {
const configOptions = new ConfigOptions('.');
const configOptions = new ConfigOptions(Uri.empty());
let analysisResults = TestUtils.typeAnalyzeSampleFiles(['pyrightIgnore2.py'], configOptions);
TestUtils.validateResults(analysisResults, 2);
@ -353,14 +354,14 @@ test('PyrightIgnore2', () => {
});
test('PyrightComment1', () => {
const configOptions = new ConfigOptions('.');
const configOptions = new ConfigOptions(Uri.empty());
const analysisResults = TestUtils.typeAnalyzeSampleFiles(['pyrightComment1.py'], configOptions);
TestUtils.validateResults(analysisResults, 9);
});
test('DuplicateImports1', () => {
const configOptions = new ConfigOptions('.');
const configOptions = new ConfigOptions(Uri.empty());
// By default, optional diagnostics are ignored.
let analysisResults = TestUtils.typeAnalyzeSampleFiles(['duplicateImports1.py'], configOptions);
@ -373,7 +374,7 @@ test('DuplicateImports1', () => {
});
test('ParamNames1', () => {
const configOptions = new ConfigOptions('.');
const configOptions = new ConfigOptions(Uri.empty());
let analysisResults = TestUtils.typeAnalyzeSampleFiles(['paramNames1.py'], configOptions);
TestUtils.validateResults(analysisResults, 0, 7);
@ -423,7 +424,7 @@ test('DuplicateDeclaration2', () => {
});
test('Strings1', () => {
const configOptions = new ConfigOptions('.');
const configOptions = new ConfigOptions(Uri.empty());
const analysisResults1 = TestUtils.typeAnalyzeSampleFiles(['strings1.py'], configOptions);
TestUtils.validateResults(analysisResults1, 0);
@ -433,7 +434,7 @@ test('Strings1', () => {
});
test('UnusedExpression1', () => {
const configOptions = new ConfigOptions('.');
const configOptions = new ConfigOptions(Uri.empty());
// By default, this is a warning.
let analysisResults = TestUtils.typeAnalyzeSampleFiles(['unusedExpression1.py'], configOptions);
@ -451,7 +452,7 @@ test('UnusedExpression1', () => {
});
test('UnusedImport1', () => {
const configOptions = new ConfigOptions('.');
const configOptions = new ConfigOptions(Uri.empty());
// Enabled it
configOptions.diagnosticRuleSet.reportUnusedImport = 'warning';
@ -470,7 +471,7 @@ test('UnusedImport1', () => {
});
test('UninitializedVariable1', () => {
const configOptions = new ConfigOptions('.');
const configOptions = new ConfigOptions(Uri.empty());
// By default, this is off.
let analysisResults = TestUtils.typeAnalyzeSampleFiles(['uninitializedVariable1.py'], configOptions);
@ -483,7 +484,7 @@ test('UninitializedVariable1', () => {
});
test('UninitializedVariable2', () => {
const configOptions = new ConfigOptions('.');
const configOptions = new ConfigOptions(Uri.empty());
// By default, this is off.
let analysisResults = TestUtils.typeAnalyzeSampleFiles(['uninitializedVariable2.py'], configOptions);
@ -502,7 +503,7 @@ test('RegionComments1', () => {
});
test('Deprecated1', () => {
const configOptions = new ConfigOptions('.');
const configOptions = new ConfigOptions(Uri.empty());
configOptions.defaultPythonVersion = PythonVersion.V3_8;
const analysisResults1 = TestUtils.typeAnalyzeSampleFiles(['deprecated1.py'], configOptions);
@ -548,7 +549,7 @@ test('Deprecated1', () => {
});
test('Deprecated2', () => {
const configOptions = new ConfigOptions('.');
const configOptions = new ConfigOptions(Uri.empty());
const analysisResults1 = TestUtils.typeAnalyzeSampleFiles(['deprecated2.py'], configOptions);
TestUtils.validateResults(analysisResults1, 0, 0, 0, undefined, undefined, 12);
@ -559,7 +560,7 @@ test('Deprecated2', () => {
});
test('Deprecated3', () => {
const configOptions = new ConfigOptions('.');
const configOptions = new ConfigOptions(Uri.empty());
const analysisResults1 = TestUtils.typeAnalyzeSampleFiles(['deprecated3.py'], configOptions);
TestUtils.validateResults(analysisResults1, 0, 0, 0, undefined, undefined, 5);
@ -570,7 +571,7 @@ test('Deprecated3', () => {
});
test('Deprecated4', () => {
const configOptions = new ConfigOptions('.');
const configOptions = new ConfigOptions(Uri.empty());
const analysisResults1 = TestUtils.typeAnalyzeSampleFiles(['deprecated4.py'], configOptions);
TestUtils.validateResults(analysisResults1, 0, 0, 0, undefined, undefined, 6);
@ -581,7 +582,7 @@ test('Deprecated4', () => {
});
test('Deprecated5', () => {
const configOptions = new ConfigOptions('.');
const configOptions = new ConfigOptions(Uri.empty());
const analysisResults1 = TestUtils.typeAnalyzeSampleFiles(['deprecated5.py'], configOptions);
TestUtils.validateResults(analysisResults1, 0, 0, 0, undefined, undefined, 2);

View File

@ -8,6 +8,7 @@ import assert from 'assert';
import { CancellationToken } from 'vscode-languageserver';
import { CompletionItemKind, MarkupKind } from 'vscode-languageserver-types';
import { Uri } from '../common/uri/uri';
import { CompletionOptions, CompletionProvider } from '../languageService/completionProvider';
import { parseAndGetTestState } from './harness/fourslash/testState';
@ -798,6 +799,7 @@ test('completion quote trigger', async () => {
const state = parseAndGetTestState(code).state;
const marker = state.getMarkerByName('marker');
const filePath = marker.fileName;
const uri = Uri.file(filePath);
const position = state.convertOffsetToPosition(filePath, marker.position);
const options: CompletionOptions = {
@ -809,8 +811,8 @@ test('completion quote trigger', async () => {
const result = new CompletionProvider(
state.program,
state.workspace.rootPath,
filePath,
state.workspace.rootUri,
uri,
position,
options,
CancellationToken.None
@ -836,6 +838,7 @@ test('completion quote trigger - middle', async () => {
const state = parseAndGetTestState(code).state;
const marker = state.getMarkerByName('marker');
const filePath = marker.fileName;
const uri = Uri.file(filePath);
const position = state.convertOffsetToPosition(filePath, marker.position);
const options: CompletionOptions = {
@ -847,8 +850,8 @@ test('completion quote trigger - middle', async () => {
const result = new CompletionProvider(
state.program,
state.workspace.rootPath,
filePath,
state.workspace.rootUri,
uri,
position,
options,
CancellationToken.None
@ -882,6 +885,7 @@ test('auto import sort text', async () => {
while (state.workspace.service.test_program.analyze());
const filePath = marker.fileName;
const uri = Uri.file(filePath);
const position = state.convertOffsetToPosition(filePath, marker.position);
const options: CompletionOptions = {
@ -892,8 +896,8 @@ test('auto import sort text', async () => {
const result = new CompletionProvider(
state.program,
state.workspace.rootPath,
filePath,
state.workspace.rootUri,
uri,
position,
options,
CancellationToken.None

View File

@ -15,10 +15,11 @@ import { CommandLineOptions } from '../common/commandLineOptions';
import { ConfigOptions, ExecutionEnvironment } from '../common/configOptions';
import { ConsoleInterface, NullConsole } from '../common/console';
import { NoAccessHost } from '../common/host';
import { combinePaths, getBaseFileName, normalizePath, normalizeSlashes, realCasePath } from '../common/pathUtils';
import { combinePaths, normalizePath, normalizeSlashes } from '../common/pathUtils';
import { PythonVersion } from '../common/pythonVersion';
import { createFromRealFileSystem } from '../common/realFileSystem';
import { createServiceProvider } from '../common/serviceProviderExtensions';
import { Uri } from '../common/uri/uri';
import { TestAccessHost } from './harness/testAccessHost';
import { TestFileSystem } from './harness/vfs/filesystem';
@ -41,8 +42,8 @@ test('FindFilesWithConfigFile', () => {
// The config file specifies a single file spec (a directory).
assert.strictEqual(configOptions.include.length, 1, `failed creating options from ${cwd}`);
assert.strictEqual(
normalizeSlashes(configOptions.projectRoot),
realCasePath(combinePaths(cwd, commandLineOptions.configFilePath), service.fs)
configOptions.projectRoot.key,
service.fs.realCasePath(Uri.file(combinePaths(cwd, commandLineOptions.configFilePath))).key
);
const fileList = service.test_getFileNamesFromFileSpecs();
@ -67,7 +68,7 @@ test('FindFilesVirtualEnvAutoDetectExclude', () => {
// There are 3 python files in the workspace, outside of myvenv
// There is 1 python file in myvenv, which should be excluded
const fileNames = fileList.map((p) => getBaseFileName(p)).sort();
const fileNames = fileList.map((p) => p.fileName).sort();
assert.deepStrictEqual(fileNames, ['sample1.py', 'sample2.py', 'sample3.py']);
});
@ -85,7 +86,7 @@ test('FindFilesVirtualEnvAutoDetectInclude', () => {
// There are 3 python files in the workspace, outside of myvenv
// There is 1 more python file in excluded folder
// There is 1 python file in myvenv, which should be included
const fileNames = fileList.map((p) => getBaseFileName(p)).sort();
const fileNames = fileList.map((p) => p.fileName).sort();
assert.deepStrictEqual(fileNames, ['library1.py', 'sample1.py', 'sample2.py', 'sample3.py']);
});
@ -132,8 +133,8 @@ test('SomeFileSpecsAreInvalid', () => {
assert.strictEqual(configOptions.include.length, 4, `failed creating options from ${cwd}`);
assert.strictEqual(configOptions.exclude.length, 1);
assert.strictEqual(
normalizeSlashes(configOptions.projectRoot),
realCasePath(combinePaths(cwd, commandLineOptions.configFilePath), service.fs)
configOptions.projectRoot.getFilePath(),
service.fs.realCasePath(Uri.file(combinePaths(cwd, commandLineOptions.configFilePath))).getFilePath()
);
const fileList = service.test_getFileNamesFromFileSpecs();
@ -157,13 +158,13 @@ test('ConfigBadJson', () => {
});
test('FindExecEnv1', () => {
const cwd = normalizePath(process.cwd());
const cwd = Uri.file(normalizePath(process.cwd()));
const configOptions = new ConfigOptions(cwd);
// Build a config option with three execution environments.
const execEnv1 = new ExecutionEnvironment(
'python',
'src/foo',
cwd.combinePaths('src/foo'),
/* defaultPythonVersion */ undefined,
/* defaultPythonPlatform */ undefined,
/* defaultExtraPaths */ undefined
@ -171,28 +172,29 @@ test('FindExecEnv1', () => {
configOptions.executionEnvironments.push(execEnv1);
const execEnv2 = new ExecutionEnvironment(
'python',
'src',
cwd.combinePaths('src'),
/* defaultPythonVersion */ undefined,
/* defaultPythonPlatform */ undefined,
/* defaultExtraPaths */ undefined
);
configOptions.executionEnvironments.push(execEnv2);
const file1 = normalizeSlashes(combinePaths(cwd, 'src/foo/bar.py'));
const file1 = cwd.combinePaths('src/foo/bar.py');
assert.strictEqual(configOptions.findExecEnvironment(file1), execEnv1);
const file2 = normalizeSlashes(combinePaths(cwd, 'src/foo2/bar.py'));
const file2 = cwd.combinePaths('src/foo2/bar.py');
assert.strictEqual(configOptions.findExecEnvironment(file2), execEnv2);
// If none of the execution environments matched, we should get
// a default environment with the root equal to that of the config.
const file4 = '/nothing/bar.py';
const file4 = Uri.file('/nothing/bar.py');
const defaultExecEnv = configOptions.findExecEnvironment(file4);
assert(defaultExecEnv.root);
assert.strictEqual(normalizeSlashes(defaultExecEnv.root), normalizeSlashes(configOptions.projectRoot));
const rootFilePath = Uri.isUri(defaultExecEnv.root) ? defaultExecEnv.root.getFilePath() : defaultExecEnv.root;
assert.strictEqual(normalizeSlashes(rootFilePath), normalizeSlashes(configOptions.projectRoot.getFilePath()));
});
test('PythonPlatform', () => {
const cwd = normalizePath(process.cwd());
const cwd = Uri.file(normalizePath(process.cwd()));
const configOptions = new ConfigOptions(cwd);
@ -216,16 +218,16 @@ test('PythonPlatform', () => {
});
test('AutoSearchPathsOn', () => {
const cwd = normalizePath(combinePaths(process.cwd(), 'src/tests/samples/project_src'));
const cwd = Uri.file(normalizePath(combinePaths(process.cwd(), 'src/tests/samples/project_src')));
const nullConsole = new NullConsole();
const service = createAnalyzer(nullConsole);
const commandLineOptions = new CommandLineOptions(cwd, /* fromVsCodeExtension */ false);
const commandLineOptions = new CommandLineOptions(cwd.getFilePath(), /* fromVsCodeExtension */ false);
commandLineOptions.autoSearchPaths = true;
service.setOptions(commandLineOptions);
const configOptions = service.test_getConfigOptions(commandLineOptions);
const expectedExtraPaths = [realCasePath(combinePaths(cwd, 'src'), service.fs)];
const expectedExtraPaths = [service.fs.realCasePath(cwd.combinePaths('src'))];
assert.deepStrictEqual(configOptions.defaultExtraPaths, expectedExtraPaths);
});
@ -274,19 +276,22 @@ test('AutoSearchPathsOnWithConfigExecEnv', () => {
});
test('AutoSearchPathsOnAndExtraPaths', () => {
const cwd = normalizePath(combinePaths(process.cwd(), 'src/tests/samples/project_src_with_config_no_extra_paths'));
const nullConsole = new NullConsole();
const service = createAnalyzer(nullConsole);
const commandLineOptions = new CommandLineOptions(cwd, /* fromVsCodeExtension */ false);
const cwd = Uri.file(
normalizePath(combinePaths(process.cwd(), 'src/tests/samples/project_src_with_config_no_extra_paths')),
service.fs.isCaseSensitive
);
const commandLineOptions = new CommandLineOptions(cwd.getFilePath(), /* fromVsCodeExtension */ false);
commandLineOptions.autoSearchPaths = true;
commandLineOptions.extraPaths = ['src/_vendored'];
service.setOptions(commandLineOptions);
const configOptions = service.test_getConfigOptions(commandLineOptions);
const expectedExtraPaths: string[] = [
realCasePath(combinePaths(cwd, 'src'), service.fs),
realCasePath(combinePaths(cwd, 'src', '_vendored'), service.fs),
const expectedExtraPaths: Uri[] = [
service.fs.realCasePath(cwd.combinePaths('src')),
service.fs.realCasePath(cwd.combinePaths('src', '_vendored')),
];
assert.deepStrictEqual(configOptions.defaultExtraPaths, expectedExtraPaths);
@ -314,11 +319,11 @@ test('FindFilesInMemoryOnly', () => {
service.setOptions(commandLineOptions);
// Open a file that is not backed by the file system.
const untitled = 'untitled:Untitled-1.py';
const untitled = Uri.parse('untitled:Untitled-1.py', true);
service.setFileOpened(untitled, 1, '# empty');
const fileList = service.test_getFileNamesFromFileSpecs();
assert(fileList.filter((f) => f === untitled));
assert(fileList.filter((f) => f.equals(untitled)));
});
test('verify config fileSpecs after cloning', () => {
@ -327,7 +332,7 @@ test('verify config fileSpecs after cloning', () => {
ignore: ['**/node_modules/**'],
};
const config = new ConfigOptions(process.cwd());
const config = new ConfigOptions(Uri.file(process.cwd()));
const sp = createServiceProvider(fs, new NullConsole());
config.initializeFromJson(configFile, undefined, sp, new TestAccessHost());
const cloned = createConfigOptionsFrom(config);

View File

@ -6,6 +6,7 @@
* Tests documentSymbolCollector
*/
import { Uri } from '../common/uri/uri';
import { parseAndGetTestState } from './harness/fourslash/testState';
import { verifyReferencesAtPosition } from './testStateUtils';
@ -225,7 +226,7 @@ test('use localName import alias', () => {
const references = state
.getRangesByText()
.get('tools')!
.map((r) => ({ path: r.fileName, range: state.convertPositionRange(r) }));
.map((r) => ({ uri: Uri.file(r.fileName), range: state.convertPositionRange(r) }));
state.verifyFindAllReferences({
marker1: { references },
@ -288,7 +289,7 @@ test('use localName import module', () => {
const references = state
.getRangesByText()
.get('tools')!
.map((r) => ({ path: r.fileName, range: state.convertPositionRange(r) }));
.map((r) => ({ uri: Uri.file(r.fileName), range: state.convertPositionRange(r) }));
state.verifyFindAllReferences({
marker1: { references },

View File

@ -9,6 +9,7 @@
import assert from 'assert';
import { combinePaths, normalizeSlashes } from '../common/pathUtils';
import { Uri } from '../common/uri/uri';
import * as host from './harness/testHost';
import * as factory from './harness/vfs/factory';
import * as vfs from './harness/vfs/filesystem';
@ -20,54 +21,55 @@ test('CreateVFS', () => {
});
test('Folders', () => {
const cwd = normalizeSlashes('/');
const fs = new vfs.TestFileSystem(/*ignoreCase*/ true, { cwd });
const cwd = Uri.file(normalizeSlashes('/'));
const fs = new vfs.TestFileSystem(/*ignoreCase*/ true, { cwd: cwd.getFilePath() });
// no such dir exist
assert.throws(() => {
fs.chdir('a');
fs.chdir(cwd.combinePaths('a'));
});
fs.mkdirSync('a');
fs.chdir('a');
fs.mkdirSync(cwd.combinePaths('a'));
fs.chdir(cwd.combinePaths('a'));
assert.equal(fs.cwd(), normalizeSlashes('/a'));
fs.chdir('..');
fs.rmdirSync('a');
fs.chdir(cwd.combinePaths('..'));
fs.rmdirSync(cwd.combinePaths('a'));
// no such dir exist
assert.throws(() => {
fs.chdir('a');
fs.chdir(cwd.combinePaths('a'));
});
});
test('Folders Recursive', () => {
const cwd = normalizeSlashes('/');
const fs = new vfs.TestFileSystem(/*ignoreCase*/ true, { cwd });
const cwd = Uri.file(normalizeSlashes('/'));
const fs = new vfs.TestFileSystem(/*ignoreCase*/ true, { cwd: cwd.getFilePath() });
// no such dir exist
assert.throws(() => {
fs.chdir('a');
fs.chdir(cwd.combinePaths('a'));
});
const path = combinePaths('/', 'a', 'b', 'c');
const path = cwd.combinePaths('a', 'b', 'c');
fs.mkdirSync(path, { recursive: true });
assert(fs.existsSync(path));
});
test('Files', () => {
const cwd = normalizeSlashes('/');
const fs = new vfs.TestFileSystem(/*ignoreCase*/ true, { cwd });
const cwd = Uri.file(normalizeSlashes('/'));
const fs = new vfs.TestFileSystem(/*ignoreCase*/ true, { cwd: cwd.getFilePath() });
fs.writeFileSync('1.txt', 'hello', 'utf8');
const buffer1 = fs.readFileSync('1.txt');
const uri = cwd.combinePaths('1.txt');
fs.writeFileSync(uri, 'hello', 'utf8');
const buffer1 = fs.readFileSync(uri);
assert.equal(buffer1.toString(), 'hello');
const p = normalizeSlashes('a/b/c');
fs.mkdirpSync(p);
const p = cwd.combinePaths('a/b/c');
fs.mkdirpSync(p.getFilePath());
const f = combinePaths(p, '2.txt');
const f = p.combinePaths('2.txt');
fs.writeFileSync(f, 'hi');
const str = fs.readFileSync(f, 'utf8');
@ -90,11 +92,11 @@ test('CreateRich', () => {
// files + directory + root
assert.equal(entries.length, 10);
assert.equal(fs.readFileSync(normalizeSlashes('/a/b/c/1.txt'), 'ascii'), 'hello1');
assert.equal(fs.readFileSync(normalizeSlashes('/a/b/2.txt'), 'utf8'), 'hello2');
assert.equal(fs.readFileSync(normalizeSlashes('/a/3.txt'), 'utf-8'), 'hello3');
assert.equal(fs.readFileSync(normalizeSlashes('/4.txt'), 'utf16le'), 'hello4');
assert.equal(fs.readFileSync(normalizeSlashes('/a/c/5.txt'), 'ucs2'), 'hello5');
assert.equal(fs.readFileSync(Uri.file(normalizeSlashes('/a/b/c/1.txt')), 'ascii'), 'hello1');
assert.equal(fs.readFileSync(Uri.file(normalizeSlashes('/a/b/2.txt')), 'utf8'), 'hello2');
assert.equal(fs.readFileSync(Uri.file(normalizeSlashes('/a/3.txt')), 'utf-8'), 'hello3');
assert.equal(fs.readFileSync(Uri.file(normalizeSlashes('/4.txt')), 'utf16le'), 'hello4');
assert.equal(fs.readFileSync(Uri.file(normalizeSlashes('/a/c/5.txt')), 'ucs2'), 'hello5');
});
test('Shadow', () => {
@ -124,19 +126,19 @@ test('Shadow', () => {
});
test('Diffing', () => {
const cwd = normalizeSlashes('/');
const fs = new vfs.TestFileSystem(/*ignoreCase*/ true, { cwd });
const cwd = Uri.file(normalizeSlashes('/'));
const fs = new vfs.TestFileSystem(/*ignoreCase*/ true, { cwd: cwd.getFilePath() });
// first snapshot
fs.snapshot();
fs.writeFileSync('test1.txt', 'hello1');
fs.writeFileSync(cwd.combinePaths('test1.txt'), 'hello1');
// compared with original
assert.equal(countFile(fs.diff()!), 1);
// second snapshot
fs.snapshot();
fs.writeFileSync('test2.txt', 'hello2');
fs.writeFileSync(cwd.combinePaths('test2.txt'), 'hello2');
// compared with first snapshot
assert.equal(countFile(fs.diff()!), 1);
@ -148,11 +150,11 @@ test('Diffing', () => {
const s = fs.shadowRoot!.shadow();
// "test2.txt" only exist in first snapshot
assert(!s.existsSync('test2.txt'));
assert(!s.existsSync(cwd.combinePaths('test2.txt')));
// create parallel universe where it has another version of test2.txt with different content
// compared to second snapshot which forked from same first snapshot
s.writeFileSync('test2.txt', 'hello3');
s.writeFileSync(cwd.combinePaths('test2.txt'), 'hello3');
// diff between non direct snapshots
// diff gives test2.txt even though it exist in both snapshot
@ -170,16 +172,16 @@ test('createFromFileSystem1', () => {
});
// check existing typeshed folder on virtual path inherited from base snapshot from physical file system
const entries = fs.readdirSync(factory.typeshedFolder);
const entries = fs.readdirSync(Uri.file(factory.typeshedFolder));
assert(entries.length > 0);
// confirm file
assert.equal(fs.readFileSync(filepath, 'utf8'), content);
assert.equal(fs.readFileSync(Uri.file(filepath), 'utf8'), content);
});
test('createFromFileSystem2', () => {
const fs = factory.createFromFileSystem(host.HOST, /* ignoreCase */ true, { cwd: factory.srcFolder });
const entries = fs.readdirSync(factory.typeshedFolder.toUpperCase());
const entries = fs.readdirSync(Uri.file(factory.typeshedFolder.toUpperCase()));
assert(entries.length > 0);
});
@ -190,7 +192,7 @@ test('createFromFileSystemWithCustomTypeshedPath', () => {
meta: { [factory.typeshedFolder]: invalidpath },
});
const entries = fs.readdirSync(factory.typeshedFolder);
const entries = fs.readdirSync(Uri.file(factory.typeshedFolder));
assert(entries.filter((e) => e.endsWith('.md')).length > 0);
});
@ -200,7 +202,7 @@ test('createFromFileSystemWithMetadata', () => {
meta: { unused: 'unused' },
});
assert(fs.existsSync(factory.srcFolder));
assert(fs.existsSync(Uri.file(factory.srcFolder)));
});
function countFile(files: vfs.FileSet): number {

View File

@ -9,8 +9,9 @@
import assert from 'assert';
import { combinePaths, getBaseFileName, normalizeSlashes } from '../common/pathUtils';
import { getBaseFileName, normalizeSlashes } from '../common/pathUtils';
import { compareStringsCaseSensitive } from '../common/stringUtils';
import { Uri } from '../common/uri/uri';
import { parseTestData } from './harness/fourslash/fourSlashParser';
import { CompilerSettings } from './harness/fourslash/fourSlashTypes';
import * as host from './harness/testHost';
@ -89,7 +90,7 @@ test('Library options', () => {
const data = parseTestData('.', code, 'test.py');
assert.equal(data.files[0].fileName, normalizeSlashes(combinePaths(factory.libFolder, 'file1.py')));
assert.equal(data.files[0].fileName, factory.libFolder.combinePaths('file1.py').getFilePath());
});
test('Range', () => {
@ -222,8 +223,7 @@ test('Multiple Files', () => {
assert.equal(data.files.filter((f) => f.fileName === normalizeSlashes('./src/A.py'))[0].content, getContent('A'));
assert.equal(
data.files.filter((f) => f.fileName === normalizeSlashes(combinePaths(factory.libFolder, 'src/B.py')))[0]
.content,
data.files.filter((f) => f.fileName === factory.libFolder.combinePaths('src/B.py').getFilePath())[0].content,
getContent('B')
);
assert.equal(data.files.filter((f) => f.fileName === normalizeSlashes('./src/C.py'))[0].content, getContent('C'));
@ -312,7 +312,10 @@ test('fourSlashWithFileSystem', () => {
});
for (const file of data.files) {
assert.equal(fs.readFileSync(file.fileName, 'utf8'), getContent(getBaseFileName(file.fileName, '.py', false)));
assert.equal(
fs.readFileSync(Uri.file(file.fileName), 'utf8'),
getContent(getBaseFileName(file.fileName, '.py', false))
);
}
});

View File

@ -17,13 +17,13 @@ import {
} from '../../../common/pathUtils';
import { distlibFolder, libFolder } from '../vfs/factory';
import {
fileMetadataNames,
FourSlashData,
FourSlashFile,
GlobalMetadataOptionNames,
Marker,
MetadataOptionNames,
Range,
fileMetadataNames,
} from './fourSlashTypes';
/**
@ -70,13 +70,13 @@ export function parseTestData(basePath: string, contents: string, fileName: stri
if (toBoolean(currentFileOptions[MetadataOptionNames.library])) {
currentFileName = normalizePath(
combinePaths(libFolder, getRelativePath(currentFileName, normalizedBasePath))
combinePaths(libFolder.getFilePath(), getRelativePath(currentFileName, normalizedBasePath))
);
}
if (toBoolean(currentFileOptions[MetadataOptionNames.distLibrary])) {
currentFileName = normalizePath(
combinePaths(distlibFolder, getRelativePath(currentFileName, normalizedBasePath))
combinePaths(distlibFolder.getFilePath(), getRelativePath(currentFileName, normalizedBasePath))
);
}

Some files were not shown because too many files have changed in this diff Show More