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.formatOnSave": true
}, },
"editor.codeActionsOnSave": { "editor.codeActionsOnSave": {
"source.fixAll.eslint": true, "source.fixAll.eslint": "explicit",
"source.organizeImports": true "source.organizeImports": "explicit"
}, },
"typescript.tsdk": "node_modules/typescript/lib", "typescript.tsdk": "node_modules/typescript/lib",
} }

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -10,6 +10,7 @@
*/ */
import { Range } from '../common/textRange'; import { Range } from '../common/textRange';
import { Uri } from '../common/uri/uri';
import { import {
ClassNode, ClassNode,
ExpressionNode, ExpressionNode,
@ -31,7 +32,7 @@ import {
YieldNode, YieldNode,
} from '../parser/parseNodes'; } from '../parser/parseNodes';
export const UnresolvedModuleMarker = '*** unresolved ***'; export const UnresolvedModuleMarker = Uri.parse('unresolved-module-marker://**/*', /* isCaseSensitive */ true);
export const enum DeclarationType { export const enum DeclarationType {
Intrinsic, Intrinsic,
@ -57,9 +58,9 @@ export interface DeclarationBase {
node: ParseNode; node: ParseNode;
// The file and range within that file that // 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. // file the alias is referring to.
path: string; uri: Uri;
range: Range; range: Range;
// The dot-separated import name for the file that // 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 // This interface represents a set of actions that the python loader
// performs when a module import is encountered. // performs when a module import is encountered.
export interface ModuleLoaderActions { export interface ModuleLoaderActions {
// The resolved path of the implicit import. This can be empty // The resolved uri of the implicit import. This can be empty
// if the resolved path doesn't reference a module (e.g. it's // if the resolved uri doesn't reference a module (e.g. it's
// a directory). // a directory).
path: string; uri: Uri;
// Is this a dummy entry for an unresolved import? // Is this a dummy entry for an unresolved import?
isUnresolved?: boolean; isUnresolved?: boolean;
@ -285,5 +286,5 @@ export function isIntrinsicDeclaration(decl: Declaration): decl is IntrinsicDecl
} }
export function isUnresolvedAliasDeclaration(decl: Declaration): boolean { 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 { getEmptyRange } from '../common/textRange';
import { Uri } from '../common/uri/uri';
import { NameNode, ParseNodeType } from '../parser/parseNodes'; import { NameNode, ParseNodeType } from '../parser/parseNodes';
import { ImportLookup, ImportLookupResult } from './analyzerFileInfo'; import { ImportLookup, ImportLookupResult } from './analyzerFileInfo';
import { AliasDeclaration, Declaration, DeclarationType, ModuleLoaderActions, isAliasDeclaration } from './declaration'; import { AliasDeclaration, Declaration, DeclarationType, ModuleLoaderActions, isAliasDeclaration } from './declaration';
@ -78,7 +79,7 @@ export function areDeclarationsSame(
return false; return false;
} }
if (decl1.path !== decl2.path) { if (!decl1.uri.equals(decl2.uri)) {
return false; return false;
} }
@ -174,16 +175,16 @@ export function getNameNodeForDeclaration(declaration: Declaration): NameNode |
throw new Error(`Shouldn't reach here`); throw new Error(`Shouldn't reach here`);
} }
export function isDefinedInFile(decl: Declaration, filePath: string) { export function isDefinedInFile(decl: Declaration, fileUri: Uri) {
if (isAliasDeclaration(decl)) { if (isAliasDeclaration(decl)) {
// Alias decl's path points to the original symbol // Alias decl's path points to the original symbol
// the alias is pointing to. So, we need to get the // the alias is pointing to. So, we need to get the
// filepath in that the alias is defined from the node. // 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. // 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[]) { 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 only time this decl is used is for IDE services such as
// the find all references, hover provider and etc. // the find all references, hover provider and etc.
return { return {
type: DeclarationType.Alias, type: DeclarationType.Alias,
node: undefined!, node: undefined!,
path, uri,
loadSymbolsFromPath: false, loadSymbolsFromPath: false,
range: getEmptyRange(), range: getEmptyRange(),
implicitImports: new Map<string, ModuleLoaderActions>(), implicitImports: new Map<string, ModuleLoaderActions>(),
@ -267,8 +268,10 @@ export function resolveAliasDeclaration(
} }
let lookupResult: ImportLookupResult | undefined; let lookupResult: ImportLookupResult | undefined;
if (curDeclaration.path && curDeclaration.loadSymbolsFromPath) { if (!curDeclaration.uri.isEmpty() && curDeclaration.loadSymbolsFromPath) {
lookupResult = importLookup(curDeclaration.path, { skipFileNeededCheck: options.skipFileNeededCheck }); lookupResult = importLookup(curDeclaration.uri, {
skipFileNeededCheck: options.skipFileNeededCheck,
});
} }
const symbol: Symbol | undefined = lookupResult const symbol: Symbol | undefined = lookupResult
@ -284,11 +287,11 @@ export function resolveAliasDeclaration(
// when useLibraryCodeForTypes is disabled), b should be evaluated as Unknown, // when useLibraryCodeForTypes is disabled), b should be evaluated as Unknown,
// not as a module. // not as a module.
if ( if (
curDeclaration.path && !curDeclaration.uri.isEmpty() &&
curDeclaration.submoduleFallback.type === DeclarationType.Alias && 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, skipFileNeededCheck: options.skipFileNeededCheck,
skipParsing: true, skipParsing: true,
}); });
@ -393,7 +396,7 @@ export function resolveAliasDeclaration(
// the module is foo, and the foo.__init__.py file contains the statement // 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. // "from foo import bar", we want to import the foo/bar.py submodule.
if ( if (
curDeclaration.path === declaration.path && curDeclaration.uri.equals(declaration.uri) &&
curDeclaration.type === DeclarationType.Alias && curDeclaration.type === DeclarationType.Alias &&
curDeclaration.submoduleFallback curDeclaration.submoduleFallback
) { ) {

View File

@ -81,7 +81,7 @@ export function createEnumType(
className, className,
getClassFullName(errorNode, fileInfo.moduleName, className), getClassFullName(errorNode, fileInfo.moduleName, className),
fileInfo.moduleName, fileInfo.moduleName,
fileInfo.filePath, fileInfo.fileUri,
ClassTypeFlags.EnumClass, ClassTypeFlags.EnumClass,
getTypeSourceId(errorNode), getTypeSourceId(errorNode),
/* declaredMetaclass */ undefined, /* 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. * Interface that describes the output of the import resolver.
*/ */
import { Uri } from '../common/uri/uri';
import { PyTypedInfo } from './pyTypedUtils'; import { PyTypedInfo } from './pyTypedUtils';
export const enum ImportType { export const enum ImportType {
@ -19,7 +20,7 @@ export interface ImplicitImport {
isStubFile: boolean; isStubFile: boolean;
isNativeLib: boolean; isNativeLib: boolean;
name: string; name: string;
path: string; uri: Uri;
pyTypedInfo?: PyTypedInfo | undefined; pyTypedInfo?: PyTypedInfo | undefined;
} }
@ -62,11 +63,11 @@ export interface ImportResult {
// The resolved absolute paths for each of the files in the module name. // The resolved absolute paths for each of the files in the module name.
// Parts that have no files (e.g. directories within a namespace // Parts that have no files (e.g. directories within a namespace
// package) have empty strings for a resolvedPath. // package) have empty strings for a resolvedPath.
resolvedPaths: string[]; resolvedUris: Uri[];
// For absolute imports, the search path that was used to resolve // For absolute imports, the search path that was used to resolve
// (or partially resolve) the module. // (or partially resolve) the module.
searchPath?: string; searchPath?: Uri;
// True if resolved file is a type hint (.pyi) file rather than // True if resolved file is a type hint (.pyi) file rather than
// a python (.py) file. // a python (.py) file.
@ -103,5 +104,5 @@ export interface ImportResult {
pyTypedInfo?: PyTypedInfo | undefined; pyTypedInfo?: PyTypedInfo | undefined;
// The directory of the package, if found. // 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 { addIfUnique, appendArray, createMapFromItems } from '../common/collectionUtils';
import { TextEditAction } from '../common/editAction'; import { TextEditAction } from '../common/editAction';
import { ReadOnlyFileSystem } from '../common/fileSystem'; import { ReadOnlyFileSystem } from '../common/fileSystem';
import {
getDirectoryPath,
getFileName,
getRelativePathComponentsFromDirectory,
isFile,
stripFileExtension,
} from '../common/pathUtils';
import { convertOffsetToPosition, convertPositionToOffset } from '../common/positionUtils'; import { convertOffsetToPosition, convertPositionToOffset } from '../common/positionUtils';
import { compareStringsCaseSensitive } from '../common/stringUtils'; import { compareStringsCaseSensitive } from '../common/stringUtils';
import { Position, Range, TextRange } from '../common/textRange'; import { Position, Range, TextRange } from '../common/textRange';
import { Uri } from '../common/uri/uri';
import { isFile } from '../common/uri/uriUtils';
import { import {
ImportAsNode, ImportAsNode,
ImportFromAsNode, ImportFromAsNode,
@ -35,18 +30,18 @@ import {
ParseNodeType, ParseNodeType,
} from '../parser/parseNodes'; } from '../parser/parseNodes';
import { ParseResults } from '../parser/parser'; import { ParseResults } from '../parser/parser';
import { TokenType } from '../parser/tokenizerTypes';
import * as AnalyzerNodeInfo from './analyzerNodeInfo'; import * as AnalyzerNodeInfo from './analyzerNodeInfo';
import { ModuleNameAndType } from './importResolver'; import { ModuleNameAndType } from './importResolver';
import { ImportResult, ImportType } from './importResult'; import { ImportResult, ImportType } from './importResult';
import * as SymbolNameUtils from './symbolNameUtils';
import { findTokenAfter, getTokenAt } from './parseTreeUtils'; import { findTokenAfter, getTokenAt } from './parseTreeUtils';
import { TokenType } from '../parser/tokenizerTypes'; import * as SymbolNameUtils from './symbolNameUtils';
export interface ImportStatement { export interface ImportStatement {
node: ImportNode | ImportFromNode; node: ImportNode | ImportFromNode;
subnode?: ImportAsNode; subnode?: ImportAsNode;
importResult: ImportResult | undefined; importResult: ImportResult | undefined;
resolvedPath: string | undefined; resolvedPath: Uri | undefined;
moduleName: string; moduleName: string;
followsNonImportStatement: boolean; followsNonImportStatement: boolean;
} }
@ -625,10 +620,10 @@ function _getInsertionEditForAutoImportInsertion(
function _processImportNode(node: ImportNode, localImports: ImportStatements, followsNonImportStatement: boolean) { function _processImportNode(node: ImportNode, localImports: ImportStatements, followsNonImportStatement: boolean) {
node.list.forEach((importAsNode) => { node.list.forEach((importAsNode) => {
const importResult = AnalyzerNodeInfo.getImportInfo(importAsNode.module); const importResult = AnalyzerNodeInfo.getImportInfo(importAsNode.module);
let resolvedPath: string | undefined; let resolvedPath: Uri | undefined;
if (importResult && importResult.isImportFound) { if (importResult && importResult.isImportFound) {
resolvedPath = importResult.resolvedPaths[importResult.resolvedPaths.length - 1]; resolvedPath = importResult.resolvedUris[importResult.resolvedUris.length - 1];
} }
const localImport: ImportStatement = { const localImport: ImportStatement = {
@ -647,8 +642,8 @@ function _processImportNode(node: ImportNode, localImports: ImportStatements, fo
// Don't overwrite existing import or import from statements // Don't overwrite existing import or import from statements
// because we always want to prefer 'import from' over 'import' // because we always want to prefer 'import from' over 'import'
// in the map. // in the map.
if (!localImports.mapByFilePath.has(resolvedPath)) { if (!localImports.mapByFilePath.has(resolvedPath.key)) {
localImports.mapByFilePath.set(resolvedPath, localImport); localImports.mapByFilePath.set(resolvedPath.key, localImport);
} }
} }
}); });
@ -661,10 +656,10 @@ function _processImportFromNode(
includeImplicitImports: boolean includeImplicitImports: boolean
) { ) {
const importResult = AnalyzerNodeInfo.getImportInfo(node.module); const importResult = AnalyzerNodeInfo.getImportInfo(node.module);
let resolvedPath: string | undefined; let resolvedPath: Uri | undefined;
if (importResult && importResult.isImportFound) { if (importResult && importResult.isImportFound) {
resolvedPath = importResult.resolvedPaths[importResult.resolvedPaths.length - 1]; resolvedPath = importResult.resolvedUris[importResult.resolvedUris.length - 1];
} }
if (includeImplicitImports && importResult) { if (includeImplicitImports && importResult) {
@ -673,7 +668,7 @@ function _processImportFromNode(
for (const implicitImport of importResult.implicitImports.values()) { for (const implicitImport of importResult.implicitImports.values()) {
const importFromAs = node.imports.find((i) => i.name.value === implicitImport.name); const importFromAs = node.imports.find((i) => i.name.value === implicitImport.name);
if (importFromAs) { 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. // Add it to the map.
if (resolvedPath) { 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 // Overwrite existing import statements because we always want to prefer
// 'import from' over 'import'. Also, overwrite existing 'import from' if // 'import from' over 'import'. Also, overwrite existing 'import from' if
// the module name is shorter. // the module name is shorter.
@ -699,7 +694,7 @@ function _processImportFromNode(
prevEntry.node.nodeType === ParseNodeType.Import || prevEntry.node.nodeType === ParseNodeType.Import ||
prevEntry.moduleName.length > localImport.moduleName.length 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( export function getRelativeModuleName(
fs: ReadOnlyFileSystem, fs: ReadOnlyFileSystem,
sourcePath: string, sourcePath: Uri,
targetPath: string, targetPath: Uri,
ignoreFolderStructure = false, ignoreFolderStructure = false,
sourceIsFile?: boolean sourceIsFile?: boolean
) { ) {
let srcPath = sourcePath; let srcPath = sourcePath;
sourceIsFile = sourceIsFile !== undefined ? sourceIsFile : isFile(fs, sourcePath); sourceIsFile = sourceIsFile !== undefined ? sourceIsFile : isFile(fs, sourcePath);
if (sourceIsFile) { if (sourceIsFile) {
srcPath = getDirectoryPath(sourcePath); srcPath = sourcePath.getDirectory();
} }
let symbolName: string | undefined; let symbolName: string | undefined;
let destPath = targetPath; let destPath = targetPath;
if (sourceIsFile) { if (sourceIsFile) {
destPath = getDirectoryPath(targetPath); destPath = targetPath.getDirectory();
const fileName = stripFileExtension(getFileName(targetPath)); const fileName = targetPath.stripAllExtensions().fileName;
if (fileName !== '__init__') { if (fileName !== '__init__') {
// ex) src: a.py, dest: b.py -> ".b" will be returned. // ex) src: a.py, dest: b.py -> ".b" will be returned.
symbolName = fileName; symbolName = fileName;
@ -875,18 +870,18 @@ export function getRelativeModuleName(
// like how it would return for sibling folder. // like how it would return for sibling folder.
// //
// if folder structure is not ignored, ".." will be returned // if folder structure is not ignored, ".." will be returned
symbolName = getFileName(destPath); symbolName = destPath.fileName;
destPath = getDirectoryPath(destPath); 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. // This assumes both file paths are under the same importing root.
// So this doesn't handle paths pointing to 2 different import roots. // So this doesn't handle paths pointing to 2 different import roots.
// ex) user file A to library file B // ex) user file A to library file B
let currentPaths = '.'; let currentPaths = '.';
for (let i = 1; i < relativePaths.length; i++) { for (let i = 0; i < relativePaths.length; i++) {
const relativePath = relativePaths[i]; const relativePath = relativePaths[i];
if (relativePath === '..') { if (relativePath === '..') {
currentPaths += '.'; currentPaths += '.';
@ -907,25 +902,25 @@ export function getRelativeModuleName(
return currentPaths; return currentPaths;
} }
export function getDirectoryLeadingDotsPointsTo(fromDirectory: string, leadingDots: number) { export function getDirectoryLeadingDotsPointsTo(fromDirectory: Uri, leadingDots: number) {
let currentDirectory = fromDirectory; let currentDirectory = fromDirectory;
for (let i = 1; i < leadingDots; i++) { for (let i = 1; i < leadingDots; i++) {
if (currentDirectory === '') { if (currentDirectory.isRoot()) {
return undefined; return undefined;
} }
currentDirectory = getDirectoryPath(currentDirectory); currentDirectory = currentDirectory.getDirectory();
} }
return currentDirectory; return currentDirectory;
} }
export function getResolvedFilePath(importResult: ImportResult | undefined) { export function getResolvedFilePath(importResult: ImportResult | undefined) {
if (!importResult || !importResult.isImportFound || importResult.resolvedPaths.length === 0) { if (!importResult || !importResult.isImportFound || importResult.resolvedUris.length === 0) {
return undefined; 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. // Import is resolved to namespace package folder.
if (importResult.packageDirectory) { if (importResult.packageDirectory) {
return importResult.packageDirectory; return importResult.packageDirectory;
@ -940,7 +935,7 @@ export function getResolvedFilePath(importResult: ImportResult | undefined) {
} }
// Regular case. // Regular case.
return importResult.resolvedPaths[importResult.resolvedPaths.length - 1]; return importResult.resolvedUris[importResult.resolvedUris.length - 1];
} }
export function haveSameParentModule(module1: string[], module2: string[]) { export function haveSameParentModule(module1: string[], module2: string[]) {

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -8,24 +8,23 @@
*/ */
import { FileSystem } from '../common/fileSystem'; 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 { export interface PyTypedInfo {
pyTypedPath: string; pyTypedPath: Uri;
isPartiallyTyped: boolean; isPartiallyTyped: boolean;
} }
const _pyTypedFileName = 'py.typed'; export function getPyTypedInfo(fileSystem: FileSystem, dirPath: Uri): PyTypedInfo | undefined {
export function getPyTypedInfo(fileSystem: FileSystem, dirPath: string): PyTypedInfo | undefined {
if (!fileSystem.existsSync(dirPath) || !isDirectory(fileSystem, dirPath)) { if (!fileSystem.existsSync(dirPath) || !isDirectory(fileSystem, dirPath)) {
return undefined; return undefined;
} }
let isPartiallyTyped = false; 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; return undefined;
} }

View File

@ -12,43 +12,32 @@ import { compareComparableValues } from '../common/core';
import { FileSystem } from '../common/fileSystem'; import { FileSystem } from '../common/fileSystem';
import { Host } from '../common/host'; import { Host } from '../common/host';
import * as pathConsts from '../common/pathConsts'; import * as pathConsts from '../common/pathConsts';
import { import { PythonVersion, versionToString } from '../common/pythonVersion';
combinePaths, import { Uri } from '../common/uri/uri';
containsPath, import { getFileSystemEntries, isDirectory, tryStat } from '../common/uri/uriUtils';
ensureTrailingDirectorySeparator,
getDirectoryPath,
getFileSystemEntries,
isDirectory,
normalizePath,
tryStat,
} from '../common/pathUtils';
import { versionToString } from '../common/pythonVersion';
import { PythonVersion } from '../common/pythonVersion';
export interface PythonPathResult { export interface PythonPathResult {
paths: string[]; paths: Uri[];
prefix: string; prefix: Uri | undefined;
} }
export const stdLibFolderName = 'stdlib'; export const stdLibFolderName = 'stdlib';
export const thirdPartyFolderName = 'stubs'; export const thirdPartyFolderName = 'stubs';
export function getTypeShedFallbackPath(fs: FileSystem) { export function getTypeShedFallbackPath(fs: FileSystem) {
let moduleDirectory = fs.getModulePath(); const moduleDirectory = fs.getModulePath();
if (!moduleDirectory) { if (!moduleDirectory || moduleDirectory.isEmpty()) {
return undefined; return undefined;
} }
moduleDirectory = getDirectoryPath(ensureTrailingDirectorySeparator(normalizePath(moduleDirectory))); const typeshedPath = moduleDirectory.combinePaths(pathConsts.typeshedFallback);
const typeshedPath = combinePaths(moduleDirectory, pathConsts.typeshedFallback);
if (fs.existsSync(typeshedPath)) { if (fs.existsSync(typeshedPath)) {
return fs.realCasePath(typeshedPath); return fs.realCasePath(typeshedPath);
} }
// In the debug version of Pyright, the code is one level // In the debug version of Pyright, the code is one level
// deeper, so we need to look one level up for the typeshed fallback. // 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)) { if (fs.existsSync(debugTypeshedPath)) {
return fs.realCasePath(debugTypeshedPath); return fs.realCasePath(debugTypeshedPath);
} }
@ -56,8 +45,8 @@ export function getTypeShedFallbackPath(fs: FileSystem) {
return undefined; return undefined;
} }
export function getTypeshedSubdirectory(typeshedPath: string, isStdLib: boolean) { export function getTypeshedSubdirectory(typeshedPath: Uri, isStdLib: boolean) {
return combinePaths(typeshedPath, isStdLib ? stdLibFolderName : thirdPartyFolderName); return typeshedPath.combinePaths(isStdLib ? stdLibFolderName : thirdPartyFolderName);
} }
export function findPythonSearchPaths( export function findPythonSearchPaths(
@ -66,21 +55,21 @@ export function findPythonSearchPaths(
host: Host, host: Host,
importFailureInfo: string[], importFailureInfo: string[],
includeWatchPathsOnly?: boolean | undefined, includeWatchPathsOnly?: boolean | undefined,
workspaceRoot?: string | undefined workspaceRoot?: Uri | undefined
): string[] | undefined { ): Uri[] | undefined {
importFailureInfo.push('Finding python search paths'); importFailureInfo.push('Finding python search paths');
if (configOptions.venvPath !== undefined && configOptions.venv) { if (configOptions.venvPath !== undefined && configOptions.venv) {
const venvDir = configOptions.venv; const venvDir = configOptions.venv;
const venvPath = combinePaths(configOptions.venvPath, venvDir); const venvPath = configOptions.venvPath.combinePaths(venvDir);
const foundPaths: string[] = []; const foundPaths: Uri[] = [];
const sitePackagesPaths: string[] = []; const sitePackagesPaths: Uri[] = [];
[pathConsts.lib, pathConsts.lib64, pathConsts.libAlternate].forEach((libPath) => { [pathConsts.lib, pathConsts.lib64, pathConsts.libAlternate].forEach((libPath) => {
const sitePackagesPath = findSitePackagesPath( const sitePackagesPath = findSitePackagesPath(
fs, fs,
combinePaths(venvPath, libPath), venvPath.combinePaths(libPath),
configOptions.defaultPythonVersion, configOptions.defaultPythonVersion,
importFailureInfo importFailureInfo
); );
@ -115,11 +104,7 @@ export function findPythonSearchPaths(
const pathResult = host.getPythonSearchPaths(configOptions.pythonPath, importFailureInfo); const pathResult = host.getPythonSearchPaths(configOptions.pythonPath, importFailureInfo);
if (includeWatchPathsOnly && workspaceRoot) { if (includeWatchPathsOnly && workspaceRoot) {
const paths = pathResult.paths const paths = pathResult.paths
.filter( .filter((p) => !p.startsWith(workspaceRoot) || p.startsWith(pathResult.prefix))
(p) =>
!containsPath(workspaceRoot, p, /* ignoreCase */ true) ||
containsPath(pathResult.prefix, p, /* ignoreCase */ true)
)
.map((p) => fs.realCasePath(p)); .map((p) => fs.realCasePath(p));
return paths; return paths;
@ -135,10 +120,10 @@ export function isPythonBinary(p: string): boolean {
function findSitePackagesPath( function findSitePackagesPath(
fs: FileSystem, fs: FileSystem,
libPath: string, libPath: Uri,
pythonVersion: PythonVersion | undefined, pythonVersion: PythonVersion | undefined,
importFailureInfo: string[] importFailureInfo: string[]
): string | undefined { ): Uri | undefined {
if (fs.existsSync(libPath)) { if (fs.existsSync(libPath)) {
importFailureInfo.push(`Found path '${libPath}'; looking for ${pathConsts.sitePackages}`); importFailureInfo.push(`Found path '${libPath}'; looking for ${pathConsts.sitePackages}`);
} else { } else {
@ -146,7 +131,7 @@ function findSitePackagesPath(
return undefined; return undefined;
} }
const sitePackagesPath = combinePaths(libPath, pathConsts.sitePackages); const sitePackagesPath = libPath.combinePaths(pathConsts.sitePackages);
if (fs.existsSync(sitePackagesPath)) { if (fs.existsSync(sitePackagesPath)) {
importFailureInfo.push(`Found path '${sitePackagesPath}'`); importFailureInfo.push(`Found path '${sitePackagesPath}'`);
return sitePackagesPath; return sitePackagesPath;
@ -160,8 +145,8 @@ function findSitePackagesPath(
// Candidate directories start with "python3.". // Candidate directories start with "python3.".
const candidateDirs = entries.directories.filter((dirName) => { const candidateDirs = entries.directories.filter((dirName) => {
if (dirName.startsWith('python3.')) { if (dirName.fileName.startsWith('python3.')) {
const dirPath = combinePaths(libPath, dirName, pathConsts.sitePackages); const dirPath = dirName.combinePaths(pathConsts.sitePackages);
return fs.existsSync(dirPath); return fs.existsSync(dirPath);
} }
return false; return false;
@ -170,9 +155,11 @@ function findSitePackagesPath(
// If there is a python3.X directory (where 3.X matches the configured python // If there is a python3.X directory (where 3.X matches the configured python
// version), prefer that over other python directories. // version), prefer that over other python directories.
if (pythonVersion) { if (pythonVersion) {
const preferredDir = candidateDirs.find((dirName) => dirName === `python${versionToString(pythonVersion)}`); const preferredDir = candidateDirs.find(
(dirName) => dirName.fileName === `python${versionToString(pythonVersion)}`
);
if (preferredDir) { if (preferredDir) {
const dirPath = combinePaths(libPath, preferredDir, pathConsts.sitePackages); const dirPath = preferredDir.combinePaths(pathConsts.sitePackages);
importFailureInfo.push(`Found path '${dirPath}'`); importFailureInfo.push(`Found path '${dirPath}'`);
return dirPath; return dirPath;
} }
@ -182,7 +169,7 @@ function findSitePackagesPath(
// first directory that starts with "python". Most of the time, there will be // first directory that starts with "python". Most of the time, there will be
// only one. // only one.
if (candidateDirs.length > 0) { if (candidateDirs.length > 0) {
const dirPath = combinePaths(libPath, candidateDirs[0], pathConsts.sitePackages); const dirPath = candidateDirs[0].combinePaths(pathConsts.sitePackages);
importFailureInfo.push(`Found path '${dirPath}'`); importFailureInfo.push(`Found path '${dirPath}'`);
return dirPath; return dirPath;
} }
@ -190,8 +177,8 @@ function findSitePackagesPath(
return undefined; return undefined;
} }
export function getPathsFromPthFiles(fs: FileSystem, parentDir: string): string[] { export function getPathsFromPthFiles(fs: FileSystem, parentDir: Uri): Uri[] {
const searchPaths: string[] = []; const searchPaths: Uri[] = [];
// Get a list of all *.pth files within the specified directory. // Get a list of all *.pth files within the specified directory.
const pthFiles = fs const pthFiles = fs
@ -200,7 +187,7 @@ export function getPathsFromPthFiles(fs: FileSystem, parentDir: string): string[
.sort((a, b) => compareComparableValues(a.name, b.name)); .sort((a, b) => compareComparableValues(a.name, b.name));
pthFiles.forEach((pthFile) => { pthFiles.forEach((pthFile) => {
const filePath = fs.realCasePath(combinePaths(parentDir, pthFile.name)); const filePath = fs.realCasePath(parentDir.combinePaths(pthFile.name));
const fileStats = tryStat(fs, filePath); const fileStats = tryStat(fs, filePath);
// Skip all files that are much larger than expected. // Skip all files that are much larger than expected.
@ -210,7 +197,7 @@ export function getPathsFromPthFiles(fs: FileSystem, parentDir: string): string[
lines.forEach((line) => { lines.forEach((line) => {
const trimmedLine = line.trim(); const trimmedLine = line.trim();
if (trimmedLine.length > 0 && !trimmedLine.startsWith('#') && !trimmedLine.match(/^import\s/)) { 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)) { if (fs.existsSync(pthPath) && isDirectory(fs, pthPath)) {
searchPaths.push(fs.realCasePath(pthPath)); searchPaths.push(fs.realCasePath(pthPath));
} }
@ -222,8 +209,8 @@ export function getPathsFromPthFiles(fs: FileSystem, parentDir: string): string[
return searchPaths; return searchPaths;
} }
function addPathIfUnique(pathList: string[], pathToAdd: string) { function addPathIfUnique(pathList: Uri[], pathToAdd: Uri) {
if (!pathList.some((path) => path === pathToAdd)) { if (!pathList.some((path) => path.key === pathToAdd.key)) {
pathList.push(pathToAdd); pathList.push(pathToAdd);
return true; 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 { ServiceProvider } from '../common/extensibility';
import { FileSystem } from '../common/fileSystem'; import { FileSystem } from '../common/fileSystem';
import { LogTracker, getPathForLogging } from '../common/logTracker'; 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 { convertOffsetsToRange, convertTextRangeToRange } from '../common/positionUtils';
import { ServiceKeys } from '../common/serviceProviderExtensions'; import { ServiceKeys } from '../common/serviceProviderExtensions';
import * as StringUtils from '../common/stringUtils'; import * as StringUtils from '../common/stringUtils';
import { Range, TextRange, getEmptyRange } from '../common/textRange'; import { Range, TextRange, getEmptyRange } from '../common/textRange';
import { TextRangeCollection } from '../common/textRangeCollection'; import { TextRangeCollection } from '../common/textRangeCollection';
import { Duration, timingStats } from '../common/timing'; import { Duration, timingStats } from '../common/timing';
import { Uri } from '../common/uri/uri';
import { Localizer } from '../localization/localize'; import { Localizer } from '../localization/localize';
import { ModuleNode } from '../parser/parseNodes'; import { ModuleNode } from '../parser/parseNodes';
import { IParser, ModuleImport, ParseOptions, ParseResults, Parser } from '../parser/parser'; import { IParser, ModuleImport, ParseOptions, ParseResults, Parser } from '../parser/parser';
@ -179,12 +180,9 @@ export class SourceFile {
// Console interface to use for debugging. // Console interface to use for debugging.
private _console: ConsoleInterface; 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. // a real file on disk.
private readonly _filePath: string; private readonly _uri: Uri;
// File path on disk. May not be unique.
private readonly _realFilePath: string;
// Period-delimited import path for the module. // Period-delimited import path for the module.
private _moduleName: string; private _moduleName: string;
@ -232,46 +230,42 @@ export class SourceFile {
constructor( constructor(
readonly serviceProvider: ServiceProvider, readonly serviceProvider: ServiceProvider,
filePath: string, uri: Uri,
moduleName: string, moduleName: string,
isThirdPartyImport: boolean, isThirdPartyImport: boolean,
isThirdPartyPyTypedPresent: boolean, isThirdPartyPyTypedPresent: boolean,
editMode: SourceFileEditMode, editMode: SourceFileEditMode,
console?: ConsoleInterface, console?: ConsoleInterface,
logTracker?: LogTracker, logTracker?: LogTracker,
realFilePath?: string,
ipythonMode?: IPythonMode ipythonMode?: IPythonMode
) { ) {
this.fileSystem = serviceProvider.get(ServiceKeys.fs); this.fileSystem = serviceProvider.get(ServiceKeys.fs);
this._console = console || new StandardConsole(); this._console = console || new StandardConsole();
this._editMode = editMode; this._editMode = editMode;
this._filePath = filePath; this._uri = uri;
this._realFilePath = realFilePath ?? filePath;
this._moduleName = moduleName; this._moduleName = moduleName;
this._isStubFile = filePath.endsWith('.pyi'); this._isStubFile = uri.hasExtension('.pyi');
this._isThirdPartyImport = isThirdPartyImport; this._isThirdPartyImport = isThirdPartyImport;
this._isThirdPartyPyTypedPresent = isThirdPartyPyTypedPresent; this._isThirdPartyPyTypedPresent = isThirdPartyPyTypedPresent;
const fileName = getFileName(filePath); const fileName = uri.fileName;
this._isTypingStubFile = this._isTypingStubFile =
this._isStubFile && this._isStubFile && (this._uri.pathEndsWith('stdlib/typing.pyi') || fileName === 'typing_extensions.pyi');
(this._filePath.endsWith(normalizeSlashes('stdlib/typing.pyi')) || fileName === 'typing_extensions.pyi');
this._isTypingExtensionsStubFile = this._isStubFile && fileName === 'typing_extensions.pyi'; this._isTypingExtensionsStubFile = this._isStubFile && fileName === 'typing_extensions.pyi';
this._isTypeshedStubFile = this._isTypeshedStubFile = this._isStubFile && this._uri.pathEndsWith('stdlib/_typeshed/__init__.pyi');
this._isStubFile && this._filePath.endsWith(normalizeSlashes('stdlib/_typeshed/__init__.pyi'));
this._isBuiltInStubFile = false; this._isBuiltInStubFile = false;
if (this._isStubFile) { if (this._isStubFile) {
if ( if (
this._filePath.endsWith(normalizeSlashes('stdlib/collections/__init__.pyi')) || this._uri.pathEndsWith('stdlib/collections/__init__.pyi') ||
this._filePath.endsWith(normalizeSlashes('stdlib/asyncio/futures.pyi')) || this._uri.pathEndsWith('stdlib/asyncio/futures.pyi') ||
this._filePath.endsWith(normalizeSlashes('stdlib/asyncio/tasks.pyi')) || this._uri.pathEndsWith('stdlib/asyncio/tasks.pyi') ||
this._filePath.endsWith(normalizeSlashes('stdlib/builtins.pyi')) || this._uri.pathEndsWith('stdlib/builtins.pyi') ||
this._filePath.endsWith(normalizeSlashes('stdlib/_importlib_modulespec.pyi')) || this._uri.pathEndsWith('stdlib/_importlib_modulespec.pyi') ||
this._filePath.endsWith(normalizeSlashes('stdlib/dataclasses.pyi')) || this._uri.pathEndsWith('stdlib/dataclasses.pyi') ||
this._filePath.endsWith(normalizeSlashes('stdlib/abc.pyi')) || this._uri.pathEndsWith('stdlib/abc.pyi') ||
this._filePath.endsWith(normalizeSlashes('stdlib/enum.pyi')) || this._uri.pathEndsWith('stdlib/enum.pyi') ||
this._filePath.endsWith(normalizeSlashes('stdlib/queue.pyi')) || this._uri.pathEndsWith('stdlib/queue.pyi') ||
this._filePath.endsWith(normalizeSlashes('stdlib/types.pyi')) this._uri.pathEndsWith('stdlib/types.pyi')
) { ) {
this._isBuiltInStubFile = true; this._isBuiltInStubFile = true;
} }
@ -282,16 +276,12 @@ export class SourceFile {
this._ipythonMode = ipythonMode ?? IPythonMode.None; this._ipythonMode = ipythonMode ?? IPythonMode.None;
} }
getRealFilePath(): string {
return this._realFilePath;
}
getIPythonMode(): IPythonMode { getIPythonMode(): IPythonMode {
return this._ipythonMode; return this._ipythonMode;
} }
getFilePath(): string { getUri(): Uri {
return this._filePath; return this._uri;
} }
getModuleName(): string { getModuleName(): string {
@ -300,7 +290,7 @@ export class SourceFile {
} }
// Synthesize a module name using the file path. // Synthesize a module name using the file path.
return stripFileExtension(getFileName(this._filePath)); return stripFileExtension(this._uri.fileName);
} }
setModuleName(name: string) { setModuleName(name: string) {
@ -378,8 +368,8 @@ export class SourceFile {
// that of the previous contents. // that of the previous contents.
try { try {
// Read the file's contents. // Read the file's contents.
if (this.fileSystem.existsSync(this._filePath)) { if (this.fileSystem.existsSync(this._uri)) {
const fileContents = this.fileSystem.readFileSync(this._filePath, 'utf8'); const fileContents = this.fileSystem.readFileSync(this._uri, 'utf8');
if (fileContents.length !== this._writableData.lastFileContentLength) { if (fileContents.length !== this._writableData.lastFileContentLength) {
return true; return true;
@ -465,16 +455,16 @@ export class SourceFile {
// Otherwise, get content from file system. // Otherwise, get content from file system.
try { try {
// Check the file's length before attempting to read its full contents. // 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) { if (fileStat.size > _maxSourceFileSize) {
this._console.error( 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}` `which exceeds the maximum supported file size of ${_maxSourceFileSize}`
); );
throw new Error('File larger than max'); throw new Error('File larger than max');
} }
return this.fileSystem.readFileSync(this._filePath, 'utf8'); return this.fileSystem.readFileSync(this._uri, 'utf8');
} catch (error) { } catch (error) {
return undefined; return undefined;
} }
@ -580,7 +570,7 @@ export class SourceFile {
// (or at least cancel) prior to calling again. It returns true if a parse // (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. // was required and false if the parse information was up to date already.
parse(configOptions: ConfigOptions, importResolver: ImportResolver, content?: string): boolean { 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 the file is already parsed, we can skip.
if (!this.isParseRequired()) { if (!this.isParseRequired()) {
logState.suppress(); logState.suppress();
@ -608,7 +598,7 @@ export class SourceFile {
diagSink.addError(`Source file could not be read`, getEmptyRange()); diagSink.addError(`Source file could not be read`, getEmptyRange());
fileContents = ''; fileContents = '';
if (!this.fileSystem.existsSync(this._realFilePath)) { if (!this.fileSystem.existsSync(this._uri)) {
this._writableData.isFileDeleted = true; this._writableData.isFileDeleted = true;
} }
} }
@ -618,7 +608,7 @@ export class SourceFile {
// Parse the token stream, building the abstract syntax tree. // Parse the token stream, building the abstract syntax tree.
const parseResults = this._parseFile( const parseResults = this._parseFile(
configOptions, configOptions,
this._filePath, this._uri,
fileContents!, fileContents!,
this._ipythonMode, this._ipythonMode,
diagSink diagSink
@ -632,7 +622,7 @@ export class SourceFile {
this._writableData.parseResults.tokenizerOutput.pyrightIgnoreLines; this._writableData.parseResults.tokenizerOutput.pyrightIgnoreLines;
// Resolve imports. // Resolve imports.
const execEnvironment = configOptions.findExecEnvironment(this._filePath); const execEnvironment = configOptions.findExecEnvironment(this._uri);
timingStats.resolveImportsTime.timeOperation(() => { timingStats.resolveImportsTime.timeOperation(() => {
const importResult = this._resolveImports( const importResult = this._resolveImports(
importResolver, importResolver,
@ -648,7 +638,7 @@ export class SourceFile {
// Is this file in a "strict" path? // Is this file in a "strict" path?
const useStrict = const useStrict =
configOptions.strict.find((strictFileSpec) => strictFileSpec.regExp.test(this._realFilePath)) !== configOptions.strict.find((strictFileSpec) => this._uri.matchesRegex(strictFileSpec.regExp)) !==
undefined; undefined;
const commentDiags: CommentUtils.CommentDiagnostic[] = []; const commentDiags: CommentUtils.CommentDiagnostic[] = [];
@ -680,7 +670,10 @@ export class SourceFile {
(typeof e.message === 'string' ? e.message : undefined) || (typeof e.message === 'string' ? e.message : undefined) ||
JSON.stringify(e); JSON.stringify(e);
this._console.error( this._console.error(
Localizer.Diagnostic.internalParseError().format({ file: this.getFilePath(), message }) Localizer.Diagnostic.internalParseError().format({
file: this.getUri().toUserVisibleString(),
message,
})
); );
// Create dummy parse results. // Create dummy parse results.
@ -708,7 +701,10 @@ export class SourceFile {
const diagSink = this.createDiagnosticSink(); const diagSink = this.createDiagnosticSink();
diagSink.addError( diagSink.addError(
Localizer.Diagnostic.internalParseError().format({ file: this.getFilePath(), message }), Localizer.Diagnostic.internalParseError().format({
file: this.getUri().toUserVisibleString(),
message,
}),
getEmptyRange() getEmptyRange()
); );
this._writableData.parseDiagnostics = diagSink.fetchAndClear(); 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.isBindingInProgress, 'Bind called while binding in progress');
assert(this._writableData.parseResults !== undefined, 'Parse results not available'); 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 { try {
// Perform name binding. // Perform name binding.
timingStats.bindTime.timeOperation(() => { timingStats.bindTime.timeOperation(() => {
@ -777,12 +773,18 @@ export class SourceFile {
(typeof e.message === 'string' ? e.message : undefined) || (typeof e.message === 'string' ? e.message : undefined) ||
JSON.stringify(e); JSON.stringify(e);
this._console.error( this._console.error(
Localizer.Diagnostic.internalBindError().format({ file: this.getFilePath(), message }) Localizer.Diagnostic.internalBindError().format({
file: this.getUri().toUserVisibleString(),
message,
})
); );
const diagSink = this.createDiagnosticSink(); const diagSink = this.createDiagnosticSink();
diagSink.addError( diagSink.addError(
Localizer.Diagnostic.internalBindError().format({ file: this.getFilePath(), message }), Localizer.Diagnostic.internalBindError().format({
file: this.getUri().toUserVisibleString(),
message,
}),
getEmptyRange() getEmptyRange()
); );
this._writableData.bindDiagnostics = diagSink.fetchAndClear(); this._writableData.bindDiagnostics = diagSink.fetchAndClear();
@ -814,7 +816,7 @@ export class SourceFile {
assert(this.isCheckingRequired(), 'Check called unnecessarily'); assert(this.isCheckingRequired(), 'Check called unnecessarily');
assert(this._writableData.parseResults !== undefined, 'Parse results not available'); 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 { try {
timingStats.typeCheckerTime.timeOperation(() => { timingStats.typeCheckerTime.timeOperation(() => {
const checkDuration = new Duration(); const checkDuration = new Duration();
@ -840,11 +842,17 @@ export class SourceFile {
(typeof e.message === 'string' ? e.message : undefined) || (typeof e.message === 'string' ? e.message : undefined) ||
JSON.stringify(e); JSON.stringify(e);
this._console.error( this._console.error(
Localizer.Diagnostic.internalTypeCheckingError().format({ file: this.getFilePath(), message }) Localizer.Diagnostic.internalTypeCheckingError().format({
file: this.getUri().toUserVisibleString(),
message,
})
); );
const diagSink = this.createDiagnosticSink(); const diagSink = this.createDiagnosticSink();
diagSink.addError( diagSink.addError(
Localizer.Diagnostic.internalTypeCheckingError().format({ file: this.getFilePath(), message }), Localizer.Diagnostic.internalTypeCheckingError().format({
file: this.getUri().toUserVisibleString(),
message,
}),
getEmptyRange() getEmptyRange()
); );
@ -1097,7 +1105,7 @@ export class SourceFile {
'\n' + '\n' +
cirDep cirDep
.getPaths() .getPaths()
.map((path) => ' ' + path) .map((path) => ' ' + path.toUserVisibleString())
.join('\n'), .join('\n'),
getEmptyRange() getEmptyRange()
); );
@ -1120,7 +1128,7 @@ export class SourceFile {
this._addTaskListDiagnostics(configOptions.taskListTokens, diagList); this._addTaskListDiagnostics(configOptions.taskListTokens, diagList);
// If the file is in the ignore list, clear the diagnostic list. // 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 = []; diagList = [];
} }
@ -1243,13 +1251,13 @@ export class SourceFile {
futureImports, futureImports,
builtinsScope, builtinsScope,
diagnosticSink: analysisDiagnostics, diagnosticSink: analysisDiagnostics,
executionEnvironment: configOptions.findExecEnvironment(this._filePath), executionEnvironment: configOptions.findExecEnvironment(this._uri),
diagnosticRuleSet: this._diagnosticRuleSet, diagnosticRuleSet: this._diagnosticRuleSet,
fileContents, fileContents,
lines: this._writableData.parseResults!.tokenizerOutput.lines, lines: this._writableData.parseResults!.tokenizerOutput.lines,
typingSymbolAliases: this._writableData.parseResults!.typingSymbolAliases, typingSymbolAliases: this._writableData.parseResults!.typingSymbolAliases,
definedConstants: configOptions.defineConstant, definedConstants: configOptions.defineConstant,
filePath: this._filePath, fileUri: this._uri,
moduleName: this.getModuleName(), moduleName: this.getModuleName(),
isStubFile: this._isStubFile, isStubFile: this._isStubFile,
isTypingStubFile: this._isTypingStubFile, isTypingStubFile: this._isTypingStubFile,
@ -1281,7 +1289,7 @@ export class SourceFile {
const imports: ImportResult[] = []; const imports: ImportResult[] = [];
const resolveAndAddIfNotSelf = (nameParts: string[], skipMissingImport = false) => { const resolveAndAddIfNotSelf = (nameParts: string[], skipMissingImport = false) => {
const importResult = importResolver.resolveImport(this._filePath, execEnv, { const importResult = importResolver.resolveImport(this._uri, execEnv, {
leadingDots: 0, leadingDots: 0,
nameParts, nameParts,
importedSymbols: undefined, importedSymbols: undefined,
@ -1292,7 +1300,7 @@ export class SourceFile {
} }
// Avoid importing module from the module file itself. // 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); imports.push(importResult);
return importResult; return importResult;
} }
@ -1314,7 +1322,7 @@ export class SourceFile {
} }
for (const moduleImport of moduleImports) { for (const moduleImport of moduleImports) {
const importResult = importResolver.resolveImport(this._filePath, execEnv, { const importResult = importResolver.resolveImport(this._uri, execEnv, {
leadingDots: moduleImport.leadingDots, leadingDots: moduleImport.leadingDots,
nameParts: moduleImport.nameParts, nameParts: moduleImport.nameParts,
importedSymbols: moduleImport.importedSymbols, importedSymbols: moduleImport.importedSymbols,
@ -1347,24 +1355,24 @@ export class SourceFile {
}; };
} }
private _getPathForLogging(filepath: string) { private _getPathForLogging(fileUri: Uri) {
return getPathForLogging(this.fileSystem, filepath); return getPathForLogging(this.fileSystem, fileUri);
} }
private _parseFile( private _parseFile(
configOptions: ConfigOptions, configOptions: ConfigOptions,
filePath: string, fileUri: Uri,
fileContents: string, fileContents: string,
ipythonMode: IPythonMode, ipythonMode: IPythonMode,
diagSink: DiagnosticSink diagSink: DiagnosticSink
) { ) {
// Use the configuration options to determine the environment in which // Use the configuration options to determine the environment in which
// this source file will be executed. // this source file will be executed.
const execEnvironment = configOptions.findExecEnvironment(filePath); const execEnvironment = configOptions.findExecEnvironment(fileUri);
const parseOptions = new ParseOptions(); const parseOptions = new ParseOptions();
parseOptions.ipythonMode = ipythonMode; parseOptions.ipythonMode = ipythonMode;
if (filePath.endsWith('pyi')) { if (fileUri.pathEndsWith('pyi')) {
parseOptions.isStubFile = true; parseOptions.isStubFile = true;
} }
parseOptions.pythonVersion = execEnvironment.pythonVersion; parseOptions.pythonVersion = execEnvironment.pythonVersion;
@ -1378,7 +1386,7 @@ export class SourceFile {
private _fireFileDirtyEvent() { private _fireFileDirtyEvent() {
this.serviceProvider.tryGet(ServiceKeys.stateMutationListeners)?.forEach((l) => { this.serviceProvider.tryGet(ServiceKeys.stateMutationListeners)?.forEach((l) => {
try { try {
l.fileDirty?.(this._filePath); l.fileDirty?.(this._uri);
} catch (ex: any) { } catch (ex: any) {
const console = this.serviceProvider.tryGet(ServiceKeys.console); const console = this.serviceProvider.tryGet(ServiceKeys.console);
if (console) { if (console) {

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -9,6 +9,7 @@
import { CancellationToken, ExecuteCommandParams } from 'vscode-languageserver'; import { CancellationToken, ExecuteCommandParams } from 'vscode-languageserver';
import { OperationCanceledException } from '../common/cancellationUtils'; import { OperationCanceledException } from '../common/cancellationUtils';
import { Uri } from '../common/uri/uri';
import { LanguageServerInterface } from '../languageServerBase'; import { LanguageServerInterface } from '../languageServerBase';
import { AnalyzerServiceExecutor } from '../languageService/analyzerServiceExecutor'; import { AnalyzerServiceExecutor } from '../languageService/analyzerServiceExecutor';
import { ServerCommand } from './commandController'; import { ServerCommand } from './commandController';
@ -18,9 +19,9 @@ export class CreateTypeStubCommand implements ServerCommand {
async execute(cmdParams: ExecuteCommandParams, token: CancellationToken): Promise<any> { async execute(cmdParams: ExecuteCommandParams, token: CancellationToken): Promise<any> {
if (cmdParams.arguments && cmdParams.arguments.length >= 2) { 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 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( const service = await AnalyzerServiceExecutor.cloneService(
this._ls, this._ls,

View File

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

View File

@ -8,6 +8,7 @@
import { CancellationToken, ExecuteCommandParams } from 'vscode-languageserver'; import { CancellationToken, ExecuteCommandParams } from 'vscode-languageserver';
import { Uri } from '../common/uri/uri';
import { convertToFileTextEdits, convertToWorkspaceEdit } from '../common/workspaceEditUtils'; import { convertToFileTextEdits, convertToWorkspaceEdit } from '../common/workspaceEditUtils';
import { LanguageServerInterface } from '../languageServerBase'; import { LanguageServerInterface } from '../languageServerBase';
import { performQuickAction } from '../languageService/quickActions'; import { performQuickAction } from '../languageService/quickActions';
@ -19,20 +20,19 @@ export class QuickActionCommand implements ServerCommand {
async execute(params: ExecuteCommandParams, token: CancellationToken): Promise<any> { async execute(params: ExecuteCommandParams, token: CancellationToken): Promise<any> {
if (params.arguments && params.arguments.length >= 1) { 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 otherArgs = params.arguments.slice(1);
const filePath = this._ls.decodeTextDocumentUri(docUri); const workspace = await this._ls.getWorkspaceForFile(docUri);
const workspace = await this._ls.getWorkspaceForFile(filePath);
if (params.command === Commands.orderImports && workspace.disableOrganizeImports) { if (params.command === Commands.orderImports && workspace.disableOrganizeImports) {
return []; return [];
} }
const editActions = workspace.service.run((p) => { const editActions = workspace.service.run((p) => {
return performQuickAction(p, filePath, params.command, otherArgs, token); return performQuickAction(p, docUri, params.command, otherArgs, token);
}, 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 { DiagnosticRule } from './diagnosticRules';
import { FileSystem } from './fileSystem'; import { FileSystem } from './fileSystem';
import { Host } from './host'; import { Host } from './host';
import {
FileSpec,
combinePaths,
ensureTrailingDirectorySeparator,
getFileSpec,
isDirectory,
normalizePath,
realCasePath,
resolvePaths,
} from './pathUtils';
import { PythonVersion, latestStablePythonVersion, versionFromString, versionToString } from './pythonVersion'; import { PythonVersion, latestStablePythonVersion, versionFromString, versionToString } from './pythonVersion';
import { ServiceProvider } from './serviceProvider'; import { ServiceProvider } from './serviceProvider';
import { ServiceKeys } from './serviceProviderExtensions'; import { ServiceKeys } from './serviceProviderExtensions';
import { Uri } from './uri/uri';
import { FileSpec, getFileSpec, isDirectory } from './uri/uriUtils';
export enum PythonPlatform { export enum PythonPlatform {
Darwin = 'Darwin', Darwin = 'Darwin',
@ -39,10 +31,9 @@ export enum PythonPlatform {
} }
export class ExecutionEnvironment { export class ExecutionEnvironment {
// Root directory for execution - absolute or relative to the // Root directory for execution.
// project root.
// Undefined if this is a rootless environment (e.g., open file mode). // 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 // Name of a virtual environment if there is one, otherwise
// just the path to the python executable. // just the path to the python executable.
@ -55,18 +46,18 @@ export class ExecutionEnvironment {
pythonPlatform?: string | undefined; pythonPlatform?: string | undefined;
// Default to no extra paths. // Default to no extra paths.
extraPaths: string[] = []; extraPaths: Uri[] = [];
// Default to "." which indicates every file in the project. // Default to "." which indicates every file in the project.
constructor( constructor(
name: string, name: string,
root: string, root: Uri,
defaultPythonVersion: PythonVersion | undefined, defaultPythonVersion: PythonVersion | undefined,
defaultPythonPlatform: string | undefined, defaultPythonPlatform: string | undefined,
defaultExtraPaths: string[] | undefined defaultExtraPaths: Uri[] | undefined
) { ) {
this.name = name; this.name = name;
this.root = root || undefined; this.root = root;
this.pythonVersion = defaultPythonVersion || latestStablePythonVersion; this.pythonVersion = defaultPythonVersion || latestStablePythonVersion;
this.pythonPlatform = defaultPythonPlatform; this.pythonPlatform = defaultPythonPlatform;
this.extraPaths = Array.from(defaultExtraPaths ?? []); this.extraPaths = Array.from(defaultExtraPaths ?? []);
@ -780,9 +771,9 @@ export function getStrictDiagnosticRuleSet(): DiagnosticRuleSet {
return diagSettings; 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) { 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; return true;
} }
} }
@ -795,19 +786,19 @@ export function matchFileSpecs(configOptions: ConfigOptions, filePath: string, i
export class ConfigOptions { export class ConfigOptions {
// Absolute directory of project. All relative paths in the config // Absolute directory of project. All relative paths in the config
// are based on this path. // are based on this path.
projectRoot: string; projectRoot: Uri;
// Path to python interpreter. // Path to python interpreter.
pythonPath?: string | undefined; pythonPath?: Uri | undefined;
// Name of the python environment. // Name of the python environment.
pythonEnvironmentName?: string | undefined; pythonEnvironmentName?: string | undefined;
// Path to use for typeshed definitions. // Path to use for typeshed definitions.
typeshedPath?: string | undefined; typeshedPath?: Uri | undefined;
// Path to custom typings (stub) modules. // Path to custom typings (stub) modules.
stubPath?: string | undefined; stubPath?: Uri | undefined;
// A list of file specs to include in the analysis. Can contain // A list of file specs to include in the analysis. Can contain
// directories, in which case all "*.py" files within those directories // 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 // directories. This is used in conjunction with the "venv" name in
// the config file to identify the python environment used for resolving // the config file to identify the python environment used for resolving
// third-party modules. // third-party modules.
venvPath?: string | undefined; venvPath?: Uri | undefined;
// Default venv environment. // Default venv environment.
venv?: string | undefined; venv?: string | undefined;
@ -899,7 +890,7 @@ export class ConfigOptions {
defaultPythonPlatform?: string | undefined; defaultPythonPlatform?: string | undefined;
// Default extraPaths. Can be overridden by executionEnvironment. // Default extraPaths. Can be overridden by executionEnvironment.
defaultExtraPaths?: string[] | undefined; defaultExtraPaths?: Uri[] | undefined;
//--------------------------------------------------------------- //---------------------------------------------------------------
// Internal-only switches // Internal-only switches
@ -917,7 +908,7 @@ export class ConfigOptions {
// Controls how hover and completion function signatures are displayed. // Controls how hover and completion function signatures are displayed.
functionSignatureDisplay: SignatureDisplayType; functionSignatureDisplay: SignatureDisplayType;
constructor(projectRoot: string, typeCheckingMode?: string) { constructor(projectRoot: Uri, typeCheckingMode?: string) {
this.projectRoot = projectRoot; this.projectRoot = projectRoot;
this.typeCheckingMode = typeCheckingMode; this.typeCheckingMode = typeCheckingMode;
this.diagnosticRuleSet = ConfigOptions.getDiagnosticRuleSet(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. // specified file path should be absolute.
// If no matching execution environment can be found, a default // If no matching execution environment can be found, a default
// execution environment is used. // execution environment is used.
findExecEnvironment(filePath: string): ExecutionEnvironment { findExecEnvironment(file: Uri): ExecutionEnvironment {
return ( return (
this.executionEnvironments.find((env) => { this.executionEnvironments.find((env) => {
const envRoot = ensureTrailingDirectorySeparator( const envRoot = Uri.isUri(env.root) ? env.root : this.projectRoot.combinePaths(env.root || '');
normalizePath(combinePaths(this.projectRoot, env.root)) return file.startsWith(envRoot);
);
return filePath.startsWith(envRoot);
}) ?? this.getDefaultExecEnvironment() }) ?? this.getDefaultExecEnvironment()
); );
} }
@ -997,7 +986,7 @@ export class ConfigOptions {
} else if (isAbsolute(fileSpec)) { } else if (isAbsolute(fileSpec)) {
console.error(`Ignoring path "${fileSpec}" in "include" array because it is not relative.`); console.error(`Ignoring path "${fileSpec}" in "include" array because it is not relative.`);
} else { } 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)) { } else if (isAbsolute(fileSpec)) {
console.error(`Ignoring path "${fileSpec}" in "exclude" array because it is not relative.`); console.error(`Ignoring path "${fileSpec}" in "exclude" array because it is not relative.`);
} else { } 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)) { } else if (isAbsolute(fileSpec)) {
console.error(`Ignoring path "${fileSpec}" in "ignore" array because it is not relative.`); console.error(`Ignoring path "${fileSpec}" in "ignore" array because it is not relative.`);
} else { } 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)) { } else if (isAbsolute(fileSpec)) {
console.error(`Ignoring path "${fileSpec}" in "strict" array because it is not relative.`); console.error(`Ignoring path "${fileSpec}" in "strict" array because it is not relative.`);
} else { } 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') { if (typeof configObj.venvPath !== 'string') {
console.error(`Config "venvPath" field must contain a string.`); console.error(`Config "venvPath" field must contain a string.`);
} else { } 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') { if (typeof path !== 'string') {
console.error(`Config "extraPaths" field ${pathIndex} must be a string.`); console.error(`Config "extraPaths" field ${pathIndex} must be a string.`);
} else { } 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.`); console.error(`Config "typeshedPath" field must contain a string.`);
} else { } else {
this.typeshedPath = configObj.typeshedPath 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.`); console.error(`Config "typingsPath" field must contain a string.`);
} else { } else {
console.error(`Config "typingsPath" is now deprecated. Please, use stubPath instead.`); 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') { if (typeof configObj.stubPath !== 'string') {
console.error(`Config "stubPath" field must contain a string.`); console.error(`Config "stubPath" field must contain a string.`);
} else { } 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) { ensureDefaultExtraPaths(fs: FileSystem, autoSearchPaths: boolean, extraPaths: string[] | undefined) {
const paths: string[] = []; const paths: Uri[] = [];
if (autoSearchPaths) { if (autoSearchPaths) {
// Auto-detect the common scenario where the sources are under the src folder // Auto-detect the common scenario where the sources are under the src folder
const srcPath = resolvePaths(this.projectRoot, pathConsts.src); const srcPath = this.projectRoot.combinePaths(pathConsts.src);
if (fs.existsSync(srcPath) && !fs.existsSync(resolvePaths(srcPath, '__init__.py'))) { if (fs.existsSync(srcPath) && !fs.existsSync(srcPath.combinePaths('__init__.py'))) {
paths.push(realCasePath(srcPath, fs)); paths.push(fs.realCasePath(srcPath));
} }
} }
if (extraPaths && extraPaths.length > 0) { if (extraPaths && extraPaths.length > 0) {
for (const p of extraPaths) { for (const p of extraPaths) {
const path = resolvePaths(this.projectRoot, p); const path = this.projectRoot.combinePaths(p);
paths.push(realCasePath(path, fs)); paths.push(fs.realCasePath(path));
if (isDirectory(fs, path)) { if (isDirectory(fs, path)) {
appendArray(paths, getPathsFromPthFiles(fs, path)); appendArray(paths, getPathsFromPthFiles(fs, path));
} }
@ -1384,7 +1373,7 @@ export class ConfigOptions {
} }
private _getEnvironmentName(): string { 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 { private _convertBoolean(value: any, fieldName: string, defaultValue: boolean): boolean {
@ -1429,7 +1418,7 @@ export class ConfigOptions {
// Validate the root. // Validate the root.
if (envObj.root && typeof envObj.root === 'string') { if (envObj.root && typeof envObj.root === 'string') {
newExecEnv.root = normalizePath(combinePaths(this.projectRoot, envObj.root)); newExecEnv.root = this.projectRoot.combinePaths(envObj.root);
} else { } else {
console.error(`Config executionEnvironments index ${index}: missing root value.`); console.error(`Config executionEnvironments index ${index}: missing root value.`);
} }
@ -1449,7 +1438,7 @@ export class ConfigOptions {
` extraPaths field ${pathIndex} must be a string.` ` extraPaths field ${pathIndex} must be a string.`
); );
} else { } 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 { appendArray } from './collectionUtils';
import { DiagnosticLevel } from './configOptions'; import { DiagnosticLevel } from './configOptions';
import { Range, TextRange } from './textRange'; import { Range, TextRange } from './textRange';
import { Uri } from './uri/uri';
const defaultMaxDepth = 5; const defaultMaxDepth = 5;
const defaultMaxLineCount = 8; const defaultMaxLineCount = 8;
@ -63,7 +64,7 @@ export interface DiagnosticAction {
} }
export interface DiagnosticWithinFile { export interface DiagnosticWithinFile {
filePath: string; uri: Uri;
diagnostic: Diagnostic; diagnostic: Diagnostic;
} }
@ -74,13 +75,13 @@ export interface CreateTypeStubFileAction extends DiagnosticAction {
export interface RenameShadowedFileAction extends DiagnosticAction { export interface RenameShadowedFileAction extends DiagnosticAction {
action: ActionKind.RenameShadowedFileAction; action: ActionKind.RenameShadowedFileAction;
oldFile: string; oldUri: Uri;
newFile: string; newUri: Uri;
} }
export interface DiagnosticRelatedInfo { export interface DiagnosticRelatedInfo {
message: string; message: string;
filePath: string; uri: Uri;
range: Range; range: Range;
priority: TaskListPriority; priority: TaskListPriority;
} }
@ -118,13 +119,8 @@ export class Diagnostic {
return this._rule; return this._rule;
} }
addRelatedInfo( addRelatedInfo(message: string, fileUri: Uri, range: Range, priority: TaskListPriority = TaskListPriority.Normal) {
message: string, this._relatedInfo.push({ uri: fileUri, message, range, priority });
filePath: string,
range: Range,
priority: TaskListPriority = TaskListPriority.Normal
) {
this._relatedInfo.push({ filePath, message, range, priority });
} }
getRelatedInfo() { getRelatedInfo() {

View File

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

View File

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

View File

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

View File

@ -11,19 +11,20 @@ import { CancellationToken } from 'vscode-languageserver';
import { Declaration } from '../analyzer/declaration'; import { Declaration } from '../analyzer/declaration';
import { ImportResolver } from '../analyzer/importResolver'; import { ImportResolver } from '../analyzer/importResolver';
import * as prog from '../analyzer/program'; import * as prog from '../analyzer/program';
import { IPythonMode } from '../analyzer/sourceFile';
import { SourceMapper } from '../analyzer/sourceMapper'; import { SourceMapper } from '../analyzer/sourceMapper';
import { SymbolTable } from '../analyzer/symbol';
import { TypeEvaluator } from '../analyzer/typeEvaluatorTypes'; import { TypeEvaluator } from '../analyzer/typeEvaluatorTypes';
import { Diagnostic } from '../common/diagnostic';
import { ServerSettings } from '../languageServerBase'; import { ServerSettings } from '../languageServerBase';
import { ParseNode } from '../parser/parseNodes'; import { ParseNode } from '../parser/parseNodes';
import { ParseResults } from '../parser/parser'; import { ParseResults } from '../parser/parser';
import { ConfigOptions } from './configOptions'; import { ConfigOptions } from './configOptions';
import { ConsoleInterface } from './console'; import { ConsoleInterface } from './console';
import { ReadOnlyFileSystem } from './fileSystem'; 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 { GroupServiceKey, ServiceKey } from './serviceProvider';
import { Range } from './textRange';
import { Uri } from './uri/uri';
export interface SourceFile { export interface SourceFile {
// See whether we can convert these to regular properties. // See whether we can convert these to regular properties.
@ -31,9 +32,8 @@ export interface SourceFile {
isThirdPartyPyTypedPresent(): boolean; isThirdPartyPyTypedPresent(): boolean;
getIPythonMode(): IPythonMode; getIPythonMode(): IPythonMode;
getFilePath(): string; getUri(): Uri;
getFileContent(): string | undefined; getFileContent(): string | undefined;
getRealFilePath(): string | undefined;
getClientVersion(): number | undefined; getClientVersion(): number | undefined;
getOpenFileContents(): string | undefined; getOpenFileContents(): string | undefined;
getModuleSymbolTable(): SymbolTable | undefined; getModuleSymbolTable(): SymbolTable | undefined;
@ -73,7 +73,7 @@ export interface ServiceProvider {
// Readonly wrapper around a Program. Makes sure it doesn't mutate the program. // Readonly wrapper around a Program. Makes sure it doesn't mutate the program.
export interface ProgramView { export interface ProgramView {
readonly id: string; readonly id: string;
readonly rootPath: string; readonly rootPath: Uri;
readonly console: ConsoleInterface; readonly console: ConsoleInterface;
readonly evaluator: TypeEvaluator | undefined; readonly evaluator: TypeEvaluator | undefined;
readonly configOptions: ConfigOptions; readonly configOptions: ConfigOptions;
@ -81,21 +81,16 @@ export interface ProgramView {
readonly fileSystem: ReadOnlyFileSystem; readonly fileSystem: ReadOnlyFileSystem;
readonly serviceProvider: ServiceProvider; readonly serviceProvider: ServiceProvider;
owns(file: string): boolean; owns(uri: Uri): boolean;
getSourceFileInfoList(): readonly SourceFileInfo[]; getSourceFileInfoList(): readonly SourceFileInfo[];
getParseResults(filePath: string): ParseResults | undefined; getParseResults(fileUri: Uri): ParseResults | undefined;
getSourceFileInfo(filePath: string): SourceFileInfo | undefined; getSourceFileInfo(fileUri: Uri): SourceFileInfo | undefined;
getChainedFilePath(filePath: string): string | undefined; getChainedUri(fileUri: Uri): Uri | undefined;
getSourceMapper( getSourceMapper(fileUri: Uri, token: CancellationToken, mapCompiled?: boolean, preferStubs?: boolean): SourceMapper;
filePath: string,
token: CancellationToken,
mapCompiled?: boolean,
preferStubs?: boolean
): SourceMapper;
// Consider getDiagnosticsForRange to call `analyzeFile` automatically if the file is not analyzed. // Consider getDiagnosticsForRange to call `analyzeFile` automatically if the file is not analyzed.
analyzeFile(filePath: string, token: CancellationToken): boolean; analyzeFile(fileUri: Uri, token: CancellationToken): boolean;
getDiagnosticsForRange(filePath: string, range: Range): Diagnostic[]; getDiagnosticsForRange(fileUri: Uri, range: Range): Diagnostic[];
// See whether we can get rid of these methods // See whether we can get rid of these methods
handleMemoryHighUsage(): void; handleMemoryHighUsage(): void;
@ -106,9 +101,9 @@ export interface ProgramView {
// and doesn't forward the request to the BG thread. // and doesn't forward the request to the BG thread.
// One can use this when edits are temporary such as `runEditMode` or `test` // One can use this when edits are temporary such as `runEditMode` or `test`
export interface EditableProgram extends ProgramView { export interface EditableProgram extends ProgramView {
addInterimFile(file: string): void; addInterimFile(uri: Uri): void;
setFileOpened(filePath: string, version: number | null, contents: string, options?: prog.OpenFileOptions): void; setFileOpened(fileUri: Uri, version: number | null, contents: string, options?: prog.OpenFileOptions): void;
updateChainedFilePath(filePath: string, chainedFilePath: string | undefined): 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 // 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 { export interface ProgramMutator {
addInterimFile(file: string): void; addInterimFile(file: string): void;
setFileOpened( setFileOpened(
filePath: string, fileUri: Uri,
version: number | null, version: number | null,
contents: string, contents: string,
ipythonMode: IPythonMode, ipythonMode: IPythonMode,
@ -162,7 +157,7 @@ export interface SymbolUsageProvider {
} }
export interface StatusMutationListener { export interface StatusMutationListener {
fileDirty?: (filePath: string) => void; fileDirty?: (fileUri: Uri) => void;
clearCache?: () => void; clearCache?: () => void;
updateSettings?: <T extends ServerSettings>(settings: T) => 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" // * NOTE * except tests, this should be only file that import "fs"
import type * as fs from 'fs'; import type * as fs from 'fs';
import { FileWatcher, FileWatcherEventHandler } from './fileWatcher'; import { FileWatcher, FileWatcherEventHandler } from './fileWatcher';
import { Uri } from './uri/uri';
export interface Stats { export interface Stats {
size: number; size: number;
@ -33,48 +34,47 @@ export interface MkDirOptions {
} }
export interface ReadOnlyFileSystem { export interface ReadOnlyFileSystem {
existsSync(path: string): boolean; readonly isCaseSensitive: boolean;
chdir(path: string): void; existsSync(uri: Uri): boolean;
readdirEntriesSync(path: string): fs.Dirent[]; chdir(uri: Uri): void;
readdirSync(path: string): string[]; readdirEntriesSync(uri: Uri): fs.Dirent[];
readFileSync(path: string, encoding?: null): Buffer; readdirSync(uri: Uri): string[];
readFileSync(path: string, encoding: BufferEncoding): string; readFileSync(uri: Uri, encoding?: null): Buffer;
readFileSync(path: string, encoding?: BufferEncoding | null): string | Buffer; readFileSync(uri: Uri, encoding: BufferEncoding): string;
readFileSync(uri: Uri, encoding?: BufferEncoding | null): string | Buffer;
statSync(path: string): Stats; statSync(uri: Uri): Stats;
realpathSync(path: string): string; realpathSync(uri: Uri): Uri;
getModulePath(): string; getModulePath(): Uri;
// Async I/O // Async I/O
readFile(path: string): Promise<Buffer>; readFile(uri: Uri): Promise<Buffer>;
readFileText(path: string, encoding?: BufferEncoding): Promise<string>; readFileText(uri: Uri, encoding?: BufferEncoding): Promise<string>;
// Return path in casing on OS. // Return path in casing on OS.
realCasePath(path: string): string; realCasePath(uri: Uri): Uri;
// See whether the file is mapped to another location. // 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. // Get original uri if the given uri is mapped.
getOriginalFilePath(mappedFilePath: string): string; getOriginalUri(mappedUri: Uri): Uri;
// Get mapped filepath if the given filepath is mapped. // Get mapped uri if the given uri is mapped.
getMappedFilePath(originalFilepath: string): string; getMappedUri(originalUri: Uri): Uri;
getUri(path: string): string; isInZip(uri: Uri): boolean;
isInZip(path: string): boolean;
} }
export interface FileSystem extends ReadOnlyFileSystem { export interface FileSystem extends ReadOnlyFileSystem {
mkdirSync(path: string, options?: MkDirOptions): void; mkdirSync(uri: Uri, options?: MkDirOptions): void;
writeFileSync(path: string, data: string | Buffer, encoding: BufferEncoding | null): void; writeFileSync(uri: Uri, data: string | Buffer, encoding: BufferEncoding | null): void;
unlinkSync(path: string): void; unlinkSync(uri: Uri): void;
rmdirSync(path: string): void; rmdirSync(uri: Uri): void;
createFileSystemWatcher(paths: string[], listener: FileWatcherEventHandler): FileWatcher; createFileSystemWatcher(uris: Uri[], listener: FileWatcherEventHandler): FileWatcher;
createReadStream(path: string): fs.ReadStream; createReadStream(uri: Uri): fs.ReadStream;
createWriteStream(path: string): fs.WriteStream; createWriteStream(uri: Uri): fs.WriteStream;
copyFileSync(src: string, dst: string): void; copyFileSync(uri: Uri, dst: Uri): void;
} }
export interface TmpfileOptions { export interface TmpfileOptions {
@ -84,8 +84,8 @@ export interface TmpfileOptions {
export interface TempFile { export interface TempFile {
// The directory returned by tmpdir must exist and be the same each time tmpdir is called. // The directory returned by tmpdir must exist and be the same each time tmpdir is called.
tmpdir(): string; tmpdir(): Uri;
tmpfile(options?: TmpfileOptions): string; tmpfile(options?: TmpfileOptions): Uri;
dispose(): void; dispose(): void;
} }

View File

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

View File

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

View File

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

View File

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

View File

@ -7,24 +7,14 @@
* Pathname utility functions. * Pathname utility functions.
*/ */
import type { Dirent } from 'fs';
import * as path from 'path'; import * as path from 'path';
import { URI, Utils } from 'vscode-uri';
import { Char } from './charCodes'; import { Char } from './charCodes';
import { some } from './collectionUtils'; import { some } from './collectionUtils';
import { GetCanonicalFileName, identity } from './core'; import { GetCanonicalFileName, identity } from './core';
import { randomBytesHex } from './crypto';
import * as debug from './debug'; 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'; import { equateStringsCaseInsensitive, equateStringsCaseSensitive } from './stringUtils';
let _fsCaseSensitivity: boolean | undefined = undefined;
let _underTest: boolean = false;
const _uriSchemePattern = /^\w[\w\d+.-]*$/;
export interface FileSpec { export interface FileSpec {
// File specs can contain wildcard characters (**, *, ?). This // File specs can contain wildcard characters (**, *, ?). This
// specifies the first portion of the file spec that contains // specifies the first portion of the file spec that contains
@ -72,52 +62,26 @@ export interface FileSystemEntries {
directories: string[]; 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 { 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))); 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/"). * 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 { export function getRootLength(pathString: string, sep = path.sep): number {
if (pathString.charAt(0) === path.sep) { if (pathString.charAt(0) === sep) {
if (pathString.charAt(1) !== path.sep) { if (pathString.charAt(1) !== sep) {
return 1; // POSIX: "/" (or non-normalized "\") return 1; // POSIX: "/" (or non-normalized "\")
} }
const p1 = pathString.indexOf(path.sep, 2); const p1 = pathString.indexOf(sep, 2);
if (p1 < 0) { if (p1 < 0) {
return pathString.length; // UNC: "//server" or "\\server" return pathString.length; // UNC: "//server" or "\\server"
} }
return p1 + 1; // UNC: "//server/" or "\\server\" return p1 + 1; // UNC: "//server/" or "\\server\"
} }
if (pathString.charAt(1) === ':') { if (pathString.charAt(1) === ':') {
if (pathString.charAt(2) === path.sep) { if (pathString.charAt(2) === sep) {
return 3; // DOS: "c:/" or "c:\" return 3; // DOS: "c:/" or "c:\"
} }
if (pathString.length === 2) { 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; return 0;
} }
export function getPathSeparator(pathString: string) { export function getPathSeparator(pathString: string) {
return isUri(pathString) ? '/' : path.sep; return path.sep;
} }
export function getPathComponents(pathString: string) { export function getPathComponents(pathString: string) {
const normalizedPath = normalizeSlashes(pathString); const normalizedPath = normalizeSlashes(pathString);
const rootLength = getRootLength(normalizedPath, /* checkUri */ isUri(normalizedPath)); const rootLength = getRootLength(normalizedPath);
const root = normalizedPath.substring(0, rootLength); const root = normalizedPath.substring(0, rootLength);
const sep = getPathSeparator(pathString); const sep = getPathSeparator(pathString);
const rest = normalizedPath.substring(rootLength).split(sep); const rest = normalizedPath.substring(rootLength).split(sep);
@ -211,47 +166,11 @@ export function getRelativePath(dirPath: string, relativeTo: string) {
return relativePath; 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 === '/' ? '\\' : '/'); const getInvalidSeparator = (sep: string) => (sep === '/' ? '\\' : '/');
export function normalizeSlashes(pathString: string, sep = path.sep): string { export function normalizeSlashes(pathString: string, sep = path.sep): string {
if (!isUri(pathString)) { if (pathString.includes(getInvalidSeparator(sep))) {
if (pathString.includes(getInvalidSeparator(sep))) { const separatorRegExp = /[\\/]/g;
const separatorRegExp = /[\\/]/g; return pathString.replace(separatorRegExp, sep);
return pathString.replace(separatorRegExp, sep);
}
} }
return pathString; return pathString;
@ -271,7 +190,7 @@ export function resolvePaths(path: string, ...paths: (string | undefined)[]): st
return normalizePath(some(paths) ? combinePaths(path, ...paths) : normalizeSlashes(path)); 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) { if (pathString) {
pathString = normalizeSlashes(pathString); pathString = normalizeSlashes(pathString);
} }
@ -283,7 +202,7 @@ function combineFilePaths(pathString: string, ...paths: (string | undefined)[]):
relativePath = normalizeSlashes(relativePath); relativePath = normalizeSlashes(relativePath);
if (!pathString || getRootLength(relativePath, /* checkUri */ false) !== 0) { if (!pathString || getRootLength(relativePath) !== 0) {
pathString = relativePath; pathString = relativePath;
} else { } else {
pathString = ensureTrailingDirectorySeparator(pathString) + relativePath; pathString = ensureTrailingDirectorySeparator(pathString) + relativePath;
@ -293,29 +212,6 @@ function combineFilePaths(pathString: string, ...paths: (string | undefined)[]):
return pathString; 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. * 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 // return the trailing portion of the path starting after the last (non-terminal) directory
// separator but not including any trailing directory separator. // separator but not including any trailing directory separator.
pathString = stripTrailingDirectorySeparator(pathString); pathString = stripTrailingDirectorySeparator(pathString);
const name = isUri(pathString) const name = pathString.slice(Math.max(getRootLength(pathString), pathString.lastIndexOf(path.sep) + 1));
? Utils.basename(URI.parse(pathString).with({ fragment: '', query: '' }))
: pathString.slice(Math.max(getRootLength(pathString), pathString.lastIndexOf(path.sep) + 1));
const extension = const extension =
extensions !== undefined && ignoreCase !== undefined extensions !== undefined && ignoreCase !== undefined
? getAnyExtensionFromPath(name, extensions, ignoreCase) ? getAnyExtensionFromPath(name, extensions, ignoreCase)
@ -561,7 +455,7 @@ export function stripTrailingDirectorySeparator(pathString: string) {
if (!hasTrailingDirectorySeparator(pathString)) { if (!hasTrailingDirectorySeparator(pathString)) {
return pathString; return pathString;
} }
return pathString.substr(0, pathString.length - 1); return pathString.slice(0, pathString.length - 1);
} }
export function getFileExtension(fileName: string, multiDotExtension = false) { export function getFileExtension(fileName: string, multiDotExtension = false) {
@ -571,7 +465,7 @@ export function getFileExtension(fileName: string, multiDotExtension = false) {
fileName = getFileName(fileName); fileName = getFileName(fileName);
const firstDotIndex = fileName.indexOf('.'); const firstDotIndex = fileName.indexOf('.');
return fileName.substr(firstDotIndex); return fileName.slice(firstDotIndex);
} }
export function getFileName(pathString: string) { export function getFileName(pathString: string) {
@ -592,102 +486,8 @@ export function stripFileExtension(fileName: string, multiDotExtension = false)
return fileName.substr(0, fileName.length - ext.length); 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 { export function normalizePath(pathString: string): string {
if (!isUri(pathString)) { return normalizeSlashes(path.normalize(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 };
} }
// Transforms a relative file spec (one that potentially contains // 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'); 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) { export function getRegexEscapedSeparator(pathSep: string = path.sep) {
// we don't need to escape "/" in typescript regular expression // we don't need to escape "/" in typescript regular expression
return pathSep === '/' ? '/' : '\\\\'; return pathSep === '/' ? '/' : '\\\\';
@ -908,168 +689,3 @@ function getPathComponentsRelativeTo(
} }
return ['', ...relative, ...components]; 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 { getLibzipSync } from '@yarnpkg/libzip';
import * as fs from 'fs'; import * as fs from 'fs';
import * as tmp from 'tmp'; import * as tmp from 'tmp';
import { URI } from 'vscode-uri';
import { isMainThread } from 'worker_threads'; import { isMainThread } from 'worker_threads';
import { ConsoleInterface, NullConsole } from './console'; import { ConsoleInterface, NullConsole } from './console';
@ -21,7 +20,9 @@ import {
FileWatcherProvider, FileWatcherProvider,
nullFileWatcherProvider, nullFileWatcherProvider,
} from './fileWatcher'; } 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. // Automatically remove files created by tmp at process exit.
tmp.setGracefulCleanup(); tmp.setGracefulCleanup();
@ -208,9 +209,19 @@ class YarnFS extends PosixFS {
const yarnFS = new YarnFS(); const yarnFS = new YarnFS();
class RealFileSystem implements FileSystem { 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 { try {
// Catch zip open errors. existsSync is assumed to never throw by callers. // Catch zip open errors. existsSync is assumed to never throw by callers.
return yarnFS.existsSync(path); 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); 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, // If this file system happens to be running in a worker thread,
// then we can't call 'chdir'. // then we can't call 'chdir'.
if (isMainThread) { 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); 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 => { return yarnFS.readdirSync(path, { withFileTypes: true }).map((entry): fs.Dirent => {
// Treat zip/egg files as directories. // Treat zip/egg files as directories.
// See: https://github.com/yarnpkg/berry/blob/master/packages/vscode-zipfs/sources/ZipFSProvider.ts // 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(uri: Uri, encoding?: null): Buffer;
readFileSync(path: string, encoding: BufferEncoding): string; readFileSync(uri: Uri, encoding: BufferEncoding): string;
readFileSync(path: string, encoding?: BufferEncoding | null): Buffer | string; readFileSync(uri: Uri, encoding?: BufferEncoding | null): Buffer | string;
readFileSync(path: string, encoding: BufferEncoding | null = null) { readFileSync(uri: Uri, encoding: BufferEncoding | null = null) {
const path = uri.getFilePath();
if (encoding === 'utf8' || encoding === 'utf-8') { if (encoding === 'utf8' || encoding === 'utf-8') {
return yarnFS.readFileSync(path, 'utf8'); return yarnFS.readFileSync(path, 'utf8');
} }
return yarnFS.readFileSync(path); 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); yarnFS.writeFileSync(path, data, encoding || undefined);
} }
statSync(path: string) { statSync(uri: Uri) {
const path = uri.getFilePath();
const stat = yarnFS.statSync(path); const stat = yarnFS.statSync(path);
// Treat zip/egg files as directories. // Treat zip/egg files as directories.
// See: https://github.com/yarnpkg/berry/blob/master/packages/vscode-zipfs/sources/ZipFSProvider.ts // See: https://github.com/yarnpkg/berry/blob/master/packages/vscode-zipfs/sources/ZipFSProvider.ts
@ -286,53 +304,62 @@ class RealFileSystem implements FileSystem {
return stat; return stat;
} }
rmdirSync(path: string): void { rmdirSync(uri: Uri): void {
const path = uri.getFilePath();
yarnFS.rmdirSync(path); yarnFS.rmdirSync(path);
} }
unlinkSync(path: string) { unlinkSync(uri: Uri) {
const path = uri.getFilePath();
yarnFS.unlinkSync(path); yarnFS.unlinkSync(path);
} }
realpathSync(path: string) { realpathSync(uri: Uri) {
try { try {
return yarnFS.realpathSync(path); const path = uri.getFilePath();
return Uri.file(yarnFS.realpathSync(path), this._isCaseSensitive);
} catch (e: any) { } catch (e: any) {
return path; return uri;
} }
} }
getModulePath(): string { getModulePath(): Uri {
// The entry point to the tool should have set the __rootDirectory // The entry point to the tool should have set the __rootDirectory
// global variable to point to the directory that contains the // global variable to point to the directory that contains the
// typeshed-fallback directory. // 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( return this._fileWatcherProvider.createFileWatcher(
paths.map((p) => this.realCasePath(p)), paths.map((p) => p.getFilePath()),
listener listener
); );
} }
createReadStream(path: string): fs.ReadStream { createReadStream(uri: Uri): fs.ReadStream {
const path = uri.getFilePath();
return yarnFS.createReadStream(path); return yarnFS.createReadStream(path);
} }
createWriteStream(path: string): fs.WriteStream { createWriteStream(uri: Uri): fs.WriteStream {
const path = uri.getFilePath();
return yarnFS.createWriteStream(path); return yarnFS.createWriteStream(path);
} }
copyFileSync(src: string, dst: string): void { copyFileSync(src: Uri, dst: Uri): void {
yarnFS.copyFileSync(src, dst); 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); 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') { if (encoding === 'utf8' || encoding === 'utf-8') {
return yarnFS.readFilePromise(path, 'utf8'); return yarnFS.readFilePromise(path, 'utf8');
} }
@ -340,17 +367,18 @@ class RealFileSystem implements FileSystem {
return buffer.toString(encoding); return buffer.toString(encoding);
} }
realCasePath(path: string): string { realCasePath(uri: Uri): Uri {
try { try {
// If it doesn't exist in the real FS, then just use this path. // If it doesn't exist in the real FS, then just use this path.
if (!this.existsSync(path)) { if (!this.existsSync(uri)) {
return this._getNormalizedPath(path); return uri;
} }
// If it does exist, skip this for symlinks. // If it does exist, skip this for symlinks.
const path = uri.getFilePath();
const stat = fs.lstatSync(path); const stat = fs.lstatSync(path);
if (stat.isSymbolicLink()) { if (stat.isSymbolicLink()) {
return this._getNormalizedPath(path); return uri;
} }
// realpathSync.native will return casing as in OS rather than // 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. // On UNC mapped drives we want to keep the original drive letter.
if (getRootLength(realCase) !== getRootLength(path)) { if (getRootLength(realCase) !== getRootLength(path)) {
return path; return uri;
} }
return realCase; return Uri.file(realCase, this._isCaseSensitive);
} catch (e: any) { } catch (e: any) {
// Return as it is, if anything failed. // 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; return false;
} }
getOriginalFilePath(mappedFilePath: string) { getOriginalUri(mappedUri: Uri) {
return mappedFilePath; return mappedUri;
} }
getMappedFilePath(originalFilepath: string) { getMappedUri(originalUri: Uri) {
return originalFilepath; return originalUri;
} }
getUri(path: string): string { isInZip(uri: Uri): boolean {
// If this is not a file path, just return the original path. const path = uri.getFilePath();
if (isUri(path)) {
return path;
}
return URI.file(path).toString();
}
isInZip(path: string): boolean {
return /[^\\/]\.(?:egg|zip|jar)[\\/]/.test(path) && yarnFS.isZip(path); 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 { interface WorkspaceFileWatcher extends FileWatcher {
@ -436,15 +445,15 @@ export class WorkspaceFileWatcherProvider implements FileWatcherProvider, FileWa
return fileWatcher; 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 // 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 // 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 // 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 // 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. // for a file change. It is event handler's job to filter those out.
this._fileWatchers.forEach((watcher) => { this._fileWatchers.forEach((watcher) => {
if (watcher.workspacePaths.some((dirPath) => filePath.startsWith(dirPath))) { if (watcher.workspacePaths.some((dirPath) => fileUri.pathStartsWith(dirPath))) {
watcher.eventHandler(eventType, filePath); watcher.eventHandler(eventType, fileUri);
} }
}); });
} }
@ -453,17 +462,15 @@ export class WorkspaceFileWatcherProvider implements FileWatcherProvider, FileWa
export class RealTempFile implements TempFile { export class RealTempFile implements TempFile {
private _tmpdir?: tmp.DirResult; private _tmpdir?: tmp.DirResult;
tmpdir() { constructor(private readonly _isCaseSensitive: boolean) {}
if (!this._tmpdir) {
this._tmpdir = tmp.dirSync({ prefix: 'pyright' });
}
return this._tmpdir.name; tmpdir(): Uri {
return Uri.file(this._getTmpDir().name, this._isCaseSensitive);
} }
tmpfile(options?: TmpfileOptions): string { tmpfile(options?: TmpfileOptions): Uri {
const f = tmp.fileSync({ dir: this.tmpdir(), discardDescriptor: true, ...options }); const f = tmp.fileSync({ dir: this._getTmpDir().name, discardDescriptor: true, ...options });
return f.name; return Uri.file(f.name, this._isCaseSensitive);
} }
dispose(): void { dispose(): void {
@ -474,4 +481,12 @@ export class RealTempFile implements TempFile {
// ignore // 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 { SupportPartialStubs } from '../pyrightFileSystem';
import { ConsoleInterface } from './console'; import { ConsoleInterface } from './console';
import { import {
StatusMutationListener, DebugInfoInspector,
ServiceProvider as ReadOnlyServiceProvider, ServiceProvider as ReadOnlyServiceProvider,
StatusMutationListener,
SymbolDefinitionProvider, SymbolDefinitionProvider,
SymbolUsageProviderFactory, SymbolUsageProviderFactory,
DebugInfoInspector,
} from './extensibility'; } from './extensibility';
import { FileSystem, TempFile } from './fileSystem'; import { FileSystem, TempFile } from './fileSystem';
import { LogTracker } from './logTracker'; import { LogTracker } from './logTracker';
import { GroupServiceKey, ServiceKey, ServiceProvider } from './serviceProvider'; import { GroupServiceKey, ServiceKey, ServiceProvider } from './serviceProvider';
import { Uri } from './uri/uri';
declare module './serviceProvider' { declare module './serviceProvider' {
interface ServiceProvider { interface ServiceProvider {
fs(): FileSystem; fs(): FileSystem;
console(): ConsoleInterface; console(): ConsoleInterface;
tmp(): TempFile | undefined;
sourceFileFactory(): ISourceFileFactory; sourceFileFactory(): ISourceFileFactory;
partialStubs(): SupportPartialStubs; partialStubs(): SupportPartialStubs;
} }
@ -79,6 +81,9 @@ ServiceProvider.prototype.console = function () {
ServiceProvider.prototype.partialStubs = function () { ServiceProvider.prototype.partialStubs = function () {
return this.get(ServiceKeys.partialStubs); return this.get(ServiceKeys.partialStubs);
}; };
ServiceProvider.prototype.tmp = function () {
return this.tryGet(ServiceKeys.tempFile);
};
ServiceProvider.prototype.sourceFileFactory = function () { ServiceProvider.prototype.sourceFileFactory = function () {
const result = this.tryGet(ServiceKeys.sourceFileFactory); const result = this.tryGet(ServiceKeys.sourceFileFactory);
return result || DefaultSourceFileFactory; return result || DefaultSourceFileFactory;
@ -87,26 +92,24 @@ ServiceProvider.prototype.sourceFileFactory = function () {
const DefaultSourceFileFactory: ISourceFileFactory = { const DefaultSourceFileFactory: ISourceFileFactory = {
createSourceFile( createSourceFile(
serviceProvider: ReadOnlyServiceProvider, serviceProvider: ReadOnlyServiceProvider,
filePath: string, fileUri: Uri,
moduleName: string, moduleName: string,
isThirdPartyImport: boolean, isThirdPartyImport: boolean,
isThirdPartyPyTypedPresent: boolean, isThirdPartyPyTypedPresent: boolean,
editMode: SourceFileEditMode, editMode: SourceFileEditMode,
console?: ConsoleInterface, console?: ConsoleInterface,
logTracker?: LogTracker, logTracker?: LogTracker,
realFilePath?: string,
ipythonMode?: IPythonMode ipythonMode?: IPythonMode
) { ) {
return new SourceFile( return new SourceFile(
serviceProvider, serviceProvider,
filePath, fileUri,
moduleName, moduleName,
isThirdPartyImport, isThirdPartyImport,
isThirdPartyPyTypedPresent, isThirdPartyPyTypedPresent,
editMode, editMode,
console, console,
logTracker, logTracker,
realFilePath,
ipythonMode ipythonMode
); );
}, },

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -29,12 +29,12 @@ import { TypeEvaluator } from '../analyzer/typeEvaluatorTypes';
import { TypeCategory } from '../analyzer/types'; import { TypeCategory } from '../analyzer/types';
import { throwIfCancellationRequested } from '../common/cancellationUtils'; import { throwIfCancellationRequested } from '../common/cancellationUtils';
import { appendArray } from '../common/collectionUtils'; import { appendArray } from '../common/collectionUtils';
import { isDefined } from '../common/core';
import { assert } from '../common/debug'; import { assert } from '../common/debug';
import { ProgramView, ReferenceUseCase, SymbolUsageProvider } from '../common/extensibility'; import { ProgramView, ReferenceUseCase, SymbolUsageProvider } from '../common/extensibility';
import { ServiceKeys } from '../common/serviceProviderExtensions';
import { TextRange } from '../common/textRange'; import { TextRange } from '../common/textRange';
import { ImportAsNode, NameNode, ParseNode, ParseNodeType, StringListNode, StringNode } from '../parser/parseNodes'; import { ImportAsNode, NameNode, ParseNode, ParseNodeType, StringListNode, StringNode } from '../parser/parseNodes';
import { ServiceKeys } from '../common/serviceProviderExtensions';
import { isDefined } from '../common/core';
export type CollectionResult = { export type CollectionResult = {
node: NameNode | StringNode; node: NameNode | StringNode;
@ -184,17 +184,18 @@ export class DocumentSymbolCollector extends ParseTreeWalker {
const declarations = getDeclarationsForNameNode(evaluator, node, /* skipUnreachableCode */ false); const declarations = getDeclarationsForNameNode(evaluator, node, /* skipUnreachableCode */ false);
const fileInfo = AnalyzerNodeInfo.getFileInfo(node); const fileInfo = AnalyzerNodeInfo.getFileInfo(node);
const fileUri = fileInfo.fileUri;
const resolvedDeclarations: Declaration[] = []; const resolvedDeclarations: Declaration[] = [];
const sourceMapper = program.getSourceMapper(fileInfo.filePath, token); const sourceMapper = program.getSourceMapper(fileUri, token);
declarations.forEach((decl) => { declarations.forEach((decl) => {
const resolvedDecl = evaluator.resolveAliasDeclaration(decl, resolveLocalName); const resolvedDecl = evaluator.resolveAliasDeclaration(decl, resolveLocalName);
if (resolvedDecl) { if (resolvedDecl) {
addDeclarationIfUnique(resolvedDeclarations, resolvedDecl); addDeclarationIfUnique(resolvedDeclarations, resolvedDecl);
if (sourceMapper && isStubFile(resolvedDecl.path)) { if (sourceMapper && isStubFile(resolvedDecl.uri)) {
const implDecls = sourceMapper.findDeclarations(resolvedDecl); const implDecls = sourceMapper.findDeclarations(resolvedDecl);
for (const implDecl of implDecls) { for (const implDecl of implDecls) {
if (implDecl && implDecl.path) { if (implDecl && !implDecl.uri.isEmpty()) {
addDeclarationIfUnique(resolvedDeclarations, implDecl); 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) { if (sourceFileInfo && sourceFileInfo.sourceFile.getIPythonMode() === IPythonMode.CellDocs) {
// Add declarations from chained source files // Add declarations from chained source files
let builtinsScope = fileInfo.builtinsScope; let builtinsScope = fileInfo.builtinsScope;
@ -215,7 +216,7 @@ export class DocumentSymbolCollector extends ParseTreeWalker {
// Add declarations from files that implicitly import the target file. // Add declarations from files that implicitly import the target file.
const implicitlyImportedBy = collectImportedByCells(program, sourceFileInfo); const implicitlyImportedBy = collectImportedByCells(program, sourceFileInfo);
implicitlyImportedBy.forEach((implicitImport) => { implicitlyImportedBy.forEach((implicitImport) => {
const parseTree = program.getParseResults(implicitImport.sourceFile.getFilePath())?.parseTree; const parseTree = program.getParseResults(implicitImport.sourceFile.getUri())?.parseTree;
if (parseTree) { if (parseTree) {
const scope = AnalyzerNodeInfo.getScope(parseTree); const scope = AnalyzerNodeInfo.getScope(parseTree);
const symbol = scope?.lookUpSymbol(node.value); const symbol = scope?.lookUpSymbol(node.value);
@ -449,7 +450,7 @@ function _getDeclarationsForNonModuleNameNode(
const type = evaluator.getType(node); const type = evaluator.getType(node);
if (type?.category === TypeCategory.Module) { if (type?.category === TypeCategory.Module) {
// Synthesize decl for the 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 { CancellationToken, DocumentSymbol, Location, SymbolInformation } from 'vscode-languageserver';
import { getFileInfo } from '../analyzer/analyzerNodeInfo';
import { throwIfCancellationRequested } from '../common/cancellationUtils'; import { throwIfCancellationRequested } from '../common/cancellationUtils';
import { ProgramView } from '../common/extensibility';
import { Uri } from '../common/uri/uri';
import { ParseResults } from '../parser/parser'; import { ParseResults } from '../parser/parser';
import { IndexSymbolData, SymbolIndexer } from './symbolIndexer'; import { IndexSymbolData, SymbolIndexer } from './symbolIndexer';
import { ProgramView } from '../common/extensibility';
import { getFileInfo } from '../analyzer/analyzerNodeInfo';
import { convertPathToUri } from '../common/pathUtils';
export function convertToFlatSymbols( export function convertToFlatSymbols(
program: ProgramView, program: ProgramView,
filePath: string, uri: Uri,
symbolList: DocumentSymbol[] symbolList: DocumentSymbol[]
): SymbolInformation[] { ): SymbolInformation[] {
const flatSymbols: SymbolInformation[] = []; const flatSymbols: SymbolInformation[] = [];
const documentUri = convertPathToUri(program.fileSystem, filePath);
for (const symbol of symbolList) { for (const symbol of symbolList) {
_appendToFlatSymbolsRecursive(flatSymbols, documentUri, symbol); _appendToFlatSymbolsRecursive(flatSymbols, uri, symbol);
} }
return flatSymbols; return flatSymbols;
@ -37,11 +36,11 @@ export class DocumentSymbolProvider {
constructor( constructor(
protected readonly program: ProgramView, protected readonly program: ProgramView,
protected readonly filePath: string, protected readonly uri: Uri,
private readonly _supportHierarchicalDocumentSymbol: boolean, private readonly _supportHierarchicalDocumentSymbol: boolean,
private readonly _token: CancellationToken private readonly _token: CancellationToken
) { ) {
this._parseResults = this.program.getParseResults(this.filePath); this._parseResults = this.program.getParseResults(this.uri);
} }
getSymbols(): DocumentSymbol[] | SymbolInformation[] { getSymbols(): DocumentSymbol[] | SymbolInformation[] {
@ -54,12 +53,12 @@ export class DocumentSymbolProvider {
return symbolList; return symbolList;
} }
return convertToFlatSymbols(this.program, this.filePath, symbolList); return convertToFlatSymbols(this.program, this.uri, symbolList);
} }
protected getHierarchicalSymbols() { protected getHierarchicalSymbols() {
const symbolList: DocumentSymbol[] = []; const symbolList: DocumentSymbol[] = [];
const parseResults = this.program.getParseResults(this.filePath); const parseResults = this.program.getParseResults(this.uri);
if (!parseResults) { if (!parseResults) {
return symbolList; return symbolList;
} }
@ -115,14 +114,14 @@ export class DocumentSymbolProvider {
function _appendToFlatSymbolsRecursive( function _appendToFlatSymbolsRecursive(
flatSymbols: SymbolInformation[], flatSymbols: SymbolInformation[],
documentUri: string, documentUri: Uri,
symbol: DocumentSymbol, symbol: DocumentSymbol,
parent?: DocumentSymbol parent?: DocumentSymbol
) { ) {
const flatSymbol: SymbolInformation = { const flatSymbol: SymbolInformation = {
name: symbol.name, name: symbol.name,
kind: symbol.kind, kind: symbol.kind,
location: Location.create(documentUri, symbol.range), location: Location.create(documentUri.toString(), symbol.range),
}; };
if (symbol.tags) { if (symbol.tags) {

View File

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

View File

@ -7,10 +7,10 @@
*/ */
import { Location } from 'vscode-languageserver-types'; import { Location } from 'vscode-languageserver-types';
import { ReadOnlyFileSystem } from '../common/fileSystem'; import { ReadOnlyFileSystem } from '../common/fileSystem';
import { convertPathToUri } from '../common/pathUtils';
import { DocumentRange } from '../common/textRange'; 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); return !fs.isInZip(path);
} }
@ -23,9 +23,9 @@ export function convertDocumentRangesToLocation(
} }
export function convertDocumentRangeToLocation(fs: ReadOnlyFileSystem, range: DocumentRange): Location | undefined { export function convertDocumentRangeToLocation(fs: ReadOnlyFileSystem, range: DocumentRange): Location | undefined {
if (!canNavigateToFile(fs, range.path)) { if (!canNavigateToFile(fs, range.uri)) {
return undefined; 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 { Commands } from '../commands/commands';
import { ProgramView } from '../common/extensibility'; import { ProgramView } from '../common/extensibility';
import { Uri } from '../common/uri/uri';
import { ImportSorter } from './importSorter'; import { ImportSorter } from './importSorter';
export function performQuickAction( export function performQuickAction(
programView: ProgramView, programView: ProgramView,
filePath: string, uri: Uri,
command: string, command: string,
args: any[], args: any[],
token: CancellationToken token: CancellationToken
) { ) {
const sourceFileInfo = programView.getSourceFileInfo(filePath); const sourceFileInfo = programView.getSourceFileInfo(uri);
// This command should be called only for open files, in which // This command should be called only for open files, in which
// case we should have the file contents already loaded. // 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. // If we have no completed analysis job, there's nothing to do.
const parseResults = programView.getParseResults(filePath); const parseResults = programView.getParseResults(uri);
if (!parseResults) { if (!parseResults) {
return []; return [];
} }

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -11,6 +11,7 @@
import { ConfigOptions } from '../common/configOptions'; import { ConfigOptions } from '../common/configOptions';
import { PythonVersion } from '../common/pythonVersion'; import { PythonVersion } from '../common/pythonVersion';
import { Uri } from '../common/uri/uri';
import * as TestUtils from './testUtils'; import * as TestUtils from './testUtils';
test('BadToken1', () => { test('BadToken1', () => {
@ -34,7 +35,7 @@ test('CircularBaseClass', () => {
}); });
test('Private1', () => { test('Private1', () => {
const configOptions = new ConfigOptions('.'); const configOptions = new ConfigOptions(Uri.empty());
// By default, optional diagnostics are ignored. // By default, optional diagnostics are ignored.
let analysisResults = TestUtils.typeAnalyzeSampleFiles(['private1.py'], configOptions); let analysisResults = TestUtils.typeAnalyzeSampleFiles(['private1.py'], configOptions);
@ -47,7 +48,7 @@ test('Private1', () => {
}); });
test('Constant1', () => { test('Constant1', () => {
const configOptions = new ConfigOptions('.'); const configOptions = new ConfigOptions(Uri.empty());
// By default, optional diagnostics are ignored. // By default, optional diagnostics are ignored.
let analysisResults = TestUtils.typeAnalyzeSampleFiles(['constant1.py'], configOptions); let analysisResults = TestUtils.typeAnalyzeSampleFiles(['constant1.py'], configOptions);
@ -168,7 +169,7 @@ test('With3', () => {
}); });
test('With4', () => { test('With4', () => {
const configOptions = new ConfigOptions('.'); const configOptions = new ConfigOptions(Uri.empty());
configOptions.defaultPythonVersion = PythonVersion.V3_8; configOptions.defaultPythonVersion = PythonVersion.V3_8;
const analysisResults1 = TestUtils.typeAnalyzeSampleFiles(['with4.py'], configOptions); const analysisResults1 = TestUtils.typeAnalyzeSampleFiles(['with4.py'], configOptions);
@ -216,7 +217,7 @@ test('Mro4', () => {
}); });
test('DefaultInitializer1', () => { test('DefaultInitializer1', () => {
const configOptions = new ConfigOptions('.'); const configOptions = new ConfigOptions(Uri.empty());
// By default, the reportCallInDefaultInitializer is disabled. // By default, the reportCallInDefaultInitializer is disabled.
let analysisResults = TestUtils.typeAnalyzeSampleFiles(['defaultInitializer1.py'], configOptions); let analysisResults = TestUtils.typeAnalyzeSampleFiles(['defaultInitializer1.py'], configOptions);
@ -229,7 +230,7 @@ test('DefaultInitializer1', () => {
}); });
test('UnnecessaryIsInstance1', () => { test('UnnecessaryIsInstance1', () => {
const configOptions = new ConfigOptions('.'); const configOptions = new ConfigOptions(Uri.empty());
let analysisResults = TestUtils.typeAnalyzeSampleFiles(['unnecessaryIsInstance1.py'], configOptions); let analysisResults = TestUtils.typeAnalyzeSampleFiles(['unnecessaryIsInstance1.py'], configOptions);
TestUtils.validateResults(analysisResults, 1); TestUtils.validateResults(analysisResults, 1);
@ -241,7 +242,7 @@ test('UnnecessaryIsInstance1', () => {
}); });
test('UnnecessaryIsSubclass1', () => { test('UnnecessaryIsSubclass1', () => {
const configOptions = new ConfigOptions('.'); const configOptions = new ConfigOptions(Uri.empty());
let analysisResults = TestUtils.typeAnalyzeSampleFiles(['unnecessaryIsSubclass1.py'], configOptions); let analysisResults = TestUtils.typeAnalyzeSampleFiles(['unnecessaryIsSubclass1.py'], configOptions);
TestUtils.validateResults(analysisResults, 0); TestUtils.validateResults(analysisResults, 0);
@ -253,7 +254,7 @@ test('UnnecessaryIsSubclass1', () => {
}); });
test('UnnecessaryCast1', () => { test('UnnecessaryCast1', () => {
const configOptions = new ConfigOptions('.'); const configOptions = new ConfigOptions(Uri.empty());
let analysisResults = TestUtils.typeAnalyzeSampleFiles(['unnecessaryCast1.py'], configOptions); let analysisResults = TestUtils.typeAnalyzeSampleFiles(['unnecessaryCast1.py'], configOptions);
TestUtils.validateResults(analysisResults, 0); TestUtils.validateResults(analysisResults, 0);
@ -265,7 +266,7 @@ test('UnnecessaryCast1', () => {
}); });
test('UnnecessaryContains1', () => { test('UnnecessaryContains1', () => {
const configOptions = new ConfigOptions('.'); const configOptions = new ConfigOptions(Uri.empty());
let analysisResults = TestUtils.typeAnalyzeSampleFiles(['unnecessaryContains1.py'], configOptions); let analysisResults = TestUtils.typeAnalyzeSampleFiles(['unnecessaryContains1.py'], configOptions);
TestUtils.validateResults(analysisResults, 0); TestUtils.validateResults(analysisResults, 0);
@ -277,7 +278,7 @@ test('UnnecessaryContains1', () => {
}); });
test('TypeIgnore1', () => { test('TypeIgnore1', () => {
const configOptions = new ConfigOptions('.'); const configOptions = new ConfigOptions(Uri.empty());
let analysisResults = TestUtils.typeAnalyzeSampleFiles(['typeIgnore1.py'], configOptions); let analysisResults = TestUtils.typeAnalyzeSampleFiles(['typeIgnore1.py'], configOptions);
TestUtils.validateResults(analysisResults, 0); TestUtils.validateResults(analysisResults, 0);
@ -289,7 +290,7 @@ test('TypeIgnore1', () => {
}); });
test('TypeIgnore2', () => { test('TypeIgnore2', () => {
const configOptions = new ConfigOptions('.'); const configOptions = new ConfigOptions(Uri.empty());
let analysisResults = TestUtils.typeAnalyzeSampleFiles(['typeIgnore2.py'], configOptions); let analysisResults = TestUtils.typeAnalyzeSampleFiles(['typeIgnore2.py'], configOptions);
TestUtils.validateResults(analysisResults, 0); TestUtils.validateResults(analysisResults, 0);
@ -301,7 +302,7 @@ test('TypeIgnore2', () => {
}); });
test('TypeIgnore3', () => { test('TypeIgnore3', () => {
const configOptions = new ConfigOptions('.'); const configOptions = new ConfigOptions(Uri.empty());
let analysisResults = TestUtils.typeAnalyzeSampleFiles(['typeIgnore3.py'], configOptions); let analysisResults = TestUtils.typeAnalyzeSampleFiles(['typeIgnore3.py'], configOptions);
TestUtils.validateResults(analysisResults, 0); TestUtils.validateResults(analysisResults, 0);
@ -313,7 +314,7 @@ test('TypeIgnore3', () => {
}); });
test('TypeIgnore4', () => { test('TypeIgnore4', () => {
const configOptions = new ConfigOptions('.'); const configOptions = new ConfigOptions(Uri.empty());
let analysisResults = TestUtils.typeAnalyzeSampleFiles(['typeIgnore4.py'], configOptions); let analysisResults = TestUtils.typeAnalyzeSampleFiles(['typeIgnore4.py'], configOptions);
TestUtils.validateResults(analysisResults, 0); TestUtils.validateResults(analysisResults, 0);
@ -324,7 +325,7 @@ test('TypeIgnore4', () => {
}); });
test('TypeIgnore5', () => { test('TypeIgnore5', () => {
const configOptions = new ConfigOptions('.'); const configOptions = new ConfigOptions(Uri.empty());
let analysisResults = TestUtils.typeAnalyzeSampleFiles(['typeIgnore5.py'], configOptions); let analysisResults = TestUtils.typeAnalyzeSampleFiles(['typeIgnore5.py'], configOptions);
TestUtils.validateResults(analysisResults, 0); TestUtils.validateResults(analysisResults, 0);
@ -335,14 +336,14 @@ test('TypeIgnore5', () => {
}); });
test('PyrightIgnore1', () => { test('PyrightIgnore1', () => {
const configOptions = new ConfigOptions('.'); const configOptions = new ConfigOptions(Uri.empty());
const analysisResults = TestUtils.typeAnalyzeSampleFiles(['pyrightIgnore1.py'], configOptions); const analysisResults = TestUtils.typeAnalyzeSampleFiles(['pyrightIgnore1.py'], configOptions);
TestUtils.validateResults(analysisResults, 1); TestUtils.validateResults(analysisResults, 1);
}); });
test('PyrightIgnore2', () => { test('PyrightIgnore2', () => {
const configOptions = new ConfigOptions('.'); const configOptions = new ConfigOptions(Uri.empty());
let analysisResults = TestUtils.typeAnalyzeSampleFiles(['pyrightIgnore2.py'], configOptions); let analysisResults = TestUtils.typeAnalyzeSampleFiles(['pyrightIgnore2.py'], configOptions);
TestUtils.validateResults(analysisResults, 2); TestUtils.validateResults(analysisResults, 2);
@ -353,14 +354,14 @@ test('PyrightIgnore2', () => {
}); });
test('PyrightComment1', () => { test('PyrightComment1', () => {
const configOptions = new ConfigOptions('.'); const configOptions = new ConfigOptions(Uri.empty());
const analysisResults = TestUtils.typeAnalyzeSampleFiles(['pyrightComment1.py'], configOptions); const analysisResults = TestUtils.typeAnalyzeSampleFiles(['pyrightComment1.py'], configOptions);
TestUtils.validateResults(analysisResults, 9); TestUtils.validateResults(analysisResults, 9);
}); });
test('DuplicateImports1', () => { test('DuplicateImports1', () => {
const configOptions = new ConfigOptions('.'); const configOptions = new ConfigOptions(Uri.empty());
// By default, optional diagnostics are ignored. // By default, optional diagnostics are ignored.
let analysisResults = TestUtils.typeAnalyzeSampleFiles(['duplicateImports1.py'], configOptions); let analysisResults = TestUtils.typeAnalyzeSampleFiles(['duplicateImports1.py'], configOptions);
@ -373,7 +374,7 @@ test('DuplicateImports1', () => {
}); });
test('ParamNames1', () => { test('ParamNames1', () => {
const configOptions = new ConfigOptions('.'); const configOptions = new ConfigOptions(Uri.empty());
let analysisResults = TestUtils.typeAnalyzeSampleFiles(['paramNames1.py'], configOptions); let analysisResults = TestUtils.typeAnalyzeSampleFiles(['paramNames1.py'], configOptions);
TestUtils.validateResults(analysisResults, 0, 7); TestUtils.validateResults(analysisResults, 0, 7);
@ -423,7 +424,7 @@ test('DuplicateDeclaration2', () => {
}); });
test('Strings1', () => { test('Strings1', () => {
const configOptions = new ConfigOptions('.'); const configOptions = new ConfigOptions(Uri.empty());
const analysisResults1 = TestUtils.typeAnalyzeSampleFiles(['strings1.py'], configOptions); const analysisResults1 = TestUtils.typeAnalyzeSampleFiles(['strings1.py'], configOptions);
TestUtils.validateResults(analysisResults1, 0); TestUtils.validateResults(analysisResults1, 0);
@ -433,7 +434,7 @@ test('Strings1', () => {
}); });
test('UnusedExpression1', () => { test('UnusedExpression1', () => {
const configOptions = new ConfigOptions('.'); const configOptions = new ConfigOptions(Uri.empty());
// By default, this is a warning. // By default, this is a warning.
let analysisResults = TestUtils.typeAnalyzeSampleFiles(['unusedExpression1.py'], configOptions); let analysisResults = TestUtils.typeAnalyzeSampleFiles(['unusedExpression1.py'], configOptions);
@ -451,7 +452,7 @@ test('UnusedExpression1', () => {
}); });
test('UnusedImport1', () => { test('UnusedImport1', () => {
const configOptions = new ConfigOptions('.'); const configOptions = new ConfigOptions(Uri.empty());
// Enabled it // Enabled it
configOptions.diagnosticRuleSet.reportUnusedImport = 'warning'; configOptions.diagnosticRuleSet.reportUnusedImport = 'warning';
@ -470,7 +471,7 @@ test('UnusedImport1', () => {
}); });
test('UninitializedVariable1', () => { test('UninitializedVariable1', () => {
const configOptions = new ConfigOptions('.'); const configOptions = new ConfigOptions(Uri.empty());
// By default, this is off. // By default, this is off.
let analysisResults = TestUtils.typeAnalyzeSampleFiles(['uninitializedVariable1.py'], configOptions); let analysisResults = TestUtils.typeAnalyzeSampleFiles(['uninitializedVariable1.py'], configOptions);
@ -483,7 +484,7 @@ test('UninitializedVariable1', () => {
}); });
test('UninitializedVariable2', () => { test('UninitializedVariable2', () => {
const configOptions = new ConfigOptions('.'); const configOptions = new ConfigOptions(Uri.empty());
// By default, this is off. // By default, this is off.
let analysisResults = TestUtils.typeAnalyzeSampleFiles(['uninitializedVariable2.py'], configOptions); let analysisResults = TestUtils.typeAnalyzeSampleFiles(['uninitializedVariable2.py'], configOptions);
@ -502,7 +503,7 @@ test('RegionComments1', () => {
}); });
test('Deprecated1', () => { test('Deprecated1', () => {
const configOptions = new ConfigOptions('.'); const configOptions = new ConfigOptions(Uri.empty());
configOptions.defaultPythonVersion = PythonVersion.V3_8; configOptions.defaultPythonVersion = PythonVersion.V3_8;
const analysisResults1 = TestUtils.typeAnalyzeSampleFiles(['deprecated1.py'], configOptions); const analysisResults1 = TestUtils.typeAnalyzeSampleFiles(['deprecated1.py'], configOptions);
@ -548,7 +549,7 @@ test('Deprecated1', () => {
}); });
test('Deprecated2', () => { test('Deprecated2', () => {
const configOptions = new ConfigOptions('.'); const configOptions = new ConfigOptions(Uri.empty());
const analysisResults1 = TestUtils.typeAnalyzeSampleFiles(['deprecated2.py'], configOptions); const analysisResults1 = TestUtils.typeAnalyzeSampleFiles(['deprecated2.py'], configOptions);
TestUtils.validateResults(analysisResults1, 0, 0, 0, undefined, undefined, 12); TestUtils.validateResults(analysisResults1, 0, 0, 0, undefined, undefined, 12);
@ -559,7 +560,7 @@ test('Deprecated2', () => {
}); });
test('Deprecated3', () => { test('Deprecated3', () => {
const configOptions = new ConfigOptions('.'); const configOptions = new ConfigOptions(Uri.empty());
const analysisResults1 = TestUtils.typeAnalyzeSampleFiles(['deprecated3.py'], configOptions); const analysisResults1 = TestUtils.typeAnalyzeSampleFiles(['deprecated3.py'], configOptions);
TestUtils.validateResults(analysisResults1, 0, 0, 0, undefined, undefined, 5); TestUtils.validateResults(analysisResults1, 0, 0, 0, undefined, undefined, 5);
@ -570,7 +571,7 @@ test('Deprecated3', () => {
}); });
test('Deprecated4', () => { test('Deprecated4', () => {
const configOptions = new ConfigOptions('.'); const configOptions = new ConfigOptions(Uri.empty());
const analysisResults1 = TestUtils.typeAnalyzeSampleFiles(['deprecated4.py'], configOptions); const analysisResults1 = TestUtils.typeAnalyzeSampleFiles(['deprecated4.py'], configOptions);
TestUtils.validateResults(analysisResults1, 0, 0, 0, undefined, undefined, 6); TestUtils.validateResults(analysisResults1, 0, 0, 0, undefined, undefined, 6);
@ -581,7 +582,7 @@ test('Deprecated4', () => {
}); });
test('Deprecated5', () => { test('Deprecated5', () => {
const configOptions = new ConfigOptions('.'); const configOptions = new ConfigOptions(Uri.empty());
const analysisResults1 = TestUtils.typeAnalyzeSampleFiles(['deprecated5.py'], configOptions); const analysisResults1 = TestUtils.typeAnalyzeSampleFiles(['deprecated5.py'], configOptions);
TestUtils.validateResults(analysisResults1, 0, 0, 0, undefined, undefined, 2); 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 { CancellationToken } from 'vscode-languageserver';
import { CompletionItemKind, MarkupKind } from 'vscode-languageserver-types'; import { CompletionItemKind, MarkupKind } from 'vscode-languageserver-types';
import { Uri } from '../common/uri/uri';
import { CompletionOptions, CompletionProvider } from '../languageService/completionProvider'; import { CompletionOptions, CompletionProvider } from '../languageService/completionProvider';
import { parseAndGetTestState } from './harness/fourslash/testState'; import { parseAndGetTestState } from './harness/fourslash/testState';
@ -798,6 +799,7 @@ test('completion quote trigger', async () => {
const state = parseAndGetTestState(code).state; const state = parseAndGetTestState(code).state;
const marker = state.getMarkerByName('marker'); const marker = state.getMarkerByName('marker');
const filePath = marker.fileName; const filePath = marker.fileName;
const uri = Uri.file(filePath);
const position = state.convertOffsetToPosition(filePath, marker.position); const position = state.convertOffsetToPosition(filePath, marker.position);
const options: CompletionOptions = { const options: CompletionOptions = {
@ -809,8 +811,8 @@ test('completion quote trigger', async () => {
const result = new CompletionProvider( const result = new CompletionProvider(
state.program, state.program,
state.workspace.rootPath, state.workspace.rootUri,
filePath, uri,
position, position,
options, options,
CancellationToken.None CancellationToken.None
@ -836,6 +838,7 @@ test('completion quote trigger - middle', async () => {
const state = parseAndGetTestState(code).state; const state = parseAndGetTestState(code).state;
const marker = state.getMarkerByName('marker'); const marker = state.getMarkerByName('marker');
const filePath = marker.fileName; const filePath = marker.fileName;
const uri = Uri.file(filePath);
const position = state.convertOffsetToPosition(filePath, marker.position); const position = state.convertOffsetToPosition(filePath, marker.position);
const options: CompletionOptions = { const options: CompletionOptions = {
@ -847,8 +850,8 @@ test('completion quote trigger - middle', async () => {
const result = new CompletionProvider( const result = new CompletionProvider(
state.program, state.program,
state.workspace.rootPath, state.workspace.rootUri,
filePath, uri,
position, position,
options, options,
CancellationToken.None CancellationToken.None
@ -882,6 +885,7 @@ test('auto import sort text', async () => {
while (state.workspace.service.test_program.analyze()); while (state.workspace.service.test_program.analyze());
const filePath = marker.fileName; const filePath = marker.fileName;
const uri = Uri.file(filePath);
const position = state.convertOffsetToPosition(filePath, marker.position); const position = state.convertOffsetToPosition(filePath, marker.position);
const options: CompletionOptions = { const options: CompletionOptions = {
@ -892,8 +896,8 @@ test('auto import sort text', async () => {
const result = new CompletionProvider( const result = new CompletionProvider(
state.program, state.program,
state.workspace.rootPath, state.workspace.rootUri,
filePath, uri,
position, position,
options, options,
CancellationToken.None CancellationToken.None

View File

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

View File

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

View File

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

View File

@ -9,8 +9,9 @@
import assert from 'assert'; import assert from 'assert';
import { combinePaths, getBaseFileName, normalizeSlashes } from '../common/pathUtils'; import { getBaseFileName, normalizeSlashes } from '../common/pathUtils';
import { compareStringsCaseSensitive } from '../common/stringUtils'; import { compareStringsCaseSensitive } from '../common/stringUtils';
import { Uri } from '../common/uri/uri';
import { parseTestData } from './harness/fourslash/fourSlashParser'; import { parseTestData } from './harness/fourslash/fourSlashParser';
import { CompilerSettings } from './harness/fourslash/fourSlashTypes'; import { CompilerSettings } from './harness/fourslash/fourSlashTypes';
import * as host from './harness/testHost'; import * as host from './harness/testHost';
@ -89,7 +90,7 @@ test('Library options', () => {
const data = parseTestData('.', code, 'test.py'); 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', () => { 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('./src/A.py'))[0].content, getContent('A'));
assert.equal( assert.equal(
data.files.filter((f) => f.fileName === normalizeSlashes(combinePaths(factory.libFolder, 'src/B.py')))[0] data.files.filter((f) => f.fileName === factory.libFolder.combinePaths('src/B.py').getFilePath())[0].content,
.content,
getContent('B') getContent('B')
); );
assert.equal(data.files.filter((f) => f.fileName === normalizeSlashes('./src/C.py'))[0].content, getContent('C')); 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) { 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'; } from '../../../common/pathUtils';
import { distlibFolder, libFolder } from '../vfs/factory'; import { distlibFolder, libFolder } from '../vfs/factory';
import { import {
fileMetadataNames,
FourSlashData, FourSlashData,
FourSlashFile, FourSlashFile,
GlobalMetadataOptionNames, GlobalMetadataOptionNames,
Marker, Marker,
MetadataOptionNames, MetadataOptionNames,
Range, Range,
fileMetadataNames,
} from './fourSlashTypes'; } from './fourSlashTypes';
/** /**
@ -70,13 +70,13 @@ export function parseTestData(basePath: string, contents: string, fileName: stri
if (toBoolean(currentFileOptions[MetadataOptionNames.library])) { if (toBoolean(currentFileOptions[MetadataOptionNames.library])) {
currentFileName = normalizePath( currentFileName = normalizePath(
combinePaths(libFolder, getRelativePath(currentFileName, normalizedBasePath)) combinePaths(libFolder.getFilePath(), getRelativePath(currentFileName, normalizedBasePath))
); );
} }
if (toBoolean(currentFileOptions[MetadataOptionNames.distLibrary])) { if (toBoolean(currentFileOptions[MetadataOptionNames.distLibrary])) {
currentFileName = normalizePath( 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