mirror of
https://github.com/microsoft/pyright.git
synced 2024-10-03 19:37:39 +03:00
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:
parent
07568b047d
commit
a5b352d929
4
.vscode/settings.json
vendored
4
.vscode/settings.json
vendored
@ -8,8 +8,8 @@
|
||||
"editor.formatOnSave": true
|
||||
},
|
||||
"editor.codeActionsOnSave": {
|
||||
"source.fixAll.eslint": true,
|
||||
"source.organizeImports": true
|
||||
"source.fixAll.eslint": "explicit",
|
||||
"source.organizeImports": "explicit"
|
||||
},
|
||||
"typescript.tsdk": "node_modules/typescript/lib",
|
||||
}
|
||||
|
@ -16,7 +16,8 @@
|
||||
"check:eslint": "eslint .",
|
||||
"fix:eslint": "eslint --fix .",
|
||||
"check:prettier": "prettier -c .",
|
||||
"fix:prettier": "prettier --write ."
|
||||
"fix:prettier": "prettier --write .",
|
||||
"typecheck": "npx lerna exec --stream --no-bail --ignore=pyright -- tsc --noEmit"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/glob": "^7.2.0",
|
||||
|
14
packages/pyright-internal/package-lock.json
generated
14
packages/pyright-internal/package-lock.json
generated
@ -23,7 +23,7 @@
|
||||
"vscode-languageserver": "8.1.0",
|
||||
"vscode-languageserver-textdocument": "1.0.10",
|
||||
"vscode-languageserver-types": "3.17.3",
|
||||
"vscode-uri": "^3.0.7"
|
||||
"vscode-uri": "^3.0.8"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/command-line-args": "^5.2.0",
|
||||
@ -3887,9 +3887,9 @@
|
||||
"integrity": "sha512-SYU4z1dL0PyIMd4Vj8YOqFvHu7Hz/enbWtpfnVbJHU4Nd1YNYx8u0ennumc6h48GQNeOLxmwySmnADouT/AuZA=="
|
||||
},
|
||||
"node_modules/vscode-uri": {
|
||||
"version": "3.0.7",
|
||||
"resolved": "https://registry.npmjs.org/vscode-uri/-/vscode-uri-3.0.7.tgz",
|
||||
"integrity": "sha512-eOpPHogvorZRobNqJGhapa0JdwaxpjVvyBp0QIUMRMSf8ZAlqOdEquKuRmw9Qwu0qXtJIWqFtMkmvJjUZmMjVA=="
|
||||
"version": "3.0.8",
|
||||
"resolved": "https://registry.npmjs.org/vscode-uri/-/vscode-uri-3.0.8.tgz",
|
||||
"integrity": "sha512-AyFQ0EVmsOZOlAnxoFOGOq1SQDWAB7C6aqMGS23svWAllfOaxbuFvcT8D1i8z3Gyn8fraVeZNNmN6e9bxxXkKw=="
|
||||
},
|
||||
"node_modules/walker": {
|
||||
"version": "1.0.8",
|
||||
@ -6943,9 +6943,9 @@
|
||||
"integrity": "sha512-SYU4z1dL0PyIMd4Vj8YOqFvHu7Hz/enbWtpfnVbJHU4Nd1YNYx8u0ennumc6h48GQNeOLxmwySmnADouT/AuZA=="
|
||||
},
|
||||
"vscode-uri": {
|
||||
"version": "3.0.7",
|
||||
"resolved": "https://registry.npmjs.org/vscode-uri/-/vscode-uri-3.0.7.tgz",
|
||||
"integrity": "sha512-eOpPHogvorZRobNqJGhapa0JdwaxpjVvyBp0QIUMRMSf8ZAlqOdEquKuRmw9Qwu0qXtJIWqFtMkmvJjUZmMjVA=="
|
||||
"version": "3.0.8",
|
||||
"resolved": "https://registry.npmjs.org/vscode-uri/-/vscode-uri-3.0.8.tgz",
|
||||
"integrity": "sha512-AyFQ0EVmsOZOlAnxoFOGOq1SQDWAB7C6aqMGS23svWAllfOaxbuFvcT8D1i8z3Gyn8fraVeZNNmN6e9bxxXkKw=="
|
||||
},
|
||||
"walker": {
|
||||
"version": "1.0.8",
|
||||
|
@ -30,7 +30,7 @@
|
||||
"vscode-languageserver": "8.1.0",
|
||||
"vscode-languageserver-textdocument": "1.0.10",
|
||||
"vscode-languageserver-types": "3.17.3",
|
||||
"vscode-uri": "^3.0.7"
|
||||
"vscode-uri": "^3.0.8"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/command-line-args": "^5.2.0",
|
||||
|
@ -13,13 +13,14 @@ import { TextRangeDiagnosticSink } from '../common/diagnosticSink';
|
||||
import { PythonVersion } from '../common/pythonVersion';
|
||||
import { TextRange } from '../common/textRange';
|
||||
import { TextRangeCollection } from '../common/textRangeCollection';
|
||||
import { Uri } from '../common/uri/uri';
|
||||
import { Scope } from './scope';
|
||||
import { IPythonMode } from './sourceFile';
|
||||
import { SymbolTable } from './symbol';
|
||||
|
||||
// Maps import paths to the symbol table for the imported module.
|
||||
export interface AbsoluteModuleDescriptor {
|
||||
importingFilePath: string;
|
||||
importingFileUri: Uri;
|
||||
nameParts: string[];
|
||||
}
|
||||
|
||||
@ -29,7 +30,7 @@ export interface LookupImportOptions {
|
||||
}
|
||||
|
||||
export type ImportLookup = (
|
||||
filePathOrModule: string | AbsoluteModuleDescriptor,
|
||||
fileUriOrModule: Uri | AbsoluteModuleDescriptor,
|
||||
options?: LookupImportOptions
|
||||
) => ImportLookupResult | undefined;
|
||||
|
||||
@ -52,7 +53,7 @@ export interface AnalyzerFileInfo {
|
||||
lines: TextRangeCollection<TextRange>;
|
||||
typingSymbolAliases: Map<string, string>;
|
||||
definedConstants: Map<string, boolean | string>;
|
||||
filePath: string;
|
||||
fileUri: Uri;
|
||||
moduleName: string;
|
||||
isStubFile: boolean;
|
||||
isTypingStubFile: boolean;
|
||||
|
@ -13,12 +13,13 @@ import { BackgroundAnalysisBase } from '../backgroundAnalysisBase';
|
||||
import { ConfigOptions, ExecutionEnvironment } from '../common/configOptions';
|
||||
import { Diagnostic } from '../common/diagnostic';
|
||||
import { FileDiagnostics } from '../common/diagnosticSink';
|
||||
import { ServiceProvider } from '../common/serviceProvider';
|
||||
import '../common/serviceProviderExtensions';
|
||||
import { Range } from '../common/textRange';
|
||||
import { Uri } from '../common/uri/uri';
|
||||
import { AnalysisCompleteCallback, analyzeProgram } from './analysis';
|
||||
import { ImportResolver } from './importResolver';
|
||||
import { MaxAnalysisTime, OpenFileOptions, Program } from './program';
|
||||
import { ServiceProvider } from '../common/serviceProvider';
|
||||
import '../common/serviceProviderExtensions';
|
||||
|
||||
export enum InvalidatedReason {
|
||||
Reanalyzed,
|
||||
@ -72,8 +73,8 @@ export class BackgroundAnalysisProgram {
|
||||
return this._backgroundAnalysis;
|
||||
}
|
||||
|
||||
hasSourceFile(filePath: string): boolean {
|
||||
return !!this._program.getSourceFile(filePath);
|
||||
hasSourceFile(fileUri: Uri): boolean {
|
||||
return !!this._program.getSourceFile(fileUri);
|
||||
}
|
||||
|
||||
setConfigOptions(configOptions: ConfigOptions) {
|
||||
@ -90,9 +91,9 @@ export class BackgroundAnalysisProgram {
|
||||
this.configOptions.getExecutionEnvironments().forEach((e) => this._ensurePartialStubPackages(e));
|
||||
}
|
||||
|
||||
setTrackedFiles(filePaths: string[]) {
|
||||
this._backgroundAnalysis?.setTrackedFiles(filePaths);
|
||||
const diagnostics = this._program.setTrackedFiles(filePaths);
|
||||
setTrackedFiles(fileUris: Uri[]) {
|
||||
this._backgroundAnalysis?.setTrackedFiles(fileUris);
|
||||
const diagnostics = this._program.setTrackedFiles(fileUris);
|
||||
this._reportDiagnosticsForRemovedFiles(diagnostics);
|
||||
}
|
||||
|
||||
@ -101,35 +102,35 @@ export class BackgroundAnalysisProgram {
|
||||
this._program.setAllowedThirdPartyImports(importNames);
|
||||
}
|
||||
|
||||
setFileOpened(filePath: string, version: number | null, contents: string, options: OpenFileOptions) {
|
||||
this._backgroundAnalysis?.setFileOpened(filePath, version, contents, options);
|
||||
this._program.setFileOpened(filePath, version, contents, options);
|
||||
setFileOpened(fileUri: Uri, version: number | null, contents: string, options: OpenFileOptions) {
|
||||
this._backgroundAnalysis?.setFileOpened(fileUri, version, contents, options);
|
||||
this._program.setFileOpened(fileUri, version, contents, options);
|
||||
}
|
||||
|
||||
getChainedFilePath(filePath: string): string | undefined {
|
||||
return this._program.getChainedFilePath(filePath);
|
||||
getChainedUri(fileUri: Uri): Uri | undefined {
|
||||
return this._program.getChainedUri(fileUri);
|
||||
}
|
||||
|
||||
updateChainedFilePath(filePath: string, chainedFilePath: string | undefined) {
|
||||
this._backgroundAnalysis?.updateChainedFilePath(filePath, chainedFilePath);
|
||||
this._program.updateChainedFilePath(filePath, chainedFilePath);
|
||||
updateChainedUri(fileUri: Uri, chainedUri: Uri | undefined) {
|
||||
this._backgroundAnalysis?.updateChainedUri(fileUri, chainedUri);
|
||||
this._program.updateChainedUri(fileUri, chainedUri);
|
||||
}
|
||||
|
||||
updateOpenFileContents(path: string, version: number | null, contents: string, options: OpenFileOptions) {
|
||||
this._backgroundAnalysis?.setFileOpened(path, version, contents, options);
|
||||
this._program.setFileOpened(path, version, contents, options);
|
||||
this.markFilesDirty([path], /* evenIfContentsAreSame */ true);
|
||||
updateOpenFileContents(uri: Uri, version: number | null, contents: string, options: OpenFileOptions) {
|
||||
this._backgroundAnalysis?.setFileOpened(uri, version, contents, options);
|
||||
this._program.setFileOpened(uri, version, contents, options);
|
||||
this.markFilesDirty([uri], /* evenIfContentsAreSame */ true);
|
||||
}
|
||||
|
||||
setFileClosed(filePath: string, isTracked?: boolean) {
|
||||
this._backgroundAnalysis?.setFileClosed(filePath, isTracked);
|
||||
const diagnostics = this._program.setFileClosed(filePath, isTracked);
|
||||
setFileClosed(fileUri: Uri, isTracked?: boolean) {
|
||||
this._backgroundAnalysis?.setFileClosed(fileUri, isTracked);
|
||||
const diagnostics = this._program.setFileClosed(fileUri, isTracked);
|
||||
this._reportDiagnosticsForRemovedFiles(diagnostics);
|
||||
}
|
||||
|
||||
addInterimFile(filePath: string) {
|
||||
this._backgroundAnalysis?.addInterimFile(filePath);
|
||||
this._program.addInterimFile(filePath);
|
||||
addInterimFile(fileUri: Uri) {
|
||||
this._backgroundAnalysis?.addInterimFile(fileUri);
|
||||
this._program.addInterimFile(fileUri);
|
||||
}
|
||||
|
||||
markAllFilesDirty(evenIfContentsAreSame: boolean) {
|
||||
@ -137,9 +138,9 @@ export class BackgroundAnalysisProgram {
|
||||
this._program.markAllFilesDirty(evenIfContentsAreSame);
|
||||
}
|
||||
|
||||
markFilesDirty(filePaths: string[], evenIfContentsAreSame: boolean) {
|
||||
this._backgroundAnalysis?.markFilesDirty(filePaths, evenIfContentsAreSame);
|
||||
this._program.markFilesDirty(filePaths, evenIfContentsAreSame);
|
||||
markFilesDirty(fileUris: Uri[], evenIfContentsAreSame: boolean) {
|
||||
this._backgroundAnalysis?.markFilesDirty(fileUris, evenIfContentsAreSame);
|
||||
this._program.markFilesDirty(fileUris, evenIfContentsAreSame);
|
||||
}
|
||||
|
||||
setCompletionCallback(callback?: AnalysisCompleteCallback) {
|
||||
@ -163,34 +164,34 @@ export class BackgroundAnalysisProgram {
|
||||
);
|
||||
}
|
||||
|
||||
async analyzeFile(filePath: string, token: CancellationToken): Promise<boolean> {
|
||||
async analyzeFile(fileUri: Uri, token: CancellationToken): Promise<boolean> {
|
||||
if (this._backgroundAnalysis) {
|
||||
return this._backgroundAnalysis.analyzeFile(filePath, token);
|
||||
return this._backgroundAnalysis.analyzeFile(fileUri, token);
|
||||
}
|
||||
|
||||
return this._program.analyzeFile(filePath, token);
|
||||
return this._program.analyzeFile(fileUri, token);
|
||||
}
|
||||
|
||||
libraryUpdated() {
|
||||
// empty
|
||||
}
|
||||
|
||||
async getDiagnosticsForRange(filePath: string, range: Range, token: CancellationToken): Promise<Diagnostic[]> {
|
||||
async getDiagnosticsForRange(fileUri: Uri, range: Range, token: CancellationToken): Promise<Diagnostic[]> {
|
||||
if (this._backgroundAnalysis) {
|
||||
return this._backgroundAnalysis.getDiagnosticsForRange(filePath, range, token);
|
||||
return this._backgroundAnalysis.getDiagnosticsForRange(fileUri, range, token);
|
||||
}
|
||||
|
||||
return this._program.getDiagnosticsForRange(filePath, range);
|
||||
return this._program.getDiagnosticsForRange(fileUri, range);
|
||||
}
|
||||
|
||||
async writeTypeStub(
|
||||
targetImportPath: string,
|
||||
targetImportUri: Uri,
|
||||
targetIsSingleFile: boolean,
|
||||
stubPath: string,
|
||||
stubUri: Uri,
|
||||
token: CancellationToken
|
||||
): Promise<any> {
|
||||
if (this._backgroundAnalysis) {
|
||||
return this._backgroundAnalysis.writeTypeStub(targetImportPath, targetIsSingleFile, stubPath, token);
|
||||
return this._backgroundAnalysis.writeTypeStub(targetImportUri, targetIsSingleFile, stubUri, token);
|
||||
}
|
||||
|
||||
analyzeProgram(
|
||||
@ -201,7 +202,7 @@ export class BackgroundAnalysisProgram {
|
||||
this._serviceProvider.console(),
|
||||
token
|
||||
);
|
||||
return this._program.writeTypeStub(targetImportPath, targetIsSingleFile, stubPath, token);
|
||||
return this._program.writeTypeStub(targetImportUri, targetIsSingleFile, stubUri, token);
|
||||
}
|
||||
|
||||
invalidateAndForceReanalysis(reason: InvalidatedReason) {
|
||||
@ -245,7 +246,7 @@ export class BackgroundAnalysisProgram {
|
||||
}
|
||||
|
||||
private _ensurePartialStubPackages(execEnv: ExecutionEnvironment) {
|
||||
this._backgroundAnalysis?.ensurePartialStubPackages(execEnv.root);
|
||||
this._backgroundAnalysis?.ensurePartialStubPackages(execEnv.root?.toString());
|
||||
return this._importResolver.ensurePartialStubPackages(execEnv);
|
||||
}
|
||||
|
||||
|
@ -22,9 +22,10 @@ import { DiagnosticLevel } from '../common/configOptions';
|
||||
import { assert, assertNever, fail } from '../common/debug';
|
||||
import { CreateTypeStubFileAction, Diagnostic } from '../common/diagnostic';
|
||||
import { DiagnosticRule } from '../common/diagnosticRules';
|
||||
import { getFileName, stripFileExtension } from '../common/pathUtils';
|
||||
import { stripFileExtension } from '../common/pathUtils';
|
||||
import { convertTextRangeToRange } from '../common/positionUtils';
|
||||
import { TextRange, getEmptyRange } from '../common/textRange';
|
||||
import { Uri } from '../common/uri/uri';
|
||||
import { Localizer } from '../localization/localize';
|
||||
import {
|
||||
ArgumentCategory,
|
||||
@ -407,7 +408,7 @@ export class Binder extends ParseTreeWalker {
|
||||
const classDeclaration: ClassDeclaration = {
|
||||
type: DeclarationType.Class,
|
||||
node,
|
||||
path: this._fileInfo.filePath,
|
||||
uri: this._fileInfo.fileUri,
|
||||
range: convertTextRangeToRange(node.name, this._fileInfo.lines),
|
||||
moduleName: this._fileInfo.moduleName,
|
||||
isInExceptSuite: this._isInExceptSuite,
|
||||
@ -465,7 +466,7 @@ export class Binder extends ParseTreeWalker {
|
||||
node,
|
||||
isMethod: !!containingClassNode,
|
||||
isGenerator: false,
|
||||
path: this._fileInfo.filePath,
|
||||
uri: this._fileInfo.fileUri,
|
||||
range: convertTextRangeToRange(node.name, this._fileInfo.lines),
|
||||
moduleName: this._fileInfo.moduleName,
|
||||
isInExceptSuite: this._isInExceptSuite,
|
||||
@ -539,7 +540,7 @@ export class Binder extends ParseTreeWalker {
|
||||
const paramDeclaration: ParameterDeclaration = {
|
||||
type: DeclarationType.Parameter,
|
||||
node: paramNode,
|
||||
path: this._fileInfo.filePath,
|
||||
uri: this._fileInfo.fileUri,
|
||||
range: convertTextRangeToRange(paramNode, this._fileInfo.lines),
|
||||
moduleName: this._fileInfo.moduleName,
|
||||
isInExceptSuite: this._isInExceptSuite,
|
||||
@ -613,7 +614,7 @@ export class Binder extends ParseTreeWalker {
|
||||
const paramDeclaration: ParameterDeclaration = {
|
||||
type: DeclarationType.Parameter,
|
||||
node: paramNode,
|
||||
path: this._fileInfo.filePath,
|
||||
uri: this._fileInfo.fileUri,
|
||||
range: convertTextRangeToRange(paramNode, this._fileInfo.lines),
|
||||
moduleName: this._fileInfo.moduleName,
|
||||
isInExceptSuite: this._isInExceptSuite,
|
||||
@ -765,7 +766,7 @@ export class Binder extends ParseTreeWalker {
|
||||
const paramDeclaration: TypeParameterDeclaration = {
|
||||
type: DeclarationType.TypeParameter,
|
||||
node: param,
|
||||
path: this._fileInfo.filePath,
|
||||
uri: this._fileInfo.fileUri,
|
||||
range: convertTextRangeToRange(node, this._fileInfo.lines),
|
||||
moduleName: this._fileInfo.moduleName,
|
||||
isInExceptSuite: this._isInExceptSuite,
|
||||
@ -806,7 +807,7 @@ export class Binder extends ParseTreeWalker {
|
||||
const typeAliasDeclaration: TypeAliasDeclaration = {
|
||||
type: DeclarationType.TypeAlias,
|
||||
node,
|
||||
path: this._fileInfo.filePath,
|
||||
uri: this._fileInfo.fileUri,
|
||||
range: convertTextRangeToRange(node.name, this._fileInfo.lines),
|
||||
moduleName: this._fileInfo.moduleName,
|
||||
isInExceptSuite: this._isInExceptSuite,
|
||||
@ -1414,7 +1415,7 @@ export class Binder extends ParseTreeWalker {
|
||||
node: node.name,
|
||||
isConstant: isConstantName(node.name.value),
|
||||
inferredTypeSource: node,
|
||||
path: this._fileInfo.filePath,
|
||||
uri: this._fileInfo.fileUri,
|
||||
range: convertTextRangeToRange(node.name, this._fileInfo.lines),
|
||||
moduleName: this._fileInfo.moduleName,
|
||||
isInExceptSuite: this._isInExceptSuite,
|
||||
@ -1745,9 +1746,9 @@ export class Binder extends ParseTreeWalker {
|
||||
|
||||
AnalyzerNodeInfo.setFlowNode(node, this._currentFlowNode!);
|
||||
|
||||
let resolvedPath = '';
|
||||
let resolvedPath = Uri.empty();
|
||||
if (importInfo && importInfo.isImportFound && !importInfo.isNativeLib) {
|
||||
resolvedPath = importInfo.resolvedPaths[importInfo.resolvedPaths.length - 1];
|
||||
resolvedPath = importInfo.resolvedUris[importInfo.resolvedUris.length - 1];
|
||||
}
|
||||
|
||||
// If this file is a module __init__.py(i), relative imports of submodules
|
||||
@ -1756,7 +1757,7 @@ export class Binder extends ParseTreeWalker {
|
||||
// symbols below) in case one of the imported symbols is the same name as the
|
||||
// submodule. In that case, we want to the symbol to appear later in the
|
||||
// declaration list because it should "win" when resolving the alias.
|
||||
const fileName = stripFileExtension(getFileName(this._fileInfo.filePath));
|
||||
const fileName = stripFileExtension(this._fileInfo.fileUri.fileName);
|
||||
const isModuleInitFile =
|
||||
fileName === '__init__' && node.module.leadingDots === 1 && node.module.nameParts.length === 1;
|
||||
|
||||
@ -1814,7 +1815,7 @@ export class Binder extends ParseTreeWalker {
|
||||
const aliasDecl: AliasDeclaration = {
|
||||
type: DeclarationType.Alias,
|
||||
node,
|
||||
path: resolvedPath,
|
||||
uri: resolvedPath,
|
||||
loadSymbolsFromPath: true,
|
||||
range: getEmptyRange(), // Range is unknown for wildcard name import.
|
||||
usesLocalName: false,
|
||||
@ -1834,7 +1835,7 @@ export class Binder extends ParseTreeWalker {
|
||||
const submoduleFallback: AliasDeclaration = {
|
||||
type: DeclarationType.Alias,
|
||||
node,
|
||||
path: implicitImport.path,
|
||||
uri: implicitImport.uri,
|
||||
loadSymbolsFromPath: true,
|
||||
range: getEmptyRange(),
|
||||
usesLocalName: false,
|
||||
@ -1845,7 +1846,7 @@ export class Binder extends ParseTreeWalker {
|
||||
const aliasDecl: AliasDeclaration = {
|
||||
type: DeclarationType.Alias,
|
||||
node,
|
||||
path: resolvedPath,
|
||||
uri: resolvedPath,
|
||||
loadSymbolsFromPath: true,
|
||||
usesLocalName: false,
|
||||
symbolName: name,
|
||||
@ -1926,7 +1927,7 @@ export class Binder extends ParseTreeWalker {
|
||||
submoduleFallback = {
|
||||
type: DeclarationType.Alias,
|
||||
node: importSymbolNode,
|
||||
path: implicitImport.path,
|
||||
uri: implicitImport.uri,
|
||||
loadSymbolsFromPath: true,
|
||||
range: getEmptyRange(),
|
||||
usesLocalName: false,
|
||||
@ -1942,7 +1943,7 @@ export class Binder extends ParseTreeWalker {
|
||||
if (fileName === '__init__') {
|
||||
if (node.module.leadingDots === 1 && node.module.nameParts.length === 0) {
|
||||
loadSymbolsFromPath = false;
|
||||
} else if (resolvedPath === this._fileInfo.filePath) {
|
||||
} else if (resolvedPath.equals(this._fileInfo.fileUri)) {
|
||||
loadSymbolsFromPath = false;
|
||||
}
|
||||
}
|
||||
@ -1951,7 +1952,7 @@ export class Binder extends ParseTreeWalker {
|
||||
const aliasDecl: AliasDeclaration = {
|
||||
type: DeclarationType.Alias,
|
||||
node: importSymbolNode,
|
||||
path: resolvedPath,
|
||||
uri: resolvedPath,
|
||||
loadSymbolsFromPath,
|
||||
usesLocalName: !!importSymbolNode.alias,
|
||||
symbolName: importedName,
|
||||
@ -2304,7 +2305,7 @@ export class Binder extends ParseTreeWalker {
|
||||
node: node.target,
|
||||
isConstant: isConstantName(node.target.value),
|
||||
inferredTypeSource: node,
|
||||
path: this._fileInfo.filePath,
|
||||
uri: this._fileInfo.fileUri,
|
||||
range: convertTextRangeToRange(node.target, this._fileInfo.lines),
|
||||
moduleName: this._fileInfo.moduleName,
|
||||
isInExceptSuite: this._isInExceptSuite,
|
||||
@ -2389,7 +2390,7 @@ export class Binder extends ParseTreeWalker {
|
||||
node: slotNameNode,
|
||||
isConstant: isConstantName(slotName),
|
||||
isDefinedBySlots: true,
|
||||
path: this._fileInfo.filePath,
|
||||
uri: this._fileInfo.fileUri,
|
||||
range: convertTextRangeToRange(slotNameNode, this._fileInfo.lines),
|
||||
moduleName: this._fileInfo.moduleName,
|
||||
isInExceptSuite: this._isInExceptSuite,
|
||||
@ -2438,7 +2439,7 @@ export class Binder extends ParseTreeWalker {
|
||||
node: target,
|
||||
isConstant: isConstantName(target.value),
|
||||
inferredTypeSource: target.parent,
|
||||
path: this._fileInfo.filePath,
|
||||
uri: this._fileInfo.fileUri,
|
||||
range: convertTextRangeToRange(target, this._fileInfo.lines),
|
||||
moduleName: this._fileInfo.moduleName,
|
||||
isInExceptSuite: this._isInExceptSuite,
|
||||
@ -2466,23 +2467,25 @@ export class Binder extends ParseTreeWalker {
|
||||
const aliasDecl = varSymbol.getDeclarations().find((decl) => decl.type === DeclarationType.Alias) as
|
||||
| AliasDeclaration
|
||||
| undefined;
|
||||
const resolvedPath =
|
||||
aliasDecl?.path && aliasDecl.loadSymbolsFromPath
|
||||
? aliasDecl.path
|
||||
: aliasDecl?.submoduleFallback?.path && aliasDecl.submoduleFallback.loadSymbolsFromPath
|
||||
? aliasDecl.submoduleFallback.path
|
||||
const resolvedUri =
|
||||
aliasDecl?.uri && !aliasDecl.uri.isEmpty() && aliasDecl.loadSymbolsFromPath
|
||||
? aliasDecl.uri
|
||||
: aliasDecl?.submoduleFallback?.uri &&
|
||||
!aliasDecl.submoduleFallback.uri.isEmpty() &&
|
||||
aliasDecl.submoduleFallback.loadSymbolsFromPath
|
||||
? aliasDecl.submoduleFallback.uri
|
||||
: undefined;
|
||||
if (!resolvedPath) {
|
||||
if (!resolvedUri) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
let lookupInfo = this._fileInfo.importLookup(resolvedPath);
|
||||
let lookupInfo = this._fileInfo.importLookup(resolvedUri);
|
||||
if (lookupInfo?.dunderAllNames) {
|
||||
return lookupInfo.dunderAllNames;
|
||||
}
|
||||
|
||||
if (aliasDecl?.submoduleFallback?.path) {
|
||||
lookupInfo = this._fileInfo.importLookup(aliasDecl.submoduleFallback.path);
|
||||
if (aliasDecl?.submoduleFallback?.uri && !aliasDecl.submoduleFallback.uri.isEmpty()) {
|
||||
lookupInfo = this._fileInfo.importLookup(aliasDecl.submoduleFallback.uri);
|
||||
return lookupInfo?.dunderAllNames;
|
||||
}
|
||||
|
||||
@ -2520,15 +2523,15 @@ export class Binder extends ParseTreeWalker {
|
||||
.getDeclarations()
|
||||
.find((decl) => decl.type === DeclarationType.Alias && decl.firstNamePart === firstNamePartValue);
|
||||
let newDecl: AliasDeclaration;
|
||||
let pathOfLastSubmodule: string;
|
||||
if (importInfo && importInfo.isImportFound && !importInfo.isNativeLib && importInfo.resolvedPaths.length > 0) {
|
||||
pathOfLastSubmodule = importInfo.resolvedPaths[importInfo.resolvedPaths.length - 1];
|
||||
let uriOfLastSubmodule: Uri;
|
||||
if (importInfo && importInfo.isImportFound && !importInfo.isNativeLib && importInfo.resolvedUris.length > 0) {
|
||||
uriOfLastSubmodule = importInfo.resolvedUris[importInfo.resolvedUris.length - 1];
|
||||
} else {
|
||||
pathOfLastSubmodule = UnresolvedModuleMarker;
|
||||
uriOfLastSubmodule = UnresolvedModuleMarker;
|
||||
}
|
||||
|
||||
const isResolved =
|
||||
importInfo && importInfo.isImportFound && !importInfo.isNativeLib && importInfo.resolvedPaths.length > 0;
|
||||
importInfo && importInfo.isImportFound && !importInfo.isNativeLib && importInfo.resolvedUris.length > 0;
|
||||
|
||||
if (existingDecl) {
|
||||
newDecl = existingDecl as AliasDeclaration;
|
||||
@ -2536,7 +2539,7 @@ export class Binder extends ParseTreeWalker {
|
||||
newDecl = {
|
||||
type: DeclarationType.Alias,
|
||||
node,
|
||||
path: pathOfLastSubmodule,
|
||||
uri: uriOfLastSubmodule,
|
||||
loadSymbolsFromPath: false,
|
||||
range: getEmptyRange(),
|
||||
usesLocalName: !!importAlias,
|
||||
@ -2551,7 +2554,7 @@ export class Binder extends ParseTreeWalker {
|
||||
newDecl = {
|
||||
type: DeclarationType.Alias,
|
||||
node,
|
||||
path: pathOfLastSubmodule,
|
||||
uri: uriOfLastSubmodule,
|
||||
loadSymbolsFromPath: true,
|
||||
range: getEmptyRange(),
|
||||
usesLocalName: !!importAlias,
|
||||
@ -2565,8 +2568,8 @@ export class Binder extends ParseTreeWalker {
|
||||
// See if there is import info for this part of the path. This allows us
|
||||
// to implicitly import all of the modules in a multi-part module name.
|
||||
const implicitImportInfo = AnalyzerNodeInfo.getImportInfo(node.module.nameParts[0]);
|
||||
if (implicitImportInfo && implicitImportInfo.resolvedPaths.length) {
|
||||
newDecl.path = implicitImportInfo.resolvedPaths[0];
|
||||
if (implicitImportInfo && implicitImportInfo.resolvedUris.length) {
|
||||
newDecl.uri = implicitImportInfo.resolvedUris[0];
|
||||
newDecl.loadSymbolsFromPath = true;
|
||||
this._addImplicitImportsToLoaderActions(implicitImportInfo, newDecl);
|
||||
}
|
||||
@ -2574,7 +2577,7 @@ export class Binder extends ParseTreeWalker {
|
||||
// Add the implicit imports for this module if it's the last
|
||||
// name part we're resolving.
|
||||
if (importAlias || node.module.nameParts.length === 1) {
|
||||
newDecl.path = pathOfLastSubmodule;
|
||||
newDecl.uri = uriOfLastSubmodule;
|
||||
newDecl.loadSymbolsFromPath = true;
|
||||
newDecl.isUnresolved = false;
|
||||
|
||||
@ -2594,13 +2597,13 @@ export class Binder extends ParseTreeWalker {
|
||||
: undefined;
|
||||
if (!loaderActions) {
|
||||
const loaderActionPath =
|
||||
importInfo && i < importInfo.resolvedPaths.length
|
||||
? importInfo.resolvedPaths[i]
|
||||
importInfo && i < importInfo.resolvedUris.length
|
||||
? importInfo.resolvedUris[i]
|
||||
: UnresolvedModuleMarker;
|
||||
|
||||
// Allocate a new loader action.
|
||||
loaderActions = {
|
||||
path: loaderActionPath,
|
||||
uri: loaderActionPath,
|
||||
loadSymbolsFromPath: false,
|
||||
implicitImports: new Map<string, ModuleLoaderActions>(),
|
||||
isUnresolved: !isResolved,
|
||||
@ -2614,8 +2617,8 @@ export class Binder extends ParseTreeWalker {
|
||||
if (i === node.module.nameParts.length - 1) {
|
||||
// If this is the last name part we're resolving, add in the
|
||||
// implicit imports as well.
|
||||
if (importInfo && i < importInfo.resolvedPaths.length) {
|
||||
loaderActions.path = importInfo.resolvedPaths[i];
|
||||
if (importInfo && i < importInfo.resolvedUris.length) {
|
||||
loaderActions.uri = importInfo.resolvedUris[i];
|
||||
loaderActions.loadSymbolsFromPath = true;
|
||||
this._addImplicitImportsToLoaderActions(importInfo, loaderActions);
|
||||
}
|
||||
@ -2625,8 +2628,8 @@ export class Binder extends ParseTreeWalker {
|
||||
// import all of the modules in a multi-part module name (e.g. "import a.b.c"
|
||||
// imports "a" and "a.b" and "a.b.c").
|
||||
const implicitImportInfo = AnalyzerNodeInfo.getImportInfo(node.module.nameParts[i]);
|
||||
if (implicitImportInfo && implicitImportInfo.resolvedPaths.length) {
|
||||
loaderActions.path = implicitImportInfo.resolvedPaths[i];
|
||||
if (implicitImportInfo && implicitImportInfo.resolvedUris.length) {
|
||||
loaderActions.uri = implicitImportInfo.resolvedUris[i];
|
||||
loaderActions.loadSymbolsFromPath = true;
|
||||
this._addImplicitImportsToLoaderActions(implicitImportInfo, loaderActions);
|
||||
}
|
||||
@ -3486,7 +3489,7 @@ export class Binder extends ParseTreeWalker {
|
||||
type: DeclarationType.Intrinsic,
|
||||
node,
|
||||
intrinsicType: type,
|
||||
path: this._fileInfo.filePath,
|
||||
uri: this._fileInfo.fileUri,
|
||||
range: getEmptyRange(),
|
||||
moduleName: this._fileInfo.moduleName,
|
||||
isInExceptSuite: this._isInExceptSuite,
|
||||
@ -3561,7 +3564,7 @@ export class Binder extends ParseTreeWalker {
|
||||
inferredTypeSource: source,
|
||||
isInferenceAllowedInPyTyped: this._isInferenceAllowedInPyTyped(name.value),
|
||||
typeAliasName: isPossibleTypeAlias ? target : undefined,
|
||||
path: this._fileInfo.filePath,
|
||||
uri: this._fileInfo.fileUri,
|
||||
range: convertTextRangeToRange(name, this._fileInfo.lines),
|
||||
moduleName: this._fileInfo.moduleName,
|
||||
isInExceptSuite: this._isInExceptSuite,
|
||||
@ -3609,7 +3612,7 @@ export class Binder extends ParseTreeWalker {
|
||||
isConstant: isConstantName(name.value),
|
||||
inferredTypeSource: source,
|
||||
isDefinedByMemberAccess: true,
|
||||
path: this._fileInfo.filePath,
|
||||
uri: this._fileInfo.fileUri,
|
||||
range: convertTextRangeToRange(target.memberName, this._fileInfo.lines),
|
||||
moduleName: this._fileInfo.moduleName,
|
||||
isInExceptSuite: this._isInExceptSuite,
|
||||
@ -3701,7 +3704,7 @@ export class Binder extends ParseTreeWalker {
|
||||
isConstant: isConstantName(name.value),
|
||||
isFinal: finalInfo.isFinal,
|
||||
typeAliasName: target,
|
||||
path: this._fileInfo.filePath,
|
||||
uri: this._fileInfo.fileUri,
|
||||
typeAnnotationNode,
|
||||
range: convertTextRangeToRange(name, this._fileInfo.lines),
|
||||
moduleName: this._fileInfo.moduleName,
|
||||
@ -3774,7 +3777,7 @@ export class Binder extends ParseTreeWalker {
|
||||
isConstant: isConstantName(name.value),
|
||||
isDefinedByMemberAccess: true,
|
||||
isFinal: finalInfo.isFinal,
|
||||
path: this._fileInfo.filePath,
|
||||
uri: this._fileInfo.fileUri,
|
||||
typeAnnotationNode: finalInfo.isFinal && !finalInfo.finalTypeNode ? undefined : typeAnnotation,
|
||||
range: convertTextRangeToRange(target.memberName, this._fileInfo.lines),
|
||||
moduleName: this._fileInfo.moduleName,
|
||||
@ -4021,14 +4024,14 @@ export class Binder extends ParseTreeWalker {
|
||||
? loaderActions.implicitImports.get(implicitImport.name)
|
||||
: undefined;
|
||||
if (existingLoaderAction) {
|
||||
existingLoaderAction.path = implicitImport.path;
|
||||
existingLoaderAction.uri = implicitImport.uri;
|
||||
existingLoaderAction.loadSymbolsFromPath = true;
|
||||
} else {
|
||||
if (!loaderActions.implicitImports) {
|
||||
loaderActions.implicitImports = new Map<string, ModuleLoaderActions>();
|
||||
}
|
||||
loaderActions.implicitImports.set(implicitImport.name, {
|
||||
path: implicitImport.path,
|
||||
uri: implicitImport.uri,
|
||||
loadSymbolsFromPath: true,
|
||||
implicitImports: new Map<string, ModuleLoaderActions>(),
|
||||
});
|
||||
@ -4095,7 +4098,7 @@ export class Binder extends ParseTreeWalker {
|
||||
symbol.addDeclaration({
|
||||
type: DeclarationType.SpecialBuiltInClass,
|
||||
node: annotationNode,
|
||||
path: this._fileInfo.filePath,
|
||||
uri: this._fileInfo.fileUri,
|
||||
range: convertTextRangeToRange(annotationNode, this._fileInfo.lines),
|
||||
moduleName: this._fileInfo.moduleName,
|
||||
isInExceptSuite: this._isInExceptSuite,
|
||||
|
@ -20,9 +20,9 @@ import { DiagnosticLevel } from '../common/configOptions';
|
||||
import { assert, assertNever } from '../common/debug';
|
||||
import { ActionKind, Diagnostic, DiagnosticAddendum, RenameShadowedFileAction } from '../common/diagnostic';
|
||||
import { DiagnosticRule } from '../common/diagnosticRules';
|
||||
import { getFileExtension } from '../common/pathUtils';
|
||||
import { PythonVersion, versionToString } from '../common/pythonVersion';
|
||||
import { TextRange } from '../common/textRange';
|
||||
import { Uri } from '../common/uri/uri';
|
||||
import { DefinitionProvider } from '../languageService/definitionProvider';
|
||||
import { Localizer } from '../localization/localize';
|
||||
import {
|
||||
@ -233,7 +233,9 @@ export class Checker extends ParseTreeWalker {
|
||||
const codeComplexity = AnalyzerNodeInfo.getCodeFlowComplexity(this._moduleNode);
|
||||
|
||||
if (isPrintCodeComplexityEnabled) {
|
||||
console.log(`Code complexity of module ${this._fileInfo.filePath} is ${codeComplexity.toString()}`);
|
||||
console.log(
|
||||
`Code complexity of module ${this._fileInfo.fileUri.toUserVisibleString()} is ${codeComplexity.toString()}`
|
||||
);
|
||||
}
|
||||
|
||||
if (codeComplexity > maxCodeComplexity) {
|
||||
@ -1566,11 +1568,12 @@ export class Checker extends ParseTreeWalker {
|
||||
}
|
||||
|
||||
const resolvedAlias = this._evaluator.resolveAliasDeclaration(decl, /* resolveLocalNames */ true);
|
||||
if (!resolvedAlias?.path || !isStubFile(resolvedAlias.path)) {
|
||||
const resolvedAliasUri = resolvedAlias?.uri;
|
||||
if (!resolvedAliasUri || !isStubFile(resolvedAliasUri)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const importResult = this._getImportResult(node, resolvedAlias.path);
|
||||
const importResult = this._getImportResult(node, resolvedAliasUri);
|
||||
if (!importResult) {
|
||||
continue;
|
||||
}
|
||||
@ -1667,22 +1670,22 @@ export class Checker extends ParseTreeWalker {
|
||||
return false;
|
||||
}
|
||||
|
||||
private _getImportResult(node: ImportFromAsNode, filePath: string) {
|
||||
const execEnv = this._importResolver.getConfigOptions().findExecEnvironment(filePath);
|
||||
private _getImportResult(node: ImportFromAsNode, uri: Uri) {
|
||||
const execEnv = this._importResolver.getConfigOptions().findExecEnvironment(uri);
|
||||
const moduleNameNode = (node.parent as ImportFromNode).module;
|
||||
|
||||
// Handle both absolute and relative imports.
|
||||
const moduleName =
|
||||
moduleNameNode.leadingDots === 0
|
||||
? this._importResolver.getModuleNameForImport(filePath, execEnv).moduleName
|
||||
: getRelativeModuleName(this._importResolver.fileSystem, this._fileInfo.filePath, filePath);
|
||||
? this._importResolver.getModuleNameForImport(uri, execEnv).moduleName
|
||||
: getRelativeModuleName(this._importResolver.fileSystem, this._fileInfo.fileUri, uri);
|
||||
|
||||
if (!moduleName) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
return this._importResolver.resolveImport(
|
||||
this._fileInfo.filePath,
|
||||
this._fileInfo.fileUri,
|
||||
execEnv,
|
||||
createImportedModuleDescriptor(moduleName)
|
||||
);
|
||||
@ -3087,7 +3090,7 @@ export class Checker extends ParseTreeWalker {
|
||||
if (diagnostic && overload.details.declaration) {
|
||||
diagnostic.addRelatedInfo(
|
||||
Localizer.DiagnosticAddendum.overloadSignature(),
|
||||
overload.details.declaration?.path ?? primaryDecl.path,
|
||||
overload.details.declaration?.uri ?? primaryDecl.uri,
|
||||
overload.details.declaration?.range ?? primaryDecl.range
|
||||
);
|
||||
}
|
||||
@ -3280,7 +3283,7 @@ export class Checker extends ParseTreeWalker {
|
||||
}
|
||||
|
||||
if (primaryDeclNode) {
|
||||
diag.addRelatedInfo(primaryDeclInfo, primaryDecl.path, primaryDecl.range);
|
||||
diag.addRelatedInfo(primaryDeclInfo, primaryDecl.uri, primaryDecl.range);
|
||||
}
|
||||
}
|
||||
};
|
||||
@ -4177,7 +4180,7 @@ export class Checker extends ParseTreeWalker {
|
||||
if (
|
||||
stdlibPath &&
|
||||
this._importResolver.isStdlibModule(desc, this._fileInfo.executionEnvironment) &&
|
||||
this._sourceMapper.isUserCode(this._fileInfo.filePath)
|
||||
this._sourceMapper.isUserCode(this._fileInfo.fileUri)
|
||||
) {
|
||||
// This means the user has a module that is overwriting the stdlib module.
|
||||
const diag = this._evaluator.addDiagnosticForTextRange(
|
||||
@ -4186,7 +4189,7 @@ export class Checker extends ParseTreeWalker {
|
||||
DiagnosticRule.reportShadowedImports,
|
||||
Localizer.Diagnostic.stdlibModuleOverridden().format({
|
||||
name: moduleName,
|
||||
path: this._fileInfo.filePath,
|
||||
path: this._fileInfo.fileUri.toUserVisibleString(),
|
||||
}),
|
||||
this._moduleNode
|
||||
);
|
||||
@ -4195,8 +4198,8 @@ export class Checker extends ParseTreeWalker {
|
||||
if (diag) {
|
||||
const renameAction: RenameShadowedFileAction = {
|
||||
action: ActionKind.RenameShadowedFileAction,
|
||||
oldFile: this._fileInfo.filePath,
|
||||
newFile: this._sourceMapper.getNextFileName(this._fileInfo.filePath),
|
||||
oldUri: this._fileInfo.fileUri,
|
||||
newUri: this._sourceMapper.getNextFileName(this._fileInfo.fileUri),
|
||||
};
|
||||
diag.addAction(renameAction);
|
||||
}
|
||||
@ -4245,7 +4248,7 @@ export class Checker extends ParseTreeWalker {
|
||||
namePartNodes[namePartNodes.length - 1].start,
|
||||
CancellationToken.None
|
||||
);
|
||||
const paths = definitions ? definitions.map((d) => d.path) : [];
|
||||
const paths = definitions ? definitions.map((d) => d.uri) : [];
|
||||
paths.forEach((p) => {
|
||||
if (!p.startsWith(stdlibPath) && !isStubFile(p) && this._sourceMapper.isUserCode(p)) {
|
||||
// This means the user has a module that is overwriting the stdlib module.
|
||||
@ -4254,7 +4257,7 @@ export class Checker extends ParseTreeWalker {
|
||||
DiagnosticRule.reportShadowedImports,
|
||||
Localizer.Diagnostic.stdlibModuleOverridden().format({
|
||||
name: nameParts.join('.'),
|
||||
path: p,
|
||||
path: p.toUserVisibleString(),
|
||||
}),
|
||||
node
|
||||
);
|
||||
@ -4262,8 +4265,8 @@ export class Checker extends ParseTreeWalker {
|
||||
if (diag) {
|
||||
const renameAction: RenameShadowedFileAction = {
|
||||
action: ActionKind.RenameShadowedFileAction,
|
||||
oldFile: p,
|
||||
newFile: this._sourceMapper.getNextFileName(p),
|
||||
oldUri: p,
|
||||
newUri: this._sourceMapper.getNextFileName(p),
|
||||
};
|
||||
diag.addAction(renameAction);
|
||||
}
|
||||
@ -4755,7 +4758,7 @@ export class Checker extends ParseTreeWalker {
|
||||
decl.type !== DeclarationType.Function || ParseTreeUtils.isSuiteEmpty(decl.node.suite)
|
||||
)
|
||||
) {
|
||||
if (getFileExtension(decls[0].path).toLowerCase() !== '.pyi') {
|
||||
if (!decls[0].uri.hasExtension('.pyi')) {
|
||||
if (!isSymbolImplemented(name)) {
|
||||
diagAddendum.addMessage(
|
||||
Localizer.DiagnosticAddendum.missingProtocolMember().format({
|
||||
@ -4881,7 +4884,7 @@ export class Checker extends ParseTreeWalker {
|
||||
if (fieldDecls.length > 0) {
|
||||
diagnostic.addRelatedInfo(
|
||||
Localizer.DiagnosticAddendum.dataClassFieldLocation(),
|
||||
fieldDecls[0].path,
|
||||
fieldDecls[0].uri,
|
||||
fieldDecls[0].range
|
||||
);
|
||||
}
|
||||
@ -5103,7 +5106,16 @@ export class Checker extends ParseTreeWalker {
|
||||
const updatedClassType = ClassType.cloneWithNewTypeParameters(classType, updatedTypeParams);
|
||||
|
||||
const objectObject = ClassType.cloneAsInstance(objectType);
|
||||
const dummyTypeObject = ClassType.createInstantiable('__varianceDummy', '', '', '', 0, 0, undefined, undefined);
|
||||
const dummyTypeObject = ClassType.createInstantiable(
|
||||
'__varianceDummy',
|
||||
'',
|
||||
'',
|
||||
Uri.empty(),
|
||||
0,
|
||||
0,
|
||||
undefined,
|
||||
undefined
|
||||
);
|
||||
|
||||
updatedTypeParams.forEach((param, paramIndex) => {
|
||||
// Skip variadics and ParamSpecs.
|
||||
@ -5382,7 +5394,7 @@ export class Checker extends ParseTreeWalker {
|
||||
)
|
||||
),
|
||||
}),
|
||||
secondaryDecl.path,
|
||||
secondaryDecl.uri,
|
||||
secondaryDecl.range
|
||||
);
|
||||
}
|
||||
@ -5760,7 +5772,7 @@ export class Checker extends ParseTreeWalker {
|
||||
baseClass: this._evaluator.printType(convertToInstance(overriddenClassAndSymbol.classType)),
|
||||
type: this._evaluator.printType(overriddenType),
|
||||
}),
|
||||
overriddenDecl.path,
|
||||
overriddenDecl.uri,
|
||||
overriddenDecl.range
|
||||
);
|
||||
|
||||
@ -5769,7 +5781,7 @@ export class Checker extends ParseTreeWalker {
|
||||
baseClass: this._evaluator.printType(convertToInstance(overrideClassAndSymbol.classType)),
|
||||
type: this._evaluator.printType(overrideType),
|
||||
}),
|
||||
overrideDecl.path,
|
||||
overrideDecl.uri,
|
||||
overrideDecl.range
|
||||
);
|
||||
}
|
||||
@ -6005,7 +6017,7 @@ export class Checker extends ParseTreeWalker {
|
||||
if (diag && origDecl) {
|
||||
diag.addRelatedInfo(
|
||||
Localizer.DiagnosticAddendum.overriddenMethod(),
|
||||
origDecl.path,
|
||||
origDecl.uri,
|
||||
origDecl.range
|
||||
);
|
||||
}
|
||||
@ -6030,7 +6042,7 @@ export class Checker extends ParseTreeWalker {
|
||||
if (diag && origDecl) {
|
||||
diag.addRelatedInfo(
|
||||
Localizer.DiagnosticAddendum.finalMethod(),
|
||||
origDecl.path,
|
||||
origDecl.uri,
|
||||
origDecl.range
|
||||
);
|
||||
}
|
||||
@ -6060,7 +6072,7 @@ export class Checker extends ParseTreeWalker {
|
||||
if (diag && origDecl) {
|
||||
diag.addRelatedInfo(
|
||||
Localizer.DiagnosticAddendum.overriddenMethod(),
|
||||
origDecl.path,
|
||||
origDecl.uri,
|
||||
origDecl.range
|
||||
);
|
||||
}
|
||||
@ -6123,7 +6135,7 @@ export class Checker extends ParseTreeWalker {
|
||||
if (diag && origDecl) {
|
||||
diag.addRelatedInfo(
|
||||
Localizer.DiagnosticAddendum.overriddenMethod(),
|
||||
origDecl.path,
|
||||
origDecl.uri,
|
||||
origDecl.range
|
||||
);
|
||||
}
|
||||
@ -6160,7 +6172,7 @@ export class Checker extends ParseTreeWalker {
|
||||
if (diag && origDecl) {
|
||||
diag.addRelatedInfo(
|
||||
Localizer.DiagnosticAddendum.overriddenMethod(),
|
||||
origDecl.path,
|
||||
origDecl.uri,
|
||||
origDecl.range
|
||||
);
|
||||
}
|
||||
@ -6247,7 +6259,7 @@ export class Checker extends ParseTreeWalker {
|
||||
if (diag && origDecl) {
|
||||
diag.addRelatedInfo(
|
||||
Localizer.DiagnosticAddendum.overriddenSymbol(),
|
||||
origDecl.path,
|
||||
origDecl.uri,
|
||||
origDecl.range
|
||||
);
|
||||
}
|
||||
@ -6306,7 +6318,7 @@ export class Checker extends ParseTreeWalker {
|
||||
if (diag) {
|
||||
diag.addRelatedInfo(
|
||||
Localizer.DiagnosticAddendum.overriddenSymbol(),
|
||||
overrideFinalVarDecl.path,
|
||||
overrideFinalVarDecl.uri,
|
||||
overrideFinalVarDecl.range
|
||||
);
|
||||
}
|
||||
@ -6353,7 +6365,7 @@ export class Checker extends ParseTreeWalker {
|
||||
if (diag && origDecl) {
|
||||
diag.addRelatedInfo(
|
||||
Localizer.DiagnosticAddendum.overriddenSymbol(),
|
||||
origDecl.path,
|
||||
origDecl.uri,
|
||||
origDecl.range
|
||||
);
|
||||
}
|
||||
|
@ -10,10 +10,12 @@
|
||||
* by picking the alphabetically-first module in the cycle.
|
||||
*/
|
||||
|
||||
export class CircularDependency {
|
||||
private _paths: string[] = [];
|
||||
import { Uri } from '../common/uri/uri';
|
||||
|
||||
appendPath(path: string) {
|
||||
export class CircularDependency {
|
||||
private _paths: Uri[] = [];
|
||||
|
||||
appendPath(path: Uri) {
|
||||
this._paths.push(path);
|
||||
}
|
||||
|
||||
|
@ -867,7 +867,7 @@ function getDescriptorForConverterField(
|
||||
descriptorName,
|
||||
getClassFullName(converterNode, fileInfo.moduleName, descriptorName),
|
||||
fileInfo.moduleName,
|
||||
fileInfo.filePath,
|
||||
fileInfo.fileUri,
|
||||
ClassTypeFlags.None,
|
||||
getTypeSourceId(converterNode),
|
||||
/* declaredMetaclass */ undefined,
|
||||
|
@ -10,6 +10,7 @@
|
||||
*/
|
||||
|
||||
import { Range } from '../common/textRange';
|
||||
import { Uri } from '../common/uri/uri';
|
||||
import {
|
||||
ClassNode,
|
||||
ExpressionNode,
|
||||
@ -31,7 +32,7 @@ import {
|
||||
YieldNode,
|
||||
} from '../parser/parseNodes';
|
||||
|
||||
export const UnresolvedModuleMarker = '*** unresolved ***';
|
||||
export const UnresolvedModuleMarker = Uri.parse('unresolved-module-marker://**/*', /* isCaseSensitive */ true);
|
||||
|
||||
export const enum DeclarationType {
|
||||
Intrinsic,
|
||||
@ -57,9 +58,9 @@ export interface DeclarationBase {
|
||||
node: ParseNode;
|
||||
|
||||
// The file and range within that file that
|
||||
// contains the declaration. Unless this is an alias, then path refers to the
|
||||
// contains the declaration. Unless this is an alias, then uri refers to the
|
||||
// file the alias is referring to.
|
||||
path: string;
|
||||
uri: Uri;
|
||||
range: Range;
|
||||
|
||||
// The dot-separated import name for the file that
|
||||
@ -222,10 +223,10 @@ export interface AliasDeclaration extends DeclarationBase {
|
||||
// This interface represents a set of actions that the python loader
|
||||
// performs when a module import is encountered.
|
||||
export interface ModuleLoaderActions {
|
||||
// The resolved path of the implicit import. This can be empty
|
||||
// if the resolved path doesn't reference a module (e.g. it's
|
||||
// The resolved uri of the implicit import. This can be empty
|
||||
// if the resolved uri doesn't reference a module (e.g. it's
|
||||
// a directory).
|
||||
path: string;
|
||||
uri: Uri;
|
||||
|
||||
// Is this a dummy entry for an unresolved import?
|
||||
isUnresolved?: boolean;
|
||||
@ -285,5 +286,5 @@ export function isIntrinsicDeclaration(decl: Declaration): decl is IntrinsicDecl
|
||||
}
|
||||
|
||||
export function isUnresolvedAliasDeclaration(decl: Declaration): boolean {
|
||||
return isAliasDeclaration(decl) && decl.path === UnresolvedModuleMarker;
|
||||
return isAliasDeclaration(decl) && decl.uri === UnresolvedModuleMarker;
|
||||
}
|
||||
|
@ -8,6 +8,7 @@
|
||||
*/
|
||||
|
||||
import { getEmptyRange } from '../common/textRange';
|
||||
import { Uri } from '../common/uri/uri';
|
||||
import { NameNode, ParseNodeType } from '../parser/parseNodes';
|
||||
import { ImportLookup, ImportLookupResult } from './analyzerFileInfo';
|
||||
import { AliasDeclaration, Declaration, DeclarationType, ModuleLoaderActions, isAliasDeclaration } from './declaration';
|
||||
@ -78,7 +79,7 @@ export function areDeclarationsSame(
|
||||
return false;
|
||||
}
|
||||
|
||||
if (decl1.path !== decl2.path) {
|
||||
if (!decl1.uri.equals(decl2.uri)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@ -174,16 +175,16 @@ export function getNameNodeForDeclaration(declaration: Declaration): NameNode |
|
||||
throw new Error(`Shouldn't reach here`);
|
||||
}
|
||||
|
||||
export function isDefinedInFile(decl: Declaration, filePath: string) {
|
||||
export function isDefinedInFile(decl: Declaration, fileUri: Uri) {
|
||||
if (isAliasDeclaration(decl)) {
|
||||
// Alias decl's path points to the original symbol
|
||||
// the alias is pointing to. So, we need to get the
|
||||
// filepath in that the alias is defined from the node.
|
||||
return getFileInfoFromNode(decl.node)?.filePath === filePath;
|
||||
return getFileInfoFromNode(decl.node)?.fileUri.equals(fileUri);
|
||||
}
|
||||
|
||||
// Other decls, the path points to the file the symbol is defined in.
|
||||
return decl.path === filePath;
|
||||
return decl.uri.equals(fileUri);
|
||||
}
|
||||
|
||||
export function getDeclarationsWithUsesLocalNameRemoved(decls: Declaration[]) {
|
||||
@ -199,13 +200,13 @@ export function getDeclarationsWithUsesLocalNameRemoved(decls: Declaration[]) {
|
||||
});
|
||||
}
|
||||
|
||||
export function createSynthesizedAliasDeclaration(path: string): AliasDeclaration {
|
||||
export function createSynthesizedAliasDeclaration(uri: Uri): AliasDeclaration {
|
||||
// The only time this decl is used is for IDE services such as
|
||||
// the find all references, hover provider and etc.
|
||||
return {
|
||||
type: DeclarationType.Alias,
|
||||
node: undefined!,
|
||||
path,
|
||||
uri,
|
||||
loadSymbolsFromPath: false,
|
||||
range: getEmptyRange(),
|
||||
implicitImports: new Map<string, ModuleLoaderActions>(),
|
||||
@ -267,8 +268,10 @@ export function resolveAliasDeclaration(
|
||||
}
|
||||
|
||||
let lookupResult: ImportLookupResult | undefined;
|
||||
if (curDeclaration.path && curDeclaration.loadSymbolsFromPath) {
|
||||
lookupResult = importLookup(curDeclaration.path, { skipFileNeededCheck: options.skipFileNeededCheck });
|
||||
if (!curDeclaration.uri.isEmpty() && curDeclaration.loadSymbolsFromPath) {
|
||||
lookupResult = importLookup(curDeclaration.uri, {
|
||||
skipFileNeededCheck: options.skipFileNeededCheck,
|
||||
});
|
||||
}
|
||||
|
||||
const symbol: Symbol | undefined = lookupResult
|
||||
@ -284,11 +287,11 @@ export function resolveAliasDeclaration(
|
||||
// when useLibraryCodeForTypes is disabled), b should be evaluated as Unknown,
|
||||
// not as a module.
|
||||
if (
|
||||
curDeclaration.path &&
|
||||
!curDeclaration.uri.isEmpty() &&
|
||||
curDeclaration.submoduleFallback.type === DeclarationType.Alias &&
|
||||
curDeclaration.submoduleFallback.path
|
||||
!curDeclaration.submoduleFallback.uri.isEmpty()
|
||||
) {
|
||||
const lookupResult = importLookup(curDeclaration.submoduleFallback.path, {
|
||||
const lookupResult = importLookup(curDeclaration.submoduleFallback.uri, {
|
||||
skipFileNeededCheck: options.skipFileNeededCheck,
|
||||
skipParsing: true,
|
||||
});
|
||||
@ -393,7 +396,7 @@ export function resolveAliasDeclaration(
|
||||
// the module is foo, and the foo.__init__.py file contains the statement
|
||||
// "from foo import bar", we want to import the foo/bar.py submodule.
|
||||
if (
|
||||
curDeclaration.path === declaration.path &&
|
||||
curDeclaration.uri.equals(declaration.uri) &&
|
||||
curDeclaration.type === DeclarationType.Alias &&
|
||||
curDeclaration.submoduleFallback
|
||||
) {
|
||||
|
@ -81,7 +81,7 @@ export function createEnumType(
|
||||
className,
|
||||
getClassFullName(errorNode, fileInfo.moduleName, className),
|
||||
fileInfo.moduleName,
|
||||
fileInfo.filePath,
|
||||
fileInfo.fileUri,
|
||||
ClassTypeFlags.EnumClass,
|
||||
getTypeSourceId(errorNode),
|
||||
/* declaredMetaclass */ undefined,
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -7,6 +7,7 @@
|
||||
* Interface that describes the output of the import resolver.
|
||||
*/
|
||||
|
||||
import { Uri } from '../common/uri/uri';
|
||||
import { PyTypedInfo } from './pyTypedUtils';
|
||||
|
||||
export const enum ImportType {
|
||||
@ -19,7 +20,7 @@ export interface ImplicitImport {
|
||||
isStubFile: boolean;
|
||||
isNativeLib: boolean;
|
||||
name: string;
|
||||
path: string;
|
||||
uri: Uri;
|
||||
pyTypedInfo?: PyTypedInfo | undefined;
|
||||
}
|
||||
|
||||
@ -62,11 +63,11 @@ export interface ImportResult {
|
||||
// The resolved absolute paths for each of the files in the module name.
|
||||
// Parts that have no files (e.g. directories within a namespace
|
||||
// package) have empty strings for a resolvedPath.
|
||||
resolvedPaths: string[];
|
||||
resolvedUris: Uri[];
|
||||
|
||||
// For absolute imports, the search path that was used to resolve
|
||||
// (or partially resolve) the module.
|
||||
searchPath?: string;
|
||||
searchPath?: Uri;
|
||||
|
||||
// True if resolved file is a type hint (.pyi) file rather than
|
||||
// a python (.py) file.
|
||||
@ -103,5 +104,5 @@ export interface ImportResult {
|
||||
pyTypedInfo?: PyTypedInfo | undefined;
|
||||
|
||||
// The directory of the package, if found.
|
||||
packageDirectory?: string | undefined;
|
||||
packageDirectory?: Uri | undefined;
|
||||
}
|
||||
|
@ -14,16 +14,11 @@ import { throwIfCancellationRequested } from '../common/cancellationUtils';
|
||||
import { addIfUnique, appendArray, createMapFromItems } from '../common/collectionUtils';
|
||||
import { TextEditAction } from '../common/editAction';
|
||||
import { ReadOnlyFileSystem } from '../common/fileSystem';
|
||||
import {
|
||||
getDirectoryPath,
|
||||
getFileName,
|
||||
getRelativePathComponentsFromDirectory,
|
||||
isFile,
|
||||
stripFileExtension,
|
||||
} from '../common/pathUtils';
|
||||
import { convertOffsetToPosition, convertPositionToOffset } from '../common/positionUtils';
|
||||
import { compareStringsCaseSensitive } from '../common/stringUtils';
|
||||
import { Position, Range, TextRange } from '../common/textRange';
|
||||
import { Uri } from '../common/uri/uri';
|
||||
import { isFile } from '../common/uri/uriUtils';
|
||||
import {
|
||||
ImportAsNode,
|
||||
ImportFromAsNode,
|
||||
@ -35,18 +30,18 @@ import {
|
||||
ParseNodeType,
|
||||
} from '../parser/parseNodes';
|
||||
import { ParseResults } from '../parser/parser';
|
||||
import { TokenType } from '../parser/tokenizerTypes';
|
||||
import * as AnalyzerNodeInfo from './analyzerNodeInfo';
|
||||
import { ModuleNameAndType } from './importResolver';
|
||||
import { ImportResult, ImportType } from './importResult';
|
||||
import * as SymbolNameUtils from './symbolNameUtils';
|
||||
import { findTokenAfter, getTokenAt } from './parseTreeUtils';
|
||||
import { TokenType } from '../parser/tokenizerTypes';
|
||||
import * as SymbolNameUtils from './symbolNameUtils';
|
||||
|
||||
export interface ImportStatement {
|
||||
node: ImportNode | ImportFromNode;
|
||||
subnode?: ImportAsNode;
|
||||
importResult: ImportResult | undefined;
|
||||
resolvedPath: string | undefined;
|
||||
resolvedPath: Uri | undefined;
|
||||
moduleName: string;
|
||||
followsNonImportStatement: boolean;
|
||||
}
|
||||
@ -625,10 +620,10 @@ function _getInsertionEditForAutoImportInsertion(
|
||||
function _processImportNode(node: ImportNode, localImports: ImportStatements, followsNonImportStatement: boolean) {
|
||||
node.list.forEach((importAsNode) => {
|
||||
const importResult = AnalyzerNodeInfo.getImportInfo(importAsNode.module);
|
||||
let resolvedPath: string | undefined;
|
||||
let resolvedPath: Uri | undefined;
|
||||
|
||||
if (importResult && importResult.isImportFound) {
|
||||
resolvedPath = importResult.resolvedPaths[importResult.resolvedPaths.length - 1];
|
||||
resolvedPath = importResult.resolvedUris[importResult.resolvedUris.length - 1];
|
||||
}
|
||||
|
||||
const localImport: ImportStatement = {
|
||||
@ -647,8 +642,8 @@ function _processImportNode(node: ImportNode, localImports: ImportStatements, fo
|
||||
// Don't overwrite existing import or import from statements
|
||||
// because we always want to prefer 'import from' over 'import'
|
||||
// in the map.
|
||||
if (!localImports.mapByFilePath.has(resolvedPath)) {
|
||||
localImports.mapByFilePath.set(resolvedPath, localImport);
|
||||
if (!localImports.mapByFilePath.has(resolvedPath.key)) {
|
||||
localImports.mapByFilePath.set(resolvedPath.key, localImport);
|
||||
}
|
||||
}
|
||||
});
|
||||
@ -661,10 +656,10 @@ function _processImportFromNode(
|
||||
includeImplicitImports: boolean
|
||||
) {
|
||||
const importResult = AnalyzerNodeInfo.getImportInfo(node.module);
|
||||
let resolvedPath: string | undefined;
|
||||
let resolvedPath: Uri | undefined;
|
||||
|
||||
if (importResult && importResult.isImportFound) {
|
||||
resolvedPath = importResult.resolvedPaths[importResult.resolvedPaths.length - 1];
|
||||
resolvedPath = importResult.resolvedUris[importResult.resolvedUris.length - 1];
|
||||
}
|
||||
|
||||
if (includeImplicitImports && importResult) {
|
||||
@ -673,7 +668,7 @@ function _processImportFromNode(
|
||||
for (const implicitImport of importResult.implicitImports.values()) {
|
||||
const importFromAs = node.imports.find((i) => i.name.value === implicitImport.name);
|
||||
if (importFromAs) {
|
||||
localImports.implicitImports.set(implicitImport.path, importFromAs);
|
||||
localImports.implicitImports.set(implicitImport.uri.key, importFromAs);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -690,7 +685,7 @@ function _processImportFromNode(
|
||||
|
||||
// Add it to the map.
|
||||
if (resolvedPath) {
|
||||
const prevEntry = localImports.mapByFilePath.get(resolvedPath);
|
||||
const prevEntry = localImports.mapByFilePath.get(resolvedPath.key);
|
||||
// Overwrite existing import statements because we always want to prefer
|
||||
// 'import from' over 'import'. Also, overwrite existing 'import from' if
|
||||
// the module name is shorter.
|
||||
@ -699,7 +694,7 @@ function _processImportFromNode(
|
||||
prevEntry.node.nodeType === ParseNodeType.Import ||
|
||||
prevEntry.moduleName.length > localImport.moduleName.length
|
||||
) {
|
||||
localImports.mapByFilePath.set(resolvedPath, localImport);
|
||||
localImports.mapByFilePath.set(resolvedPath.key, localImport);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -850,23 +845,23 @@ function getConsecutiveNumberPairs(indices: number[]) {
|
||||
|
||||
export function getRelativeModuleName(
|
||||
fs: ReadOnlyFileSystem,
|
||||
sourcePath: string,
|
||||
targetPath: string,
|
||||
sourcePath: Uri,
|
||||
targetPath: Uri,
|
||||
ignoreFolderStructure = false,
|
||||
sourceIsFile?: boolean
|
||||
) {
|
||||
let srcPath = sourcePath;
|
||||
sourceIsFile = sourceIsFile !== undefined ? sourceIsFile : isFile(fs, sourcePath);
|
||||
if (sourceIsFile) {
|
||||
srcPath = getDirectoryPath(sourcePath);
|
||||
srcPath = sourcePath.getDirectory();
|
||||
}
|
||||
|
||||
let symbolName: string | undefined;
|
||||
let destPath = targetPath;
|
||||
if (sourceIsFile) {
|
||||
destPath = getDirectoryPath(targetPath);
|
||||
destPath = targetPath.getDirectory();
|
||||
|
||||
const fileName = stripFileExtension(getFileName(targetPath));
|
||||
const fileName = targetPath.stripAllExtensions().fileName;
|
||||
if (fileName !== '__init__') {
|
||||
// ex) src: a.py, dest: b.py -> ".b" will be returned.
|
||||
symbolName = fileName;
|
||||
@ -875,18 +870,18 @@ export function getRelativeModuleName(
|
||||
// like how it would return for sibling folder.
|
||||
//
|
||||
// if folder structure is not ignored, ".." will be returned
|
||||
symbolName = getFileName(destPath);
|
||||
destPath = getDirectoryPath(destPath);
|
||||
symbolName = destPath.fileName;
|
||||
destPath = destPath.getDirectory();
|
||||
}
|
||||
}
|
||||
|
||||
const relativePaths = getRelativePathComponentsFromDirectory(srcPath, destPath, (f) => fs.realCasePath(f));
|
||||
const relativePaths = srcPath.getRelativePathComponents(destPath);
|
||||
|
||||
// This assumes both file paths are under the same importing root.
|
||||
// So this doesn't handle paths pointing to 2 different import roots.
|
||||
// ex) user file A to library file B
|
||||
let currentPaths = '.';
|
||||
for (let i = 1; i < relativePaths.length; i++) {
|
||||
for (let i = 0; i < relativePaths.length; i++) {
|
||||
const relativePath = relativePaths[i];
|
||||
if (relativePath === '..') {
|
||||
currentPaths += '.';
|
||||
@ -907,25 +902,25 @@ export function getRelativeModuleName(
|
||||
return currentPaths;
|
||||
}
|
||||
|
||||
export function getDirectoryLeadingDotsPointsTo(fromDirectory: string, leadingDots: number) {
|
||||
export function getDirectoryLeadingDotsPointsTo(fromDirectory: Uri, leadingDots: number) {
|
||||
let currentDirectory = fromDirectory;
|
||||
for (let i = 1; i < leadingDots; i++) {
|
||||
if (currentDirectory === '') {
|
||||
if (currentDirectory.isRoot()) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
currentDirectory = getDirectoryPath(currentDirectory);
|
||||
currentDirectory = currentDirectory.getDirectory();
|
||||
}
|
||||
|
||||
return currentDirectory;
|
||||
}
|
||||
|
||||
export function getResolvedFilePath(importResult: ImportResult | undefined) {
|
||||
if (!importResult || !importResult.isImportFound || importResult.resolvedPaths.length === 0) {
|
||||
if (!importResult || !importResult.isImportFound || importResult.resolvedUris.length === 0) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
if (importResult.resolvedPaths.length === 1 && importResult.resolvedPaths[0] === '') {
|
||||
if (importResult.resolvedUris.length === 1 && importResult.resolvedUris[0].equals(Uri.empty())) {
|
||||
// Import is resolved to namespace package folder.
|
||||
if (importResult.packageDirectory) {
|
||||
return importResult.packageDirectory;
|
||||
@ -940,7 +935,7 @@ export function getResolvedFilePath(importResult: ImportResult | undefined) {
|
||||
}
|
||||
|
||||
// Regular case.
|
||||
return importResult.resolvedPaths[importResult.resolvedPaths.length - 1];
|
||||
return importResult.resolvedUris[importResult.resolvedUris.length - 1];
|
||||
}
|
||||
|
||||
export function haveSameParentModule(module1: string[], module2: string[]) {
|
||||
|
@ -114,7 +114,7 @@ export function createNamedTupleType(
|
||||
className,
|
||||
ParseTreeUtils.getClassFullName(errorNode, fileInfo.moduleName, className),
|
||||
fileInfo.moduleName,
|
||||
fileInfo.filePath,
|
||||
fileInfo.fileUri,
|
||||
ClassTypeFlags.ReadOnlyInstanceVariables,
|
||||
ParseTreeUtils.getTypeSourceId(errorNode),
|
||||
/* declaredMetaclass */ undefined,
|
||||
@ -207,7 +207,7 @@ export function createNamedTupleType(
|
||||
type: DeclarationType.Variable,
|
||||
node: stringNode as StringListNode,
|
||||
isRuntimeTypeExpression: true,
|
||||
path: fileInfo.filePath,
|
||||
uri: fileInfo.fileUri,
|
||||
range: convertOffsetsToRange(
|
||||
stringNode.start,
|
||||
TextRange.getEnd(stringNode),
|
||||
@ -303,7 +303,7 @@ export function createNamedTupleType(
|
||||
const declaration: VariableDeclaration = {
|
||||
type: DeclarationType.Variable,
|
||||
node: entryNameNode,
|
||||
path: fileInfo.filePath,
|
||||
uri: fileInfo.fileUri,
|
||||
typeAnnotationNode: entryTypeNode,
|
||||
range: convertOffsetsToRange(
|
||||
entryNameNode.start,
|
||||
|
@ -10,6 +10,7 @@
|
||||
*/
|
||||
|
||||
import { Diagnostic, DiagnosticWithinFile } from '../common/diagnostic';
|
||||
import { Uri } from '../common/uri/uri';
|
||||
import { ScopeType } from './scope';
|
||||
|
||||
export enum SymbolCategory {
|
||||
@ -37,7 +38,7 @@ export interface SymbolInfo {
|
||||
category: SymbolCategory;
|
||||
name: string;
|
||||
fullName: string;
|
||||
filePath: string;
|
||||
fileUri: Uri;
|
||||
isExported: boolean;
|
||||
typeKnownStatus: TypeKnownStatus;
|
||||
referenceCount: number;
|
||||
@ -47,7 +48,7 @@ export interface SymbolInfo {
|
||||
|
||||
export interface ModuleInfo {
|
||||
name: string;
|
||||
path: string;
|
||||
uri: Uri;
|
||||
isExported: boolean;
|
||||
}
|
||||
|
||||
@ -57,10 +58,10 @@ export interface PackageTypeReport {
|
||||
packageName: string;
|
||||
moduleName: string;
|
||||
ignoreExternal: boolean;
|
||||
packageRootDirectory: string | undefined;
|
||||
moduleRootDirectory: string | undefined;
|
||||
packageRootDirectoryUri: Uri | undefined;
|
||||
moduleRootDirectoryUri: Uri | undefined;
|
||||
isModuleSingleFile: boolean;
|
||||
pyTypedPath: string | undefined;
|
||||
pyTypedPathUri: Uri | undefined;
|
||||
missingFunctionDocStringCount: number;
|
||||
missingClassDocStringCount: number;
|
||||
missingDefaultParamCount: number;
|
||||
@ -85,20 +86,20 @@ export interface PackageTypeReport {
|
||||
|
||||
export function getEmptyReport(
|
||||
packageName: string,
|
||||
packageRootDirectory: string,
|
||||
packageRootUri: Uri,
|
||||
moduleName: string,
|
||||
moduleRootDirectory: string,
|
||||
moduleRootUri: Uri,
|
||||
isModuleSingleFile: boolean,
|
||||
ignoreExternal: boolean
|
||||
) {
|
||||
const report: PackageTypeReport = {
|
||||
packageName,
|
||||
ignoreExternal,
|
||||
packageRootDirectory,
|
||||
packageRootDirectoryUri: packageRootUri,
|
||||
moduleName,
|
||||
moduleRootDirectory,
|
||||
moduleRootDirectoryUri: moduleRootUri,
|
||||
isModuleSingleFile,
|
||||
pyTypedPath: undefined,
|
||||
pyTypedPathUri: undefined,
|
||||
missingFunctionDocStringCount: 0,
|
||||
missingClassDocStringCount: 0,
|
||||
missingDefaultParamCount: 0,
|
||||
|
@ -14,16 +14,11 @@ import { NullConsole } from '../common/console';
|
||||
import { assert } from '../common/debug';
|
||||
import { Diagnostic, DiagnosticAddendum, DiagnosticCategory } from '../common/diagnostic';
|
||||
import { FullAccessHost } from '../common/fullAccessHost';
|
||||
import {
|
||||
combinePaths,
|
||||
getDirectoryPath,
|
||||
getFileExtension,
|
||||
getFileName,
|
||||
stripFileExtension,
|
||||
tryStat,
|
||||
} from '../common/pathUtils';
|
||||
import { getFileExtension, stripFileExtension } from '../common/pathUtils';
|
||||
import { ServiceProvider } from '../common/serviceProvider';
|
||||
import { getEmptyRange, Range } from '../common/textRange';
|
||||
import { Uri } from '../common/uri/uri';
|
||||
import { tryStat } from '../common/uri/uriUtils';
|
||||
import { DeclarationType, FunctionDeclaration, VariableDeclaration } from './declaration';
|
||||
import { createImportedModuleDescriptor, ImportResolver } from './importResolver';
|
||||
import {
|
||||
@ -67,7 +62,7 @@ import {
|
||||
type PublicSymbolSet = Set<string>;
|
||||
|
||||
interface ModuleDirectoryInfo {
|
||||
moduleDirectory: string;
|
||||
moduleDirectory: Uri;
|
||||
isModuleSingleFile: boolean;
|
||||
}
|
||||
|
||||
@ -83,8 +78,8 @@ export class PackageTypeVerifier {
|
||||
private _packageName: string,
|
||||
private _ignoreExternal = false
|
||||
) {
|
||||
const host = new FullAccessHost(_serviceProvider.fs());
|
||||
this._configOptions = new ConfigOptions('');
|
||||
const host = new FullAccessHost(_serviceProvider);
|
||||
this._configOptions = new ConfigOptions(Uri.empty());
|
||||
|
||||
this._configOptions.defaultPythonPlatform = commandLineOptions.pythonPlatform;
|
||||
this._configOptions.defaultPythonVersion = commandLineOptions.pythonVersion;
|
||||
@ -99,11 +94,11 @@ export class PackageTypeVerifier {
|
||||
this._configOptions.evaluateUnknownImportsAsAny = true;
|
||||
}
|
||||
|
||||
this._execEnv = this._configOptions.findExecEnvironment('.');
|
||||
this._execEnv = this._configOptions.findExecEnvironment(Uri.file('.', _serviceProvider.fs().isCaseSensitive));
|
||||
this._importResolver = new ImportResolver(
|
||||
this._serviceProvider,
|
||||
this._configOptions,
|
||||
new FullAccessHost(this._serviceProvider.fs())
|
||||
new FullAccessHost(this._serviceProvider)
|
||||
);
|
||||
this._program = new Program(this._importResolver, this._configOptions, this._serviceProvider);
|
||||
}
|
||||
@ -117,9 +112,9 @@ export class PackageTypeVerifier {
|
||||
|
||||
const report = getEmptyReport(
|
||||
moduleNameParts[0],
|
||||
packageDirectoryInfo?.moduleDirectory ?? '',
|
||||
packageDirectoryInfo?.moduleDirectory ?? Uri.empty(),
|
||||
trimmedModuleName,
|
||||
moduleDirectoryInfo?.moduleDirectory ?? '',
|
||||
moduleDirectoryInfo?.moduleDirectory ?? Uri.empty(),
|
||||
moduleDirectoryInfo?.isModuleSingleFile ?? false,
|
||||
this._ignoreExternal
|
||||
);
|
||||
@ -134,7 +129,7 @@ export class PackageTypeVerifier {
|
||||
getEmptyRange()
|
||||
)
|
||||
);
|
||||
} else if (!report.moduleRootDirectory) {
|
||||
} else if (!report.moduleRootDirectoryUri) {
|
||||
commonDiagnostics.push(
|
||||
new Diagnostic(
|
||||
DiagnosticCategory.Error,
|
||||
@ -144,14 +139,14 @@ export class PackageTypeVerifier {
|
||||
);
|
||||
} else {
|
||||
let pyTypedInfo: PyTypedInfo | undefined;
|
||||
if (report.moduleRootDirectory) {
|
||||
pyTypedInfo = this._getDeepestPyTypedInfo(report.moduleRootDirectory, moduleNameParts);
|
||||
if (report.moduleRootDirectoryUri) {
|
||||
pyTypedInfo = this._getDeepestPyTypedInfo(report.moduleRootDirectoryUri, moduleNameParts);
|
||||
}
|
||||
|
||||
// If we couldn't find any "py.typed" info in the module path, search again
|
||||
// starting at the package root.
|
||||
if (!pyTypedInfo && report.packageRootDirectory) {
|
||||
pyTypedInfo = this._getDeepestPyTypedInfo(report.packageRootDirectory, moduleNameParts);
|
||||
if (!pyTypedInfo && report.packageRootDirectoryUri) {
|
||||
pyTypedInfo = this._getDeepestPyTypedInfo(report.packageRootDirectoryUri, moduleNameParts);
|
||||
}
|
||||
|
||||
if (!pyTypedInfo) {
|
||||
@ -159,10 +154,10 @@ export class PackageTypeVerifier {
|
||||
new Diagnostic(DiagnosticCategory.Error, 'No py.typed file found', getEmptyRange())
|
||||
);
|
||||
} else {
|
||||
report.pyTypedPath = pyTypedInfo.pyTypedPath;
|
||||
report.pyTypedPathUri = pyTypedInfo.pyTypedPath;
|
||||
|
||||
const publicModules = this._getListOfPublicModules(
|
||||
report.moduleRootDirectory,
|
||||
report.moduleRootDirectoryUri,
|
||||
report.isModuleSingleFile,
|
||||
trimmedModuleName
|
||||
);
|
||||
@ -239,12 +234,12 @@ export class PackageTypeVerifier {
|
||||
}
|
||||
}
|
||||
|
||||
private _getDeepestPyTypedInfo(rootDirectory: string, packageNameParts: string[]) {
|
||||
private _getDeepestPyTypedInfo(rootDirectory: Uri, packageNameParts: string[]) {
|
||||
let subNameParts = Array.from(packageNameParts);
|
||||
|
||||
// Find the deepest py.typed file that corresponds to the requested submodule.
|
||||
while (subNameParts.length >= 1) {
|
||||
const packageSubdir = combinePaths(rootDirectory, ...subNameParts.slice(1));
|
||||
const packageSubdir = rootDirectory.combinePaths(...subNameParts.slice(1));
|
||||
const pyTypedInfo = getPyTypedInfo(this._serviceProvider.fs(), packageSubdir);
|
||||
if (pyTypedInfo) {
|
||||
return pyTypedInfo;
|
||||
@ -257,7 +252,11 @@ export class PackageTypeVerifier {
|
||||
}
|
||||
|
||||
private _resolveImport(moduleName: string) {
|
||||
return this._importResolver.resolveImport('', this._execEnv, createImportedModuleDescriptor(moduleName));
|
||||
return this._importResolver.resolveImport(
|
||||
Uri.empty(),
|
||||
this._execEnv,
|
||||
createImportedModuleDescriptor(moduleName)
|
||||
);
|
||||
}
|
||||
|
||||
private _getPublicSymbolsForModule(
|
||||
@ -268,7 +267,7 @@ export class PackageTypeVerifier {
|
||||
const importResult = this._resolveImport(moduleName);
|
||||
|
||||
if (importResult.isImportFound) {
|
||||
const modulePath = importResult.resolvedPaths[importResult.resolvedPaths.length - 1];
|
||||
const modulePath = importResult.resolvedUris[importResult.resolvedUris.length - 1];
|
||||
this._program.addTrackedFiles([modulePath], /* isThirdPartyImport */ true, /* isInPyTypedPackage */ true);
|
||||
|
||||
const sourceFile = this._program.getBoundSourceFile(modulePath);
|
||||
@ -276,7 +275,7 @@ export class PackageTypeVerifier {
|
||||
if (sourceFile) {
|
||||
const module: ModuleInfo = {
|
||||
name: moduleName,
|
||||
path: modulePath,
|
||||
uri: modulePath,
|
||||
isExported: true,
|
||||
};
|
||||
|
||||
@ -379,15 +378,15 @@ export class PackageTypeVerifier {
|
||||
)
|
||||
);
|
||||
} else {
|
||||
const modulePath = importResult.resolvedPaths[importResult.resolvedPaths.length - 1];
|
||||
const modulePath = importResult.resolvedUris[importResult.resolvedUris.length - 1];
|
||||
|
||||
const module: ModuleInfo = {
|
||||
name: moduleName,
|
||||
path: modulePath,
|
||||
uri: modulePath,
|
||||
isExported: true,
|
||||
};
|
||||
|
||||
report.modules.set(modulePath, module);
|
||||
report.modules.set(modulePath.key, module);
|
||||
this._program.addTrackedFiles([modulePath], /* isThirdPartyImport */ true, /* isInPyTypedPackage */ true);
|
||||
|
||||
const sourceFile = this._program.getBoundSourceFile(modulePath);
|
||||
@ -413,9 +412,9 @@ export class PackageTypeVerifier {
|
||||
|
||||
// Scans the directory structure for a list of public modules
|
||||
// within the package.
|
||||
private _getListOfPublicModules(moduleRootPath: string, isModuleSingleFile: boolean, moduleName: string): string[] {
|
||||
private _getListOfPublicModules(moduleRoot: Uri, isModuleSingleFile: boolean, moduleName: string): string[] {
|
||||
const publicModules: string[] = [];
|
||||
this._addPublicModulesRecursive(moduleRootPath, isModuleSingleFile, moduleName, publicModules);
|
||||
this._addPublicModulesRecursive(moduleRoot, isModuleSingleFile, moduleName, publicModules);
|
||||
|
||||
// Make sure modules are unique. There may be duplicates if a ".py" and ".pyi"
|
||||
// exist for some modules.
|
||||
@ -433,7 +432,7 @@ export class PackageTypeVerifier {
|
||||
}
|
||||
|
||||
private _addPublicModulesRecursive(
|
||||
dirPath: string,
|
||||
dirPath: Uri,
|
||||
isModuleSingleFile: boolean,
|
||||
modulePath: string,
|
||||
publicModules: string[]
|
||||
@ -444,7 +443,7 @@ export class PackageTypeVerifier {
|
||||
let isFile = entry.isFile();
|
||||
let isDirectory = entry.isDirectory();
|
||||
if (entry.isSymbolicLink()) {
|
||||
const stat = tryStat(this._serviceProvider.fs(), combinePaths(dirPath, entry.name));
|
||||
const stat = tryStat(this._serviceProvider.fs(), dirPath.combinePaths(entry.name));
|
||||
if (stat) {
|
||||
isFile = stat.isFile();
|
||||
isDirectory = stat.isDirectory();
|
||||
@ -477,7 +476,7 @@ export class PackageTypeVerifier {
|
||||
} else if (isDirectory) {
|
||||
if (!isPrivateOrProtectedName(entry.name) && this._isLegalModulePartName(entry.name)) {
|
||||
this._addPublicModulesRecursive(
|
||||
combinePaths(dirPath, entry.name),
|
||||
dirPath.combinePaths(entry.name),
|
||||
isModuleSingleFile,
|
||||
`${modulePath}.${entry.name}`,
|
||||
publicModules
|
||||
@ -572,7 +571,7 @@ export class PackageTypeVerifier {
|
||||
const decls = symbol.getDeclarations();
|
||||
const primaryDecl = decls.length > 0 ? decls[decls.length - 1] : undefined;
|
||||
const declRange = primaryDecl?.range || getEmptyRange();
|
||||
const declPath = primaryDecl?.path || '';
|
||||
const declPath = primaryDecl?.uri || Uri.empty();
|
||||
const symbolCategory = this._getSymbolCategory(symbol, symbolType);
|
||||
const isExported = publicSymbols.has(fullName);
|
||||
|
||||
@ -590,7 +589,7 @@ export class PackageTypeVerifier {
|
||||
category: symbolCategory,
|
||||
name,
|
||||
fullName,
|
||||
filePath: module.path,
|
||||
fileUri: Uri.file(module.path, this._serviceProvider.fs().isCaseSensitive),
|
||||
isExported,
|
||||
typeKnownStatus: TypeKnownStatus.Known,
|
||||
referenceCount: 1,
|
||||
@ -616,7 +615,7 @@ export class PackageTypeVerifier {
|
||||
const decls = symbol.getDeclarations();
|
||||
const primaryDecl = decls.length > 0 ? decls[decls.length - 1] : undefined;
|
||||
const declRange = primaryDecl?.range || getEmptyRange();
|
||||
const declPath = primaryDecl?.path || '';
|
||||
const declPath = primaryDecl?.uri || Uri.empty();
|
||||
|
||||
const extraInfo = new DiagnosticAddendum();
|
||||
if (baseSymbolType) {
|
||||
@ -664,7 +663,7 @@ export class PackageTypeVerifier {
|
||||
symbolInfo: SymbolInfo,
|
||||
type: Type,
|
||||
declRange: Range,
|
||||
declFilePath: string,
|
||||
declFileUri: Uri,
|
||||
publicSymbols: PublicSymbolSet,
|
||||
skipDocStringCheck = false
|
||||
): TypeKnownStatus {
|
||||
@ -677,7 +676,7 @@ export class PackageTypeVerifier {
|
||||
symbolInfo,
|
||||
`Type argument ${index + 1} for type alias "${type.typeAliasInfo!.name}" has unknown type`,
|
||||
declRange,
|
||||
declFilePath
|
||||
declFileUri
|
||||
);
|
||||
knownStatus = TypeKnownStatus.Unknown;
|
||||
} else if (isPartlyUnknown(typeArg)) {
|
||||
@ -687,7 +686,7 @@ export class PackageTypeVerifier {
|
||||
type.typeAliasInfo!.name
|
||||
}" has partially unknown type`,
|
||||
declRange,
|
||||
declFilePath
|
||||
declFileUri
|
||||
);
|
||||
knownStatus = TypeKnownStatus.PartiallyUnknown;
|
||||
}
|
||||
@ -702,7 +701,7 @@ export class PackageTypeVerifier {
|
||||
'Type is missing type annotation and could be inferred differently by type checkers' +
|
||||
ambiguousDiag.getString(),
|
||||
declRange,
|
||||
declFilePath
|
||||
declFileUri
|
||||
);
|
||||
knownStatus = this._updateKnownStatusIfWorse(knownStatus, TypeKnownStatus.Ambiguous);
|
||||
}
|
||||
@ -721,7 +720,7 @@ export class PackageTypeVerifier {
|
||||
symbolInfo.fullName
|
||||
}"`,
|
||||
declRange,
|
||||
declFilePath
|
||||
declFileUri
|
||||
);
|
||||
knownStatus = this._updateKnownStatusIfWorse(knownStatus, TypeKnownStatus.Unknown);
|
||||
break;
|
||||
@ -736,7 +735,7 @@ export class PackageTypeVerifier {
|
||||
symbolInfo,
|
||||
subtype,
|
||||
declRange,
|
||||
declFilePath,
|
||||
declFileUri,
|
||||
publicSymbols
|
||||
)
|
||||
);
|
||||
@ -753,7 +752,7 @@ export class PackageTypeVerifier {
|
||||
symbolInfo,
|
||||
overload,
|
||||
declRange,
|
||||
declFilePath,
|
||||
declFileUri,
|
||||
publicSymbols
|
||||
)
|
||||
);
|
||||
@ -771,7 +770,7 @@ export class PackageTypeVerifier {
|
||||
publicSymbols,
|
||||
symbolInfo,
|
||||
declRange,
|
||||
declFilePath,
|
||||
declFileUri,
|
||||
undefined /* diag */,
|
||||
skipDocStringCheck
|
||||
)
|
||||
@ -821,7 +820,7 @@ export class PackageTypeVerifier {
|
||||
symbolInfo,
|
||||
accessType,
|
||||
getEmptyRange(),
|
||||
'',
|
||||
Uri.empty(),
|
||||
publicSymbols,
|
||||
skipDocStringCheck
|
||||
)
|
||||
@ -847,7 +846,7 @@ export class PackageTypeVerifier {
|
||||
symbolInfo,
|
||||
`Type argument ${index + 1} for class "${type.details.name}" has unknown type`,
|
||||
declRange,
|
||||
declFilePath
|
||||
declFileUri
|
||||
);
|
||||
knownStatus = this._updateKnownStatusIfWorse(knownStatus, TypeKnownStatus.Unknown);
|
||||
} else if (isPartlyUnknown(typeArg)) {
|
||||
@ -859,7 +858,7 @@ export class PackageTypeVerifier {
|
||||
type.details.name
|
||||
}" has partially unknown type` + diag.getString(),
|
||||
declRange,
|
||||
declFilePath
|
||||
declFileUri
|
||||
);
|
||||
knownStatus = this._updateKnownStatusIfWorse(knownStatus, TypeKnownStatus.PartiallyUnknown);
|
||||
}
|
||||
@ -877,7 +876,7 @@ export class PackageTypeVerifier {
|
||||
symbolInfo,
|
||||
`Module "${moduleSymbol.fullName}" is partially unknown`,
|
||||
declRange,
|
||||
declFilePath
|
||||
declFileUri
|
||||
);
|
||||
knownStatus = this._updateKnownStatusIfWorse(knownStatus, moduleSymbol.typeKnownStatus);
|
||||
}
|
||||
@ -899,15 +898,15 @@ export class PackageTypeVerifier {
|
||||
publicSymbols: PublicSymbolSet,
|
||||
symbolInfo?: SymbolInfo,
|
||||
declRange?: Range,
|
||||
declFilePath?: string,
|
||||
declFileUri?: Uri,
|
||||
diag?: DiagnosticAddendum,
|
||||
skipDocStringCheck = false
|
||||
): TypeKnownStatus {
|
||||
let knownStatus = TypeKnownStatus.Known;
|
||||
|
||||
// If the file path wasn't provided, try to get it from the type.
|
||||
if (type.details.declaration && !declFilePath) {
|
||||
declFilePath = type.details.declaration.path;
|
||||
if (type.details.declaration && !declFileUri) {
|
||||
declFileUri = type.details.declaration.uri;
|
||||
}
|
||||
|
||||
type.details.parameters.forEach((param, index) => {
|
||||
@ -929,7 +928,7 @@ export class PackageTypeVerifier {
|
||||
symbolInfo,
|
||||
`Type annotation for parameter "${param.name}" is missing`,
|
||||
declRange ?? getEmptyRange(),
|
||||
declFilePath ?? ''
|
||||
declFileUri ?? Uri.empty()
|
||||
);
|
||||
}
|
||||
diag?.createAddendum().addMessage(`Type annotation for parameter "${param.name}" is missing`);
|
||||
@ -941,7 +940,7 @@ export class PackageTypeVerifier {
|
||||
symbolInfo,
|
||||
`Type of parameter "${param.name}" is unknown`,
|
||||
declRange ?? getEmptyRange(),
|
||||
declFilePath ?? ''
|
||||
declFileUri ?? Uri.empty()
|
||||
);
|
||||
diag?.createAddendum().addMessage(`Type of parameter "${param.name}" is unknown`);
|
||||
}
|
||||
@ -963,7 +962,7 @@ export class PackageTypeVerifier {
|
||||
symbolInfo,
|
||||
`Type of parameter "${param.name}" is partially unknown` + extraInfo.getString(),
|
||||
declRange ?? getEmptyRange(),
|
||||
declFilePath ?? ''
|
||||
declFileUri ?? Uri.empty()
|
||||
);
|
||||
}
|
||||
|
||||
@ -986,7 +985,7 @@ export class PackageTypeVerifier {
|
||||
symbolInfo,
|
||||
`Return type is unknown`,
|
||||
declRange ?? getEmptyRange(),
|
||||
declFilePath ?? ''
|
||||
declFileUri ?? Uri.empty()
|
||||
);
|
||||
}
|
||||
knownStatus = this._updateKnownStatusIfWorse(knownStatus, TypeKnownStatus.Unknown);
|
||||
@ -1009,7 +1008,7 @@ export class PackageTypeVerifier {
|
||||
symbolInfo,
|
||||
`Return type is partially unknown` + extraInfo.getString(),
|
||||
declRange ?? getEmptyRange(),
|
||||
declFilePath ?? ''
|
||||
declFileUri ?? Uri.empty()
|
||||
);
|
||||
}
|
||||
|
||||
@ -1030,7 +1029,7 @@ export class PackageTypeVerifier {
|
||||
symbolInfo,
|
||||
`Return type annotation is missing`,
|
||||
declRange ?? getEmptyRange(),
|
||||
declFilePath ?? ''
|
||||
declFileUri ?? Uri.empty()
|
||||
);
|
||||
}
|
||||
diag?.createAddendum().addMessage(`Return type annotation is missing`);
|
||||
@ -1060,7 +1059,7 @@ export class PackageTypeVerifier {
|
||||
symbolInfo,
|
||||
`No docstring found for function "${symbolInfo.fullName}"`,
|
||||
declRange ?? getEmptyRange(),
|
||||
declFilePath ?? ''
|
||||
declFileUri ?? Uri.empty()
|
||||
);
|
||||
}
|
||||
|
||||
@ -1074,7 +1073,7 @@ export class PackageTypeVerifier {
|
||||
symbolInfo,
|
||||
`One or more default values in function "${symbolInfo.fullName}" is specified as "..."`,
|
||||
declRange ?? getEmptyRange(),
|
||||
declFilePath ?? ''
|
||||
declFileUri ?? Uri.empty()
|
||||
);
|
||||
}
|
||||
|
||||
@ -1100,7 +1099,7 @@ export class PackageTypeVerifier {
|
||||
category: SymbolCategory.Class,
|
||||
name: type.details.name,
|
||||
fullName: type.details.fullName,
|
||||
filePath: type.details.filePath,
|
||||
fileUri: type.details.fileUri,
|
||||
isExported: publicSymbols.has(type.details.fullName),
|
||||
typeKnownStatus: TypeKnownStatus.Known,
|
||||
referenceCount: 1,
|
||||
@ -1116,7 +1115,7 @@ export class PackageTypeVerifier {
|
||||
symbolInfo,
|
||||
`No docstring found for class "${type.details.fullName}"`,
|
||||
getEmptyRange(),
|
||||
''
|
||||
Uri.empty()
|
||||
);
|
||||
|
||||
report.missingClassDocStringCount++;
|
||||
@ -1155,7 +1154,7 @@ export class PackageTypeVerifier {
|
||||
// Add information for the metaclass.
|
||||
if (type.details.effectiveMetaclass) {
|
||||
if (!isInstantiableClass(type.details.effectiveMetaclass)) {
|
||||
this._addSymbolError(symbolInfo, `Type of metaclass unknown`, getEmptyRange(), '');
|
||||
this._addSymbolError(symbolInfo, `Type of metaclass unknown`, getEmptyRange(), Uri.empty());
|
||||
symbolInfo.typeKnownStatus = this._updateKnownStatusIfWorse(
|
||||
symbolInfo.typeKnownStatus,
|
||||
TypeKnownStatus.PartiallyUnknown
|
||||
@ -1175,7 +1174,7 @@ export class PackageTypeVerifier {
|
||||
`Type of metaclass "${type.details.effectiveMetaclass}" is partially unknown` +
|
||||
diag.getString(),
|
||||
getEmptyRange(),
|
||||
''
|
||||
Uri.empty()
|
||||
);
|
||||
symbolInfo.typeKnownStatus = this._updateKnownStatusIfWorse(
|
||||
symbolInfo.typeKnownStatus,
|
||||
@ -1188,7 +1187,7 @@ export class PackageTypeVerifier {
|
||||
// Add information for base classes.
|
||||
type.details.baseClasses.forEach((baseClass) => {
|
||||
if (!isInstantiableClass(baseClass)) {
|
||||
this._addSymbolError(symbolInfo, `Type of base class unknown`, getEmptyRange(), '');
|
||||
this._addSymbolError(symbolInfo, `Type of base class unknown`, getEmptyRange(), Uri.empty());
|
||||
symbolInfo.typeKnownStatus = this._updateKnownStatusIfWorse(
|
||||
symbolInfo.typeKnownStatus,
|
||||
TypeKnownStatus.PartiallyUnknown
|
||||
@ -1208,7 +1207,7 @@ export class PackageTypeVerifier {
|
||||
symbolInfo,
|
||||
`Type of base class "${baseClass.details.fullName}" is partially unknown` + diag.getString(),
|
||||
getEmptyRange(),
|
||||
''
|
||||
Uri.empty()
|
||||
);
|
||||
|
||||
symbolInfo.typeKnownStatus = this._updateKnownStatusIfWorse(
|
||||
@ -1238,7 +1237,7 @@ export class PackageTypeVerifier {
|
||||
category: SymbolCategory.Module,
|
||||
name: type.moduleName,
|
||||
fullName: type.moduleName,
|
||||
filePath: type.filePath,
|
||||
fileUri: type.fileUri,
|
||||
isExported: publicSymbols.has(type.moduleName),
|
||||
typeKnownStatus: TypeKnownStatus.Known,
|
||||
referenceCount: 1,
|
||||
@ -1448,19 +1447,21 @@ export class PackageTypeVerifier {
|
||||
|
||||
private _getDirectoryInfoForModule(moduleName: string): ModuleDirectoryInfo | undefined {
|
||||
const importResult = this._importResolver.resolveImport(
|
||||
'',
|
||||
Uri.empty(),
|
||||
this._execEnv,
|
||||
createImportedModuleDescriptor(moduleName)
|
||||
);
|
||||
|
||||
if (importResult.isImportFound) {
|
||||
const resolvedPath = importResult.resolvedPaths[importResult.resolvedPaths.length - 1];
|
||||
const resolvedPath = importResult.resolvedUris[importResult.resolvedUris.length - 1];
|
||||
|
||||
// If it's a namespace package with no __init__.py(i), use the package
|
||||
// directory instead.
|
||||
const moduleDirectory = resolvedPath ? getDirectoryPath(resolvedPath) : importResult.packageDirectory ?? '';
|
||||
const moduleDirectory = resolvedPath
|
||||
? resolvedPath.getDirectory()
|
||||
: importResult.packageDirectory ?? Uri.empty();
|
||||
let isModuleSingleFile = false;
|
||||
if (resolvedPath && stripFileExtension(getFileName(resolvedPath)) !== '__init__') {
|
||||
if (resolvedPath && stripFileExtension(resolvedPath.fileName) !== '__init__') {
|
||||
isModuleSingleFile = true;
|
||||
}
|
||||
|
||||
@ -1508,17 +1509,17 @@ export class PackageTypeVerifier {
|
||||
report.symbols.set(symbolInfo.fullName, symbolInfo);
|
||||
}
|
||||
|
||||
private _addSymbolError(symbolInfo: SymbolInfo, message: string, declRange: Range, declFilePath: string) {
|
||||
private _addSymbolError(symbolInfo: SymbolInfo, message: string, declRange: Range, declUri: Uri) {
|
||||
symbolInfo.diagnostics.push({
|
||||
diagnostic: new Diagnostic(DiagnosticCategory.Error, message, declRange),
|
||||
filePath: declFilePath,
|
||||
uri: declUri,
|
||||
});
|
||||
}
|
||||
|
||||
private _addSymbolWarning(symbolInfo: SymbolInfo, message: string, declRange: Range, declFilePath: string) {
|
||||
private _addSymbolWarning(symbolInfo: SymbolInfo, message: string, declRange: Range, declUri: Uri) {
|
||||
symbolInfo.diagnostics.push({
|
||||
diagnostic: new Diagnostic(DiagnosticCategory.Warning, message, declRange),
|
||||
filePath: declFilePath,
|
||||
uri: declUri,
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -9,46 +9,46 @@
|
||||
|
||||
import { getOrAdd } from '../common/collectionUtils';
|
||||
import { FileSystem } from '../common/fileSystem';
|
||||
import { ensureTrailingDirectorySeparator, normalizePath, realCasePath } from '../common/pathUtils';
|
||||
import { Uri } from '../common/uri/uri';
|
||||
import { ImportResult } from './importResult';
|
||||
|
||||
export type ImportPath = { importPath: string | undefined };
|
||||
export type ImportPath = { importPath: Uri | undefined };
|
||||
|
||||
type CacheEntry = { importResult: ImportResult; path: string; importName: string };
|
||||
type CacheEntry = { importResult: ImportResult; path: Uri; importName: string };
|
||||
|
||||
export class ParentDirectoryCache {
|
||||
private readonly _importChecked = new Map<string, Map<string, ImportPath>>();
|
||||
private readonly _cachedResults = new Map<string, Map<string, ImportResult>>();
|
||||
|
||||
private _libPathCache: string[] | undefined = undefined;
|
||||
private _libPathCache: Uri[] | undefined = undefined;
|
||||
|
||||
constructor(private _importRootGetter: () => string[]) {
|
||||
constructor(private _importRootGetter: () => Uri[]) {
|
||||
// empty
|
||||
}
|
||||
|
||||
getImportResult(path: string, importName: string, importResult: ImportResult): ImportResult | undefined {
|
||||
const result = this._cachedResults.get(importName)?.get(path);
|
||||
getImportResult(path: Uri, importName: string, importResult: ImportResult): ImportResult | undefined {
|
||||
const result = this._cachedResults.get(importName)?.get(path.key);
|
||||
if (result) {
|
||||
// We already checked for the importName at the path.
|
||||
// Return the result if succeeded otherwise, return regular import result given.
|
||||
return result ?? importResult;
|
||||
}
|
||||
|
||||
const checked = this._importChecked.get(importName)?.get(path);
|
||||
const checked = this._importChecked.get(importName)?.get(path.key);
|
||||
if (checked) {
|
||||
// We already checked for the importName at the path.
|
||||
if (!checked.importPath) {
|
||||
return importResult;
|
||||
}
|
||||
|
||||
return this._cachedResults.get(importName)?.get(checked.importPath) ?? importResult;
|
||||
return this._cachedResults.get(importName)?.get(checked.importPath.key) ?? importResult;
|
||||
}
|
||||
|
||||
return undefined;
|
||||
}
|
||||
|
||||
checkValidPath(fs: FileSystem, sourceFilePath: string, root: string): boolean {
|
||||
if (!sourceFilePath.startsWith(root)) {
|
||||
checkValidPath(fs: FileSystem, sourceFileUri: Uri, root: Uri): boolean {
|
||||
if (!sourceFileUri.startsWith(root)) {
|
||||
// We don't search containing folders for libs.
|
||||
return false;
|
||||
}
|
||||
@ -56,11 +56,11 @@ export class ParentDirectoryCache {
|
||||
this._libPathCache =
|
||||
this._libPathCache ??
|
||||
this._importRootGetter()
|
||||
.map((r) => ensureTrailingDirectorySeparator(realCasePath(normalizePath(r), fs)))
|
||||
.map((r) => fs.realCasePath(r))
|
||||
.filter((r) => r !== root)
|
||||
.filter((r) => r.startsWith(root));
|
||||
|
||||
if (this._libPathCache.some((p) => sourceFilePath.startsWith(p))) {
|
||||
if (this._libPathCache.some((p) => sourceFileUri.startsWith(p))) {
|
||||
// Make sure it is not lib folders under user code root.
|
||||
// ex) .venv folder
|
||||
return false;
|
||||
@ -69,13 +69,13 @@ export class ParentDirectoryCache {
|
||||
return true;
|
||||
}
|
||||
|
||||
checked(path: string, importName: string, importPath: ImportPath) {
|
||||
getOrAdd(this._importChecked, importName, () => new Map<string, ImportPath>()).set(path, importPath);
|
||||
checked(path: Uri, importName: string, importPath: ImportPath) {
|
||||
getOrAdd(this._importChecked, importName, () => new Map<string, ImportPath>()).set(path.key, importPath);
|
||||
}
|
||||
|
||||
add(result: CacheEntry) {
|
||||
getOrAdd(this._cachedResults, result.importName, () => new Map<string, ImportResult>()).set(
|
||||
result.path,
|
||||
result.path.key,
|
||||
result.importResult
|
||||
);
|
||||
}
|
||||
|
@ -2718,7 +2718,7 @@ export function getScopeIdForNode(node: ParseNode): string {
|
||||
}
|
||||
|
||||
const fileInfo = AnalyzerNodeInfo.getFileInfo(node);
|
||||
return `${fileInfo.filePath}.${node.start.toString()}-${name}`;
|
||||
return `${fileInfo.fileUri.key}.${node.start.toString()}-${name}`;
|
||||
}
|
||||
|
||||
// Walks up the parse tree and finds all scopes that can provide
|
||||
|
@ -21,21 +21,14 @@ import { FileDiagnostics } from '../common/diagnosticSink';
|
||||
import { FileEditAction } from '../common/editAction';
|
||||
import { EditableProgram, ProgramView } from '../common/extensibility';
|
||||
import { LogTracker } from '../common/logTracker';
|
||||
import {
|
||||
combinePaths,
|
||||
getDirectoryPath,
|
||||
getFileName,
|
||||
getRelativePath,
|
||||
makeDirectories,
|
||||
normalizePath,
|
||||
stripFileExtension,
|
||||
} from '../common/pathUtils';
|
||||
import { convertRangeToTextRange } from '../common/positionUtils';
|
||||
import { ServiceProvider } from '../common/serviceProvider';
|
||||
import '../common/serviceProviderExtensions';
|
||||
import { ServiceKeys } from '../common/serviceProviderExtensions';
|
||||
import { Range, doRangesIntersect } from '../common/textRange';
|
||||
import { Duration, timingStats } from '../common/timing';
|
||||
import { Uri } from '../common/uri/uri';
|
||||
import { makeDirectories } from '../common/uri/uriUtils';
|
||||
import { ParseResults } from '../parser/parser';
|
||||
import { AbsoluteModuleDescriptor, ImportLookupResult, LookupImportOptions } from './analyzerFileInfo';
|
||||
import * as AnalyzerNodeInfo from './analyzerNodeInfo';
|
||||
@ -73,7 +66,7 @@ export interface MaxAnalysisTime {
|
||||
}
|
||||
|
||||
interface UpdateImportInfo {
|
||||
path: string;
|
||||
path: Uri;
|
||||
isTypeshedFile: boolean;
|
||||
isThirdPartyImport: boolean;
|
||||
isPyTypedPresent: boolean;
|
||||
@ -84,14 +77,13 @@ export type PreCheckCallback = (parseResults: ParseResults, evaluator: TypeEvalu
|
||||
export interface ISourceFileFactory {
|
||||
createSourceFile(
|
||||
serviceProvider: ServiceProvider,
|
||||
filePath: string,
|
||||
fileUri: Uri,
|
||||
moduleName: string,
|
||||
isThirdPartyImport: boolean,
|
||||
isThirdPartyPyTypedPresent: boolean,
|
||||
editMode: SourceFileEditMode,
|
||||
console?: ConsoleInterface,
|
||||
logTracker?: LogTracker,
|
||||
realFilePath?: string,
|
||||
ipythonMode?: IPythonMode
|
||||
): SourceFile;
|
||||
}
|
||||
@ -105,8 +97,7 @@ export namespace ISourceFileFactory {
|
||||
export interface OpenFileOptions {
|
||||
isTracked: boolean;
|
||||
ipythonMode: IPythonMode;
|
||||
chainedFilePath: string | undefined;
|
||||
realFilePath: string | undefined;
|
||||
chainedFileUri: Uri | undefined;
|
||||
}
|
||||
|
||||
// Track edit mode related information.
|
||||
@ -195,7 +186,7 @@ export class Program {
|
||||
return this._console;
|
||||
}
|
||||
|
||||
get rootPath(): string {
|
||||
get rootPath(): Uri {
|
||||
return this._configOptions.projectRoot;
|
||||
}
|
||||
|
||||
@ -241,7 +232,7 @@ export class Program {
|
||||
if (newContents) {
|
||||
// Create a text document so we can compute the edits.
|
||||
const textDocument = TextDocument.create(
|
||||
fileInfo.sourceFile.getFilePath(),
|
||||
fileInfo.sourceFile.getUri().toString(),
|
||||
'python',
|
||||
1,
|
||||
fileInfo.sourceFile.getFileContent() || ''
|
||||
@ -249,7 +240,7 @@ export class Program {
|
||||
|
||||
// Add an edit action to the list.
|
||||
edits.push({
|
||||
filePath: fileInfo.sourceFile.getFilePath(),
|
||||
fileUri: fileInfo.sourceFile.getUri(),
|
||||
range: {
|
||||
start: { line: 0, character: 0 },
|
||||
end: { line: textDocument.lineCount, character: 0 },
|
||||
@ -268,7 +259,7 @@ export class Program {
|
||||
// We don't need to care about file diagnostics since in edit mode
|
||||
// checker won't run.
|
||||
v.sourceFile.prepareForClose();
|
||||
this._removeSourceFileFromListAndMap(v.sourceFile.getFilePath(), i);
|
||||
this._removeSourceFileFromListAndMap(v.sourceFile.getUri(), i);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -299,26 +290,26 @@ export class Program {
|
||||
}
|
||||
|
||||
// Sets the list of tracked files that make up the program.
|
||||
setTrackedFiles(filePaths: string[]): FileDiagnostics[] {
|
||||
setTrackedFiles(fileUris: Uri[]): FileDiagnostics[] {
|
||||
if (this._sourceFileList.length > 0) {
|
||||
// We need to determine which files to remove from the existing file list.
|
||||
const newFileMap = new Map<string, string>();
|
||||
filePaths.forEach((path) => {
|
||||
newFileMap.set(path, path);
|
||||
const newFileMap = new Map<string, Uri>();
|
||||
fileUris.forEach((path) => {
|
||||
newFileMap.set(path.key, path);
|
||||
});
|
||||
|
||||
// Files that are not in the tracked file list are
|
||||
// marked as no longer tracked.
|
||||
this._sourceFileList.forEach((oldFile) => {
|
||||
const filePath = oldFile.sourceFile.getFilePath();
|
||||
if (!newFileMap.has(filePath)) {
|
||||
const fileUri = oldFile.sourceFile.getUri();
|
||||
if (!newFileMap.has(fileUri.key)) {
|
||||
oldFile.isTracked = false;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Add the new files. Only the new items will be added.
|
||||
this.addTrackedFiles(filePaths);
|
||||
this.addTrackedFiles(fileUris);
|
||||
|
||||
return this._removeUnneededFiles();
|
||||
}
|
||||
@ -338,25 +329,25 @@ export class Program {
|
||||
this._allowedThirdPartyImports = importNames;
|
||||
}
|
||||
|
||||
addTrackedFiles(filePaths: string[], isThirdPartyImport = false, isInPyTypedPackage = false) {
|
||||
filePaths.forEach((filePath) => {
|
||||
this.addTrackedFile(filePath, isThirdPartyImport, isInPyTypedPackage);
|
||||
addTrackedFiles(fileUris: Uri[], isThirdPartyImport = false, isInPyTypedPackage = false) {
|
||||
fileUris.forEach((fileUri) => {
|
||||
this.addTrackedFile(fileUri, isThirdPartyImport, isInPyTypedPackage);
|
||||
});
|
||||
}
|
||||
|
||||
addInterimFile(filePath: string): SourceFileInfo {
|
||||
addInterimFile(fileUri: Uri): SourceFileInfo {
|
||||
// Double check not already there.
|
||||
let fileInfo = this.getSourceFileInfo(filePath);
|
||||
let fileInfo = this.getSourceFileInfo(fileUri);
|
||||
if (!fileInfo) {
|
||||
fileInfo = this._createInterimFileInfo(filePath);
|
||||
fileInfo = this._createInterimFileInfo(fileUri);
|
||||
this._addToSourceFileListAndMap(fileInfo);
|
||||
}
|
||||
return fileInfo;
|
||||
}
|
||||
|
||||
addTrackedFile(filePath: string, isThirdPartyImport = false, isInPyTypedPackage = false): SourceFile {
|
||||
let sourceFileInfo = this.getSourceFileInfo(filePath);
|
||||
const moduleImportInfo = this._getModuleImportInfoForFile(filePath);
|
||||
addTrackedFile(fileUri: Uri, isThirdPartyImport = false, isInPyTypedPackage = false): SourceFile {
|
||||
let sourceFileInfo = this.getSourceFileInfo(fileUri);
|
||||
const moduleImportInfo = this._getModuleImportInfoForFile(fileUri);
|
||||
const importName = moduleImportInfo.moduleName;
|
||||
|
||||
if (sourceFileInfo) {
|
||||
@ -369,7 +360,7 @@ export class Program {
|
||||
|
||||
const sourceFile = this._sourceFileFactory.createSourceFile(
|
||||
this.serviceProvider,
|
||||
filePath,
|
||||
fileUri,
|
||||
importName,
|
||||
isThirdPartyImport,
|
||||
isInPyTypedPackage,
|
||||
@ -391,23 +382,22 @@ export class Program {
|
||||
return sourceFile;
|
||||
}
|
||||
|
||||
setFileOpened(filePath: string, version: number | null, contents: string, options?: OpenFileOptions) {
|
||||
let sourceFileInfo = this.getSourceFileInfo(filePath);
|
||||
setFileOpened(fileUri: Uri, version: number | null, contents: string, options?: OpenFileOptions) {
|
||||
let sourceFileInfo = this.getSourceFileInfo(fileUri);
|
||||
if (!sourceFileInfo) {
|
||||
const moduleImportInfo = this._getModuleImportInfoForFile(filePath);
|
||||
const moduleImportInfo = this._getModuleImportInfoForFile(fileUri);
|
||||
const sourceFile = this._sourceFileFactory.createSourceFile(
|
||||
this.serviceProvider,
|
||||
filePath,
|
||||
fileUri,
|
||||
moduleImportInfo.moduleName,
|
||||
/* isThirdPartyImport */ false,
|
||||
moduleImportInfo.isThirdPartyPyTypedPresent,
|
||||
this._editModeTracker,
|
||||
this._console,
|
||||
this._logTracker,
|
||||
options?.realFilePath,
|
||||
options?.ipythonMode ?? IPythonMode.None
|
||||
);
|
||||
const chainedFilePath = options?.chainedFilePath;
|
||||
const chainedFilePath = options?.chainedFileUri;
|
||||
sourceFileInfo = new SourceFileInfo(
|
||||
sourceFile,
|
||||
/* isTypeshedFile */ false,
|
||||
@ -435,26 +425,26 @@ export class Program {
|
||||
sourceFileInfo.sourceFile.setClientVersion(version, contents);
|
||||
}
|
||||
|
||||
getChainedFilePath(filePath: string): string | undefined {
|
||||
const sourceFileInfo = this.getSourceFileInfo(filePath);
|
||||
return sourceFileInfo?.chainedSourceFile?.sourceFile.getFilePath();
|
||||
getChainedUri(fileUri: Uri): Uri | undefined {
|
||||
const sourceFileInfo = this.getSourceFileInfo(fileUri);
|
||||
return sourceFileInfo?.chainedSourceFile?.sourceFile.getUri();
|
||||
}
|
||||
|
||||
updateChainedFilePath(filePath: string, chainedFilePath: string | undefined) {
|
||||
const sourceFileInfo = this.getSourceFileInfo(filePath);
|
||||
updateChainedUri(fileUri: Uri, chainedFileUri: Uri | undefined) {
|
||||
const sourceFileInfo = this.getSourceFileInfo(fileUri);
|
||||
if (!sourceFileInfo) {
|
||||
return;
|
||||
}
|
||||
|
||||
sourceFileInfo.chainedSourceFile = chainedFilePath ? this.getSourceFileInfo(chainedFilePath) : undefined;
|
||||
sourceFileInfo.chainedSourceFile = chainedFileUri ? this.getSourceFileInfo(chainedFileUri) : undefined;
|
||||
sourceFileInfo.sourceFile.markDirty();
|
||||
this._markFileDirtyRecursive(sourceFileInfo, new Set<string>());
|
||||
|
||||
verifyNoCyclesInChainedFiles(this, sourceFileInfo);
|
||||
}
|
||||
|
||||
setFileClosed(filePath: string, isTracked?: boolean): FileDiagnostics[] {
|
||||
const sourceFileInfo = this.getSourceFileInfo(filePath);
|
||||
setFileClosed(fileUri: Uri, isTracked?: boolean): FileDiagnostics[] {
|
||||
const sourceFileInfo = this.getSourceFileInfo(fileUri);
|
||||
if (sourceFileInfo) {
|
||||
sourceFileInfo.isOpenByClient = false;
|
||||
sourceFileInfo.isTracked = isTracked ?? sourceFileInfo.isTracked;
|
||||
@ -493,12 +483,12 @@ export class Program {
|
||||
}
|
||||
}
|
||||
|
||||
markFilesDirty(filePaths: string[], evenIfContentsAreSame: boolean) {
|
||||
markFilesDirty(fileUris: Uri[], evenIfContentsAreSame: boolean) {
|
||||
const markDirtySet = new Set<string>();
|
||||
filePaths.forEach((filePath) => {
|
||||
const sourceFileInfo = this.getSourceFileInfo(filePath);
|
||||
fileUris.forEach((fileUri) => {
|
||||
const sourceFileInfo = this.getSourceFileInfo(fileUri);
|
||||
if (sourceFileInfo) {
|
||||
const fileName = getFileName(filePath);
|
||||
const fileName = fileUri.fileName;
|
||||
|
||||
// Handle builtins and __builtins__ specially. They are implicitly
|
||||
// included by all source files.
|
||||
@ -576,9 +566,9 @@ export class Program {
|
||||
return this._configOptions.functionSignatureDisplay;
|
||||
}
|
||||
|
||||
containsSourceFileIn(folder: string): boolean {
|
||||
for (const normalizedSourceFilePath of this._sourceFileMap.keys()) {
|
||||
if (normalizedSourceFilePath.startsWith(folder)) {
|
||||
containsSourceFileIn(folder: Uri): boolean {
|
||||
for (const normalizedSourceFilePath of this._sourceFileMap.values()) {
|
||||
if (normalizedSourceFilePath.sourceFile.getUri().startsWith(folder)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@ -586,19 +576,19 @@ export class Program {
|
||||
return false;
|
||||
}
|
||||
|
||||
owns(filePath: string) {
|
||||
const fileInfo = this.getSourceFileInfo(filePath);
|
||||
owns(uri: Uri) {
|
||||
const fileInfo = this.getSourceFileInfo(uri);
|
||||
if (fileInfo) {
|
||||
// If we already determined whether the file is tracked or not, don't do it again.
|
||||
// This will make sure we have consistent look at the state once it is loaded to the memory.
|
||||
return fileInfo.isTracked;
|
||||
}
|
||||
|
||||
return matchFileSpecs(this._configOptions, filePath);
|
||||
return matchFileSpecs(this._configOptions, uri);
|
||||
}
|
||||
|
||||
getSourceFile(filePath: string): SourceFile | undefined {
|
||||
const sourceFileInfo = this.getSourceFileInfo(filePath);
|
||||
getSourceFile(uri: Uri): SourceFile | undefined {
|
||||
const sourceFileInfo = this.getSourceFileInfo(uri);
|
||||
if (!sourceFileInfo) {
|
||||
return undefined;
|
||||
}
|
||||
@ -606,20 +596,23 @@ export class Program {
|
||||
return sourceFileInfo.sourceFile;
|
||||
}
|
||||
|
||||
getBoundSourceFile(filePath: string): SourceFile | undefined {
|
||||
return this.getBoundSourceFileInfo(filePath)?.sourceFile;
|
||||
getBoundSourceFile(uri: Uri): SourceFile | undefined {
|
||||
return this.getBoundSourceFileInfo(uri)?.sourceFile;
|
||||
}
|
||||
|
||||
getSourceFileInfoList(): readonly SourceFileInfo[] {
|
||||
return this._sourceFileList;
|
||||
}
|
||||
|
||||
getSourceFileInfo(filePath: string): SourceFileInfo | undefined {
|
||||
return this._sourceFileMap.get(filePath);
|
||||
getSourceFileInfo(uri: Uri): SourceFileInfo | undefined {
|
||||
if (!uri.isEmpty()) {
|
||||
return this._sourceFileMap.get(uri.key);
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
|
||||
getBoundSourceFileInfo(filePath: string, content?: string, force?: boolean): SourceFileInfo | undefined {
|
||||
const sourceFileInfo = this.getSourceFileInfo(filePath);
|
||||
getBoundSourceFileInfo(uri: Uri, content?: string, force?: boolean): SourceFileInfo | undefined {
|
||||
const sourceFileInfo = this.getSourceFileInfo(uri);
|
||||
if (!sourceFileInfo) {
|
||||
return undefined;
|
||||
}
|
||||
@ -685,9 +678,9 @@ export class Program {
|
||||
|
||||
// Performs parsing and analysis of a single file in the program. If the file is not part of
|
||||
// the program returns false to indicate analysis was not performed.
|
||||
analyzeFile(filePath: string, token: CancellationToken = CancellationToken.None): boolean {
|
||||
analyzeFile(fileUri: Uri, token: CancellationToken = CancellationToken.None): boolean {
|
||||
return this._runEvaluatorWithCancellationToken(token, () => {
|
||||
const sourceFileInfo = this.getSourceFileInfo(filePath);
|
||||
const sourceFileInfo = this.getSourceFileInfo(fileUri);
|
||||
if (sourceFileInfo && this._checkTypes(sourceFileInfo, token)) {
|
||||
return true;
|
||||
}
|
||||
@ -710,19 +703,19 @@ export class Program {
|
||||
}
|
||||
|
||||
getSourceMapper(
|
||||
filePath: string,
|
||||
fileUri: Uri,
|
||||
token: CancellationToken,
|
||||
mapCompiled?: boolean,
|
||||
preferStubs?: boolean
|
||||
): SourceMapper {
|
||||
const sourceFileInfo = this.getSourceFileInfo(filePath);
|
||||
const execEnv = this._configOptions.findExecEnvironment(filePath);
|
||||
const sourceFileInfo = this.getSourceFileInfo(fileUri);
|
||||
const execEnv = this._configOptions.findExecEnvironment(fileUri);
|
||||
return this._createSourceMapper(execEnv, token, sourceFileInfo, mapCompiled, preferStubs);
|
||||
}
|
||||
|
||||
getParseResults(filePath: string): ParseResults | undefined {
|
||||
getParseResults(fileUri: Uri): ParseResults | undefined {
|
||||
return this.getBoundSourceFileInfo(
|
||||
filePath,
|
||||
fileUri,
|
||||
/* content */ undefined,
|
||||
/* force */ true
|
||||
)?.sourceFile.getParseResults();
|
||||
@ -746,41 +739,39 @@ export class Program {
|
||||
|
||||
sortedFiles.forEach((sfInfo) => {
|
||||
const checkTimeInMs = sfInfo.sourceFile.getCheckTime()!;
|
||||
this._console.info(`${checkTimeInMs}ms: ${sfInfo.sourceFile.getFilePath()}`);
|
||||
this._console.info(`${checkTimeInMs}ms: ${sfInfo.sourceFile.getUri()}`);
|
||||
});
|
||||
}
|
||||
|
||||
// Prints import dependency information for each of the files in
|
||||
// the program, skipping any typeshed files.
|
||||
printDependencies(projectRootDir: string, verbose: boolean) {
|
||||
printDependencies(projectRootDir: Uri, verbose: boolean) {
|
||||
const fs = this._importResolver.fileSystem;
|
||||
const sortedFiles = this._sourceFileList
|
||||
.filter((s) => !s.isTypeshedFile)
|
||||
.sort((a, b) => {
|
||||
return fs.getOriginalFilePath(a.sourceFile.getFilePath()) <
|
||||
fs.getOriginalFilePath(b.sourceFile.getFilePath())
|
||||
? 1
|
||||
: -1;
|
||||
return fs.getOriginalUri(a.sourceFile.getUri()) < fs.getOriginalUri(b.sourceFile.getUri()) ? 1 : -1;
|
||||
});
|
||||
|
||||
const zeroImportFiles: SourceFile[] = [];
|
||||
|
||||
sortedFiles.forEach((sfInfo) => {
|
||||
this._console.info('');
|
||||
let filePath = fs.getOriginalFilePath(sfInfo.sourceFile.getFilePath());
|
||||
const relPath = getRelativePath(filePath, projectRootDir);
|
||||
const fileUri = fs.getOriginalUri(sfInfo.sourceFile.getUri());
|
||||
let fileString = fileUri.toString();
|
||||
const relPath = projectRootDir.getRelativePathComponents(fileUri);
|
||||
if (relPath) {
|
||||
filePath = relPath;
|
||||
fileString = relPath.join('/');
|
||||
}
|
||||
|
||||
this._console.info(`${filePath}`);
|
||||
this._console.info(`${fileString}`);
|
||||
|
||||
this._console.info(
|
||||
` Imports ${sfInfo.imports.length} ` + `file${sfInfo.imports.length === 1 ? '' : 's'}`
|
||||
);
|
||||
if (verbose) {
|
||||
sfInfo.imports.forEach((importInfo) => {
|
||||
this._console.info(` ${fs.getOriginalFilePath(importInfo.sourceFile.getFilePath())}`);
|
||||
this._console.info(` ${fs.getOriginalUri(importInfo.sourceFile.getUri())}`);
|
||||
});
|
||||
}
|
||||
|
||||
@ -789,7 +780,7 @@ export class Program {
|
||||
);
|
||||
if (verbose) {
|
||||
sfInfo.importedBy.forEach((importInfo) => {
|
||||
this._console.info(` ${fs.getOriginalFilePath(importInfo.sourceFile.getFilePath())}`);
|
||||
this._console.info(` ${fs.getOriginalUri(importInfo.sourceFile.getUri())}`);
|
||||
});
|
||||
}
|
||||
|
||||
@ -804,33 +795,33 @@ export class Program {
|
||||
`${zeroImportFiles.length} file${zeroImportFiles.length === 1 ? '' : 's'}` + ` not explicitly imported`
|
||||
);
|
||||
zeroImportFiles.forEach((importFile) => {
|
||||
this._console.info(` ${fs.getOriginalFilePath(importFile.getFilePath())}`);
|
||||
this._console.info(` ${fs.getOriginalUri(importFile.getUri())}`);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
writeTypeStub(targetImportPath: string, targetIsSingleFile: boolean, stubPath: string, token: CancellationToken) {
|
||||
writeTypeStub(targetImportPath: Uri, targetIsSingleFile: boolean, stubPath: Uri, token: CancellationToken) {
|
||||
for (const sourceFileInfo of this._sourceFileList) {
|
||||
throwIfCancellationRequested(token);
|
||||
|
||||
const filePath = sourceFileInfo.sourceFile.getFilePath();
|
||||
const fileUri = sourceFileInfo.sourceFile.getUri();
|
||||
|
||||
// Generate type stubs only for the files within the target path,
|
||||
// not any files that the target module happened to import.
|
||||
const relativePath = getRelativePath(filePath, targetImportPath);
|
||||
const relativePath = targetImportPath.getRelativePath(fileUri);
|
||||
if (relativePath !== undefined) {
|
||||
let typeStubPath = normalizePath(combinePaths(stubPath, relativePath));
|
||||
let typeStubPath = stubPath.combinePaths(relativePath);
|
||||
|
||||
// If the target is a single file implementation, as opposed to
|
||||
// a package in a directory, transform the name of the type stub
|
||||
// to __init__.pyi because we're placing it in a directory.
|
||||
if (targetIsSingleFile) {
|
||||
typeStubPath = combinePaths(getDirectoryPath(typeStubPath), '__init__.pyi');
|
||||
typeStubPath = typeStubPath.getDirectory().combinePaths('__init__.pyi');
|
||||
} else {
|
||||
typeStubPath = stripFileExtension(typeStubPath) + '.pyi';
|
||||
typeStubPath = typeStubPath.replaceExtension('.pyi');
|
||||
}
|
||||
|
||||
const typeStubDir = getDirectoryPath(typeStubPath);
|
||||
const typeStubDir = typeStubPath.getDirectory();
|
||||
|
||||
try {
|
||||
makeDirectories(this.fileSystem, typeStubDir, stubPath);
|
||||
@ -867,8 +858,8 @@ export class Program {
|
||||
return evaluator.printType(type, options);
|
||||
}
|
||||
|
||||
getTextOnRange(filePath: string, range: Range, token: CancellationToken): string | undefined {
|
||||
const sourceFileInfo = this.getSourceFileInfo(filePath);
|
||||
getTextOnRange(fileUri: Uri, range: Range, token: CancellationToken): string | undefined {
|
||||
const sourceFileInfo = this.getSourceFileInfo(fileUri);
|
||||
if (!sourceFileInfo) {
|
||||
return undefined;
|
||||
}
|
||||
@ -904,7 +895,7 @@ export class Program {
|
||||
);
|
||||
if (diagnostics !== undefined) {
|
||||
fileDiagnostics.push({
|
||||
filePath: sourceFileInfo.sourceFile.getFilePath(),
|
||||
fileUri: sourceFileInfo.sourceFile.getUri(),
|
||||
version: sourceFileInfo.sourceFile.getClientVersion(),
|
||||
diagnostics,
|
||||
});
|
||||
@ -921,7 +912,7 @@ export class Program {
|
||||
// This condition occurs when the user switches from workspace to
|
||||
// "open files only" mode. Clear all diagnostics for this file.
|
||||
fileDiagnostics.push({
|
||||
filePath: sourceFileInfo.sourceFile.getFilePath(),
|
||||
fileUri: sourceFileInfo.sourceFile.getUri(),
|
||||
version: sourceFileInfo.sourceFile.getClientVersion(),
|
||||
diagnostics: [],
|
||||
});
|
||||
@ -932,8 +923,8 @@ export class Program {
|
||||
return fileDiagnostics;
|
||||
}
|
||||
|
||||
getDiagnosticsForRange(filePath: string, range: Range): Diagnostic[] {
|
||||
const sourceFile = this.getSourceFile(filePath);
|
||||
getDiagnosticsForRange(fileUri: Uri, range: Range): Diagnostic[] {
|
||||
const sourceFile = this.getSourceFile(fileUri);
|
||||
if (!sourceFile) {
|
||||
return [];
|
||||
}
|
||||
@ -958,7 +949,7 @@ export class Program {
|
||||
|
||||
// Cloned program will use whatever user files the program currently has.
|
||||
const userFiles = this.getUserFiles();
|
||||
program.setTrackedFiles(userFiles.map((i) => i.sourceFile.getFilePath()));
|
||||
program.setTrackedFiles(userFiles.map((i) => i.sourceFile.getUri()));
|
||||
program.markAllFilesDirty(/* evenIfContentsAreSame */ true);
|
||||
|
||||
// Make sure we keep editor content (open file) which could be different than one in the file system.
|
||||
@ -969,14 +960,13 @@ export class Program {
|
||||
}
|
||||
|
||||
program.setFileOpened(
|
||||
fileInfo.sourceFile.getFilePath(),
|
||||
fileInfo.sourceFile.getUri(),
|
||||
version,
|
||||
fileInfo.sourceFile.getOpenFileContents() ?? '',
|
||||
{
|
||||
chainedFilePath: fileInfo.chainedSourceFile?.sourceFile.getFilePath(),
|
||||
chainedFileUri: fileInfo.chainedSourceFile?.sourceFile.getUri(),
|
||||
ipythonMode: fileInfo.sourceFile.getIPythonMode(),
|
||||
isTracked: fileInfo.isTracked,
|
||||
realFilePath: fileInfo.sourceFile.getRealFilePath(),
|
||||
}
|
||||
);
|
||||
}
|
||||
@ -1071,14 +1061,14 @@ export class Program {
|
||||
// Clear only if there are any errors for this file.
|
||||
if (fileInfo.diagnosticsVersion !== undefined) {
|
||||
fileDiagnostics.push({
|
||||
filePath: fileInfo.sourceFile.getFilePath(),
|
||||
fileUri: fileInfo.sourceFile.getUri(),
|
||||
version: fileInfo.sourceFile.getClientVersion(),
|
||||
diagnostics: [],
|
||||
});
|
||||
}
|
||||
|
||||
fileInfo.sourceFile.prepareForClose();
|
||||
this._removeSourceFileFromListAndMap(fileInfo.sourceFile.getFilePath(), i);
|
||||
this._removeSourceFileFromListAndMap(fileInfo.sourceFile.getUri(), i);
|
||||
|
||||
// Unlink any imports and remove them from the list if
|
||||
// they are no longer referenced.
|
||||
@ -1099,14 +1089,14 @@ export class Program {
|
||||
// Clear if there are any errors for this import.
|
||||
if (importedFile.diagnosticsVersion !== undefined) {
|
||||
fileDiagnostics.push({
|
||||
filePath: importedFile.sourceFile.getFilePath(),
|
||||
fileUri: importedFile.sourceFile.getUri(),
|
||||
version: importedFile.sourceFile.getClientVersion(),
|
||||
diagnostics: [],
|
||||
});
|
||||
}
|
||||
|
||||
importedFile.sourceFile.prepareForClose();
|
||||
this._removeSourceFileFromListAndMap(importedFile.sourceFile.getFilePath(), indexToRemove);
|
||||
this._removeSourceFileFromListAndMap(importedFile.sourceFile.getUri(), indexToRemove);
|
||||
i--;
|
||||
}
|
||||
}
|
||||
@ -1122,7 +1112,7 @@ export class Program {
|
||||
// out the errors for the now-closed file.
|
||||
if (!this._shouldCheckFile(fileInfo) && fileInfo.diagnosticsVersion !== undefined) {
|
||||
fileDiagnostics.push({
|
||||
filePath: fileInfo.sourceFile.getFilePath(),
|
||||
fileUri: fileInfo.sourceFile.getUri(),
|
||||
version: fileInfo.sourceFile.getClientVersion(),
|
||||
diagnostics: [],
|
||||
});
|
||||
@ -1165,14 +1155,14 @@ export class Program {
|
||||
return true;
|
||||
}
|
||||
|
||||
const filePath = fileInfo.sourceFile.getFilePath();
|
||||
const fileUri = fileInfo.sourceFile.getUri();
|
||||
|
||||
// Avoid infinite recursion.
|
||||
if (recursionSet.has(filePath)) {
|
||||
if (recursionSet.has(fileUri.key)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
recursionSet.add(filePath);
|
||||
recursionSet.add(fileUri.key);
|
||||
|
||||
for (const importerInfo of fileInfo.importedBy) {
|
||||
if (this._isImportNeededRecursive(importerInfo, recursionSet)) {
|
||||
@ -1194,16 +1184,16 @@ export class Program {
|
||||
this._importResolver,
|
||||
execEnv,
|
||||
this._evaluator!,
|
||||
(stubFilePath: string, implFilePath: string) => {
|
||||
let stubFileInfo = this.getSourceFileInfo(stubFilePath);
|
||||
(stubFileUri: Uri, implFileUri: Uri) => {
|
||||
let stubFileInfo = this.getSourceFileInfo(stubFileUri);
|
||||
if (!stubFileInfo) {
|
||||
// Special case for import statement like "import X.Y". The SourceFile
|
||||
// for X might not be in memory since import `X.Y` only brings in Y.
|
||||
stubFileInfo = this.addInterimFile(stubFilePath);
|
||||
stubFileInfo = this.addInterimFile(stubFileUri);
|
||||
}
|
||||
|
||||
this._addShadowedFile(stubFileInfo, implFilePath);
|
||||
return this.getBoundSourceFile(implFilePath);
|
||||
this._addShadowedFile(stubFileInfo, implFileUri);
|
||||
return this.getBoundSourceFile(implFileUri);
|
||||
},
|
||||
(f) => {
|
||||
let fileInfo = this.getBoundSourceFileInfo(f);
|
||||
@ -1296,6 +1286,10 @@ export class Program {
|
||||
return true;
|
||||
}
|
||||
|
||||
private _getSourceFileInfoFromKey(key: string) {
|
||||
return this._sourceFileMap.get(key);
|
||||
}
|
||||
|
||||
private _updateSourceFileImports(sourceFileInfo: SourceFileInfo, options: ConfigOptions): SourceFileInfo[] {
|
||||
const filesAdded: SourceFileInfo[] = [];
|
||||
|
||||
@ -1338,9 +1332,9 @@ export class Program {
|
||||
if (sourceFileInfo.chainedSourceFile.sourceFile.isFileDeleted()) {
|
||||
sourceFileInfo.chainedSourceFile = undefined;
|
||||
} else {
|
||||
const filePath = sourceFileInfo.chainedSourceFile.sourceFile.getFilePath();
|
||||
newImportPathMap.set(filePath, {
|
||||
path: filePath,
|
||||
const fileUri = sourceFileInfo.chainedSourceFile.sourceFile.getUri();
|
||||
newImportPathMap.set(fileUri.key, {
|
||||
path: fileUri,
|
||||
isTypeshedFile: false,
|
||||
isThirdPartyImport: false,
|
||||
isPyTypedPresent: false,
|
||||
@ -1351,12 +1345,12 @@ export class Program {
|
||||
imports.forEach((importResult) => {
|
||||
if (importResult.isImportFound) {
|
||||
if (this._isImportAllowed(sourceFileInfo, importResult, importResult.isStubFile)) {
|
||||
if (importResult.resolvedPaths.length > 0) {
|
||||
const filePath = importResult.resolvedPaths[importResult.resolvedPaths.length - 1];
|
||||
if (filePath) {
|
||||
if (importResult.resolvedUris.length > 0) {
|
||||
const fileUri = importResult.resolvedUris[importResult.resolvedUris.length - 1];
|
||||
if (!fileUri.isEmpty()) {
|
||||
const thirdPartyTypeInfo = getThirdPartyImportInfo(importResult);
|
||||
newImportPathMap.set(filePath, {
|
||||
path: filePath,
|
||||
newImportPathMap.set(fileUri.key, {
|
||||
path: fileUri,
|
||||
isTypeshedFile:
|
||||
!!importResult.isStdlibTypeshedFile || !!importResult.isThirdPartyTypeshedFile,
|
||||
isThirdPartyImport: thirdPartyTypeInfo.isThirdPartyImport,
|
||||
@ -1370,8 +1364,8 @@ export class Program {
|
||||
if (this._isImportAllowed(sourceFileInfo, importResult, implicitImport.isStubFile)) {
|
||||
if (!implicitImport.isNativeLib) {
|
||||
const thirdPartyTypeInfo = getThirdPartyImportInfo(importResult);
|
||||
newImportPathMap.set(implicitImport.path, {
|
||||
path: implicitImport.path,
|
||||
newImportPathMap.set(implicitImport.uri.key, {
|
||||
path: implicitImport.uri,
|
||||
isTypeshedFile:
|
||||
!!importResult.isStdlibTypeshedFile || !!importResult.isThirdPartyTypeshedFile,
|
||||
isThirdPartyImport: thirdPartyTypeInfo.isThirdPartyImport,
|
||||
@ -1391,7 +1385,7 @@ export class Program {
|
||||
if (options.verboseOutput) {
|
||||
this._console.info(
|
||||
`Could not resolve source for '${importResult.importName}' ` +
|
||||
`in file '${sourceFileInfo.sourceFile.getFilePath()}'`
|
||||
`in file '${sourceFileInfo.sourceFile.getUri().toUserVisibleString()}'`
|
||||
);
|
||||
|
||||
if (importResult.nonStubImportResult.importFailureInfo) {
|
||||
@ -1405,7 +1399,7 @@ export class Program {
|
||||
} else if (options.verboseOutput) {
|
||||
this._console.info(
|
||||
`Could not import '${importResult.importName}' ` +
|
||||
`in file '${sourceFileInfo.sourceFile.getFilePath()}'`
|
||||
`in file '${sourceFileInfo.sourceFile.getUri().toUserVisibleString()}'`
|
||||
);
|
||||
if (importResult.importFailureInfo) {
|
||||
importResult.importFailureInfo.forEach((diag) => {
|
||||
@ -1417,17 +1411,17 @@ export class Program {
|
||||
|
||||
const updatedImportMap = new Map<string, SourceFileInfo>();
|
||||
sourceFileInfo.imports.forEach((importInfo) => {
|
||||
const oldFilePath = importInfo.sourceFile.getFilePath();
|
||||
const oldFilePath = importInfo.sourceFile.getUri();
|
||||
|
||||
// A previous import was removed.
|
||||
if (!newImportPathMap.has(oldFilePath)) {
|
||||
if (!newImportPathMap.has(oldFilePath.key)) {
|
||||
importInfo.mutate((s) => {
|
||||
s.importedBy = s.importedBy.filter(
|
||||
(fi) => fi.sourceFile.getFilePath() !== sourceFileInfo.sourceFile.getFilePath()
|
||||
(fi) => !fi.sourceFile.getUri().equals(sourceFileInfo.sourceFile.getUri())
|
||||
);
|
||||
});
|
||||
} else {
|
||||
updatedImportMap.set(oldFilePath, importInfo);
|
||||
updatedImportMap.set(oldFilePath.key, importInfo);
|
||||
}
|
||||
});
|
||||
|
||||
@ -1469,9 +1463,9 @@ export class Program {
|
||||
// Update the imports list. It should now map the set of imports
|
||||
// specified by the source file.
|
||||
sourceFileInfo.mutate((s) => (s.imports = []));
|
||||
newImportPathMap.forEach((_, path) => {
|
||||
if (this.getSourceFileInfo(path)) {
|
||||
sourceFileInfo.mutate((s) => s.imports.push(this.getSourceFileInfo(path)!));
|
||||
newImportPathMap.forEach((_, key) => {
|
||||
if (this._getSourceFileInfoFromKey(key)) {
|
||||
sourceFileInfo.mutate((s) => s.imports.push(this._getSourceFileInfoFromKey(key)!));
|
||||
}
|
||||
});
|
||||
|
||||
@ -1480,26 +1474,29 @@ export class Program {
|
||||
sourceFileInfo.builtinsImport = undefined;
|
||||
const builtinsImport = sourceFileInfo.sourceFile.getBuiltinsImport();
|
||||
if (builtinsImport && builtinsImport.isImportFound) {
|
||||
const resolvedBuiltinsPath = builtinsImport.resolvedPaths[builtinsImport.resolvedPaths.length - 1];
|
||||
const resolvedBuiltinsPath = builtinsImport.resolvedUris[builtinsImport.resolvedUris.length - 1];
|
||||
sourceFileInfo.builtinsImport = this.getSourceFileInfo(resolvedBuiltinsPath);
|
||||
}
|
||||
|
||||
return filesAdded;
|
||||
}
|
||||
|
||||
private _removeSourceFileFromListAndMap(filePath: string, indexToRemove: number) {
|
||||
this._sourceFileMap.delete(filePath);
|
||||
private _removeSourceFileFromListAndMap(fileUri: Uri, indexToRemove: number) {
|
||||
this._sourceFileMap.delete(fileUri.key);
|
||||
this._sourceFileList.splice(indexToRemove, 1);
|
||||
}
|
||||
|
||||
private _addToSourceFileListAndMap(fileInfo: SourceFileInfo) {
|
||||
const filePath = fileInfo.sourceFile.getFilePath();
|
||||
const fileUri = fileInfo.sourceFile.getUri();
|
||||
|
||||
// We should never add a file with the same path twice.
|
||||
assert(!this._sourceFileMap.has(filePath));
|
||||
assert(!this._sourceFileMap.has(fileUri.key));
|
||||
|
||||
// We should never have an empty URI for a source file.
|
||||
assert(!fileInfo.sourceFile.getUri().isEmpty());
|
||||
|
||||
this._sourceFileList.push(fileInfo);
|
||||
this._sourceFileMap.set(filePath, fileInfo);
|
||||
this._sourceFileMap.set(fileUri.key, fileInfo);
|
||||
}
|
||||
|
||||
private static _getPrintTypeFlags(configOptions: ConfigOptions): PrintTypeFlags {
|
||||
@ -1528,7 +1525,7 @@ export class Program {
|
||||
return flags;
|
||||
}
|
||||
|
||||
private _getModuleImportInfoForFile(filePath: string) {
|
||||
private _getModuleImportInfoForFile(fileUri: Uri) {
|
||||
// We allow illegal module names (e.g. names that include "-" in them)
|
||||
// because we want a unique name for each module even if it cannot be
|
||||
// imported through an "import" statement. It's important to have a
|
||||
@ -1536,7 +1533,7 @@ export class Program {
|
||||
// name. The type checker uses the fully-qualified (unique) module name
|
||||
// to differentiate between such types.
|
||||
const moduleNameAndType = this._importResolver.getModuleNameForImport(
|
||||
filePath,
|
||||
fileUri,
|
||||
this._configOptions.getDefaultExecEnvironment(),
|
||||
/* allowIllegalModuleName */ true,
|
||||
/* detectPyTyped */ true
|
||||
@ -1549,7 +1546,7 @@ export class Program {
|
||||
// it "shadows" a type stub file for purposes of finding doc strings and definitions.
|
||||
// We need to track the relationship so if the original type stub is removed from the
|
||||
// program, we can remove the corresponding shadowed file and any files it imports.
|
||||
private _addShadowedFile(stubFile: SourceFileInfo, shadowImplPath: string): SourceFile {
|
||||
private _addShadowedFile(stubFile: SourceFileInfo, shadowImplPath: Uri): SourceFile {
|
||||
let shadowFileInfo = this.getSourceFileInfo(shadowImplPath);
|
||||
|
||||
if (!shadowFileInfo) {
|
||||
@ -1567,11 +1564,11 @@ export class Program {
|
||||
return shadowFileInfo.sourceFile;
|
||||
}
|
||||
|
||||
private _createInterimFileInfo(filePath: string) {
|
||||
const moduleImportInfo = this._getModuleImportInfoForFile(filePath);
|
||||
private _createInterimFileInfo(fileUri: Uri) {
|
||||
const moduleImportInfo = this._getModuleImportInfoForFile(fileUri);
|
||||
const sourceFile = this._sourceFileFactory.createSourceFile(
|
||||
this.serviceProvider,
|
||||
filePath,
|
||||
fileUri,
|
||||
moduleImportInfo.moduleName,
|
||||
/* isThirdPartyImport */ false,
|
||||
/* isInPyTypedPackage */ false,
|
||||
@ -1673,8 +1670,8 @@ export class Program {
|
||||
|
||||
let nextImplicitImport = this._getImplicitImports(fileToAnalyze);
|
||||
while (nextImplicitImport) {
|
||||
const implicitPath = nextImplicitImport.sourceFile.getFilePath();
|
||||
if (implicitSet.has(implicitPath)) {
|
||||
const implicitPath = nextImplicitImport.sourceFile.getUri();
|
||||
if (implicitSet.has(implicitPath.key)) {
|
||||
// We've found a cycle. Break out of the loop.
|
||||
debug.fail(
|
||||
this.serviceProvider
|
||||
@ -1683,7 +1680,7 @@ export class Program {
|
||||
);
|
||||
}
|
||||
|
||||
implicitSet.add(implicitPath);
|
||||
implicitSet.add(implicitPath.key);
|
||||
implicitImports.push(nextImplicitImport);
|
||||
|
||||
this._parseFile(nextImplicitImport, /* content */ undefined, skipFileNeededCheck);
|
||||
@ -1773,27 +1770,27 @@ export class Program {
|
||||
}
|
||||
|
||||
private _lookUpImport = (
|
||||
filePathOrModule: string | AbsoluteModuleDescriptor,
|
||||
fileUriOrModule: Uri | AbsoluteModuleDescriptor,
|
||||
options?: LookupImportOptions
|
||||
): ImportLookupResult | undefined => {
|
||||
let sourceFileInfo: SourceFileInfo | undefined;
|
||||
|
||||
if (typeof filePathOrModule === 'string') {
|
||||
sourceFileInfo = this.getSourceFileInfo(filePathOrModule);
|
||||
if (Uri.isUri(fileUriOrModule)) {
|
||||
sourceFileInfo = this.getSourceFileInfo(fileUriOrModule);
|
||||
} else {
|
||||
// Resolve the import.
|
||||
const importResult = this._importResolver.resolveImport(
|
||||
filePathOrModule.importingFilePath,
|
||||
this._configOptions.findExecEnvironment(filePathOrModule.importingFilePath),
|
||||
fileUriOrModule.importingFileUri,
|
||||
this._configOptions.findExecEnvironment(fileUriOrModule.importingFileUri),
|
||||
{
|
||||
leadingDots: 0,
|
||||
nameParts: filePathOrModule.nameParts,
|
||||
nameParts: fileUriOrModule.nameParts,
|
||||
importedSymbols: undefined,
|
||||
}
|
||||
);
|
||||
|
||||
if (importResult.isImportFound && !importResult.isNativeLib && importResult.resolvedPaths.length > 0) {
|
||||
const resolvedPath = importResult.resolvedPaths[importResult.resolvedPaths.length - 1];
|
||||
if (importResult.isImportFound && !importResult.isNativeLib && importResult.resolvedUris.length > 0) {
|
||||
const resolvedPath = importResult.resolvedUris[importResult.resolvedUris.length - 1];
|
||||
if (resolvedPath) {
|
||||
// See if the source file already exists in the program.
|
||||
sourceFileInfo = this.getSourceFileInfo(resolvedPath);
|
||||
@ -1872,7 +1869,7 @@ export class Program {
|
||||
}
|
||||
|
||||
private _checkTypes(fileToCheck: SourceFileInfo, token: CancellationToken, chainedByList?: SourceFileInfo[]) {
|
||||
return this._logTracker.log(`analyzing: ${fileToCheck.sourceFile.getFilePath()}`, (logState) => {
|
||||
return this._logTracker.log(`analyzing: ${fileToCheck.sourceFile.getUri()}`, (logState) => {
|
||||
// If the file isn't needed because it was eliminated from the
|
||||
// transitive closure or deleted, skip the file rather than wasting
|
||||
// time on it.
|
||||
@ -1911,7 +1908,7 @@ export class Program {
|
||||
}
|
||||
|
||||
if (boundFile) {
|
||||
const execEnv = this._configOptions.findExecEnvironment(fileToCheck.sourceFile.getFilePath());
|
||||
const execEnv = this._configOptions.findExecEnvironment(fileToCheck.sourceFile.getUri());
|
||||
fileToCheck.sourceFile.check(
|
||||
this.configOptions,
|
||||
this._importResolver,
|
||||
@ -2028,8 +2025,8 @@ export class Program {
|
||||
) {
|
||||
// If the file is already in the closure map, we found a cyclical
|
||||
// dependency. Don't recur further.
|
||||
const filePath = file.sourceFile.getFilePath();
|
||||
if (closureMap.has(filePath)) {
|
||||
const fileUri = file.sourceFile.getUri();
|
||||
if (closureMap.has(fileUri.key)) {
|
||||
return;
|
||||
}
|
||||
|
||||
@ -2041,7 +2038,7 @@ export class Program {
|
||||
}
|
||||
|
||||
// Add the file to the closure map.
|
||||
closureMap.set(filePath, file);
|
||||
closureMap.set(fileUri.key, file);
|
||||
|
||||
// If this file hasn't already been parsed, parse it now. This will
|
||||
// discover any files it imports. Skip this if the file is part
|
||||
@ -2074,13 +2071,13 @@ export class Program {
|
||||
return false;
|
||||
}
|
||||
|
||||
const filePath = sourceFileInfo.sourceFile.getFilePath();
|
||||
const fileUri = sourceFileInfo.sourceFile.getUri();
|
||||
|
||||
filesVisited.set(filePath, sourceFileInfo);
|
||||
filesVisited.set(fileUri.key, sourceFileInfo);
|
||||
|
||||
let detectedCycle = false;
|
||||
|
||||
if (dependencyMap.has(filePath)) {
|
||||
if (dependencyMap.has(fileUri.key)) {
|
||||
// We detect a cycle (partial or full). A full cycle is one that is
|
||||
// rooted in the file at the start of our dependency chain. A partial
|
||||
// cycle loops back on some other file in the dependency chain. We
|
||||
@ -2097,7 +2094,7 @@ export class Program {
|
||||
} else {
|
||||
// If we've already checked this dependency along
|
||||
// some other path, we can skip it.
|
||||
if (dependencyMap.has(filePath)) {
|
||||
if (dependencyMap.has(fileUri.key)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@ -2105,7 +2102,7 @@ export class Program {
|
||||
// (for ordering information). Set the dependency map
|
||||
// entry to true to indicate that we're actively exploring
|
||||
// that dependency.
|
||||
dependencyMap.set(filePath, true);
|
||||
dependencyMap.set(fileUri.key, true);
|
||||
dependencyChain.push(sourceFileInfo);
|
||||
|
||||
for (const imp of sourceFileInfo.imports) {
|
||||
@ -2116,7 +2113,7 @@ export class Program {
|
||||
|
||||
// Set the dependencyMap entry to false to indicate that we have
|
||||
// already explored this file and don't need to explore it again.
|
||||
dependencyMap.set(filePath, false);
|
||||
dependencyMap.set(fileUri.key, false);
|
||||
dependencyChain.pop();
|
||||
}
|
||||
|
||||
@ -2126,7 +2123,7 @@ export class Program {
|
||||
private _logImportCycle(dependencyChain: SourceFileInfo[]) {
|
||||
const circDep = new CircularDependency();
|
||||
dependencyChain.forEach((sourceFileInfo) => {
|
||||
circDep.appendPath(sourceFileInfo.sourceFile.getFilePath());
|
||||
circDep.appendPath(sourceFileInfo.sourceFile.getUri());
|
||||
});
|
||||
|
||||
circDep.normalizeOrder();
|
||||
@ -2137,15 +2134,15 @@ export class Program {
|
||||
}
|
||||
|
||||
private _markFileDirtyRecursive(sourceFileInfo: SourceFileInfo, markSet: Set<string>, forceRebinding = false) {
|
||||
const filePath = sourceFileInfo.sourceFile.getFilePath();
|
||||
const fileUri = sourceFileInfo.sourceFile.getUri();
|
||||
|
||||
// Don't mark it again if it's already been visited.
|
||||
if (markSet.has(filePath)) {
|
||||
if (markSet.has(fileUri.key)) {
|
||||
return;
|
||||
}
|
||||
|
||||
sourceFileInfo.sourceFile.markReanalysisRequired(forceRebinding);
|
||||
markSet.add(filePath);
|
||||
markSet.add(fileUri.key);
|
||||
|
||||
sourceFileInfo.importedBy.forEach((dep) => {
|
||||
// Changes on chained source file can change symbols in the symbol table and
|
||||
|
@ -70,7 +70,7 @@ export function createProperty(
|
||||
decoratorType.details.name,
|
||||
getClassFullName(decoratorNode, fileInfo.moduleName, `__property_${fget.details.name}`),
|
||||
fileInfo.moduleName,
|
||||
fileInfo.filePath,
|
||||
fileInfo.fileUri,
|
||||
ClassTypeFlags.PropertyClass | ClassTypeFlags.BuiltInClass,
|
||||
typeSourceId,
|
||||
/* declaredMetaclass */ undefined,
|
||||
@ -171,7 +171,7 @@ export function clonePropertyWithSetter(
|
||||
classType.details.name,
|
||||
classType.details.fullName,
|
||||
classType.details.moduleName,
|
||||
getFileInfo(errorNode).filePath,
|
||||
getFileInfo(errorNode).fileUri,
|
||||
flagsToClone,
|
||||
classType.details.typeSourceId,
|
||||
classType.details.declaredMetaclass,
|
||||
@ -228,7 +228,7 @@ export function clonePropertyWithDeleter(
|
||||
classType.details.name,
|
||||
classType.details.fullName,
|
||||
classType.details.moduleName,
|
||||
getFileInfo(errorNode).filePath,
|
||||
getFileInfo(errorNode).fileUri,
|
||||
classType.details.flags,
|
||||
classType.details.typeSourceId,
|
||||
classType.details.declaredMetaclass,
|
||||
|
@ -8,24 +8,23 @@
|
||||
*/
|
||||
|
||||
import { FileSystem } from '../common/fileSystem';
|
||||
import { combinePaths, isDirectory, isFile } from '../common/pathUtils';
|
||||
import { Uri } from '../common/uri/uri';
|
||||
import { isDirectory, isFile } from '../common/uri/uriUtils';
|
||||
|
||||
export interface PyTypedInfo {
|
||||
pyTypedPath: string;
|
||||
pyTypedPath: Uri;
|
||||
isPartiallyTyped: boolean;
|
||||
}
|
||||
|
||||
const _pyTypedFileName = 'py.typed';
|
||||
|
||||
export function getPyTypedInfo(fileSystem: FileSystem, dirPath: string): PyTypedInfo | undefined {
|
||||
export function getPyTypedInfo(fileSystem: FileSystem, dirPath: Uri): PyTypedInfo | undefined {
|
||||
if (!fileSystem.existsSync(dirPath) || !isDirectory(fileSystem, dirPath)) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
let isPartiallyTyped = false;
|
||||
const pyTypedPath = combinePaths(dirPath, _pyTypedFileName);
|
||||
const pyTypedPath = dirPath.pytypedUri;
|
||||
|
||||
if (!fileSystem.existsSync(dirPath) || !isFile(fileSystem, pyTypedPath)) {
|
||||
if (!fileSystem.existsSync(pyTypedPath) || !isFile(fileSystem, pyTypedPath)) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
|
@ -12,43 +12,32 @@ import { compareComparableValues } from '../common/core';
|
||||
import { FileSystem } from '../common/fileSystem';
|
||||
import { Host } from '../common/host';
|
||||
import * as pathConsts from '../common/pathConsts';
|
||||
import {
|
||||
combinePaths,
|
||||
containsPath,
|
||||
ensureTrailingDirectorySeparator,
|
||||
getDirectoryPath,
|
||||
getFileSystemEntries,
|
||||
isDirectory,
|
||||
normalizePath,
|
||||
tryStat,
|
||||
} from '../common/pathUtils';
|
||||
import { versionToString } from '../common/pythonVersion';
|
||||
import { PythonVersion } from '../common/pythonVersion';
|
||||
import { PythonVersion, versionToString } from '../common/pythonVersion';
|
||||
import { Uri } from '../common/uri/uri';
|
||||
import { getFileSystemEntries, isDirectory, tryStat } from '../common/uri/uriUtils';
|
||||
|
||||
export interface PythonPathResult {
|
||||
paths: string[];
|
||||
prefix: string;
|
||||
paths: Uri[];
|
||||
prefix: Uri | undefined;
|
||||
}
|
||||
|
||||
export const stdLibFolderName = 'stdlib';
|
||||
export const thirdPartyFolderName = 'stubs';
|
||||
|
||||
export function getTypeShedFallbackPath(fs: FileSystem) {
|
||||
let moduleDirectory = fs.getModulePath();
|
||||
if (!moduleDirectory) {
|
||||
const moduleDirectory = fs.getModulePath();
|
||||
if (!moduleDirectory || moduleDirectory.isEmpty()) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
moduleDirectory = getDirectoryPath(ensureTrailingDirectorySeparator(normalizePath(moduleDirectory)));
|
||||
|
||||
const typeshedPath = combinePaths(moduleDirectory, pathConsts.typeshedFallback);
|
||||
const typeshedPath = moduleDirectory.combinePaths(pathConsts.typeshedFallback);
|
||||
if (fs.existsSync(typeshedPath)) {
|
||||
return fs.realCasePath(typeshedPath);
|
||||
}
|
||||
|
||||
// In the debug version of Pyright, the code is one level
|
||||
// deeper, so we need to look one level up for the typeshed fallback.
|
||||
const debugTypeshedPath = combinePaths(getDirectoryPath(moduleDirectory), pathConsts.typeshedFallback);
|
||||
const debugTypeshedPath = moduleDirectory.getDirectory().combinePaths(pathConsts.typeshedFallback);
|
||||
if (fs.existsSync(debugTypeshedPath)) {
|
||||
return fs.realCasePath(debugTypeshedPath);
|
||||
}
|
||||
@ -56,8 +45,8 @@ export function getTypeShedFallbackPath(fs: FileSystem) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
export function getTypeshedSubdirectory(typeshedPath: string, isStdLib: boolean) {
|
||||
return combinePaths(typeshedPath, isStdLib ? stdLibFolderName : thirdPartyFolderName);
|
||||
export function getTypeshedSubdirectory(typeshedPath: Uri, isStdLib: boolean) {
|
||||
return typeshedPath.combinePaths(isStdLib ? stdLibFolderName : thirdPartyFolderName);
|
||||
}
|
||||
|
||||
export function findPythonSearchPaths(
|
||||
@ -66,21 +55,21 @@ export function findPythonSearchPaths(
|
||||
host: Host,
|
||||
importFailureInfo: string[],
|
||||
includeWatchPathsOnly?: boolean | undefined,
|
||||
workspaceRoot?: string | undefined
|
||||
): string[] | undefined {
|
||||
workspaceRoot?: Uri | undefined
|
||||
): Uri[] | undefined {
|
||||
importFailureInfo.push('Finding python search paths');
|
||||
|
||||
if (configOptions.venvPath !== undefined && configOptions.venv) {
|
||||
const venvDir = configOptions.venv;
|
||||
const venvPath = combinePaths(configOptions.venvPath, venvDir);
|
||||
const venvPath = configOptions.venvPath.combinePaths(venvDir);
|
||||
|
||||
const foundPaths: string[] = [];
|
||||
const sitePackagesPaths: string[] = [];
|
||||
const foundPaths: Uri[] = [];
|
||||
const sitePackagesPaths: Uri[] = [];
|
||||
|
||||
[pathConsts.lib, pathConsts.lib64, pathConsts.libAlternate].forEach((libPath) => {
|
||||
const sitePackagesPath = findSitePackagesPath(
|
||||
fs,
|
||||
combinePaths(venvPath, libPath),
|
||||
venvPath.combinePaths(libPath),
|
||||
configOptions.defaultPythonVersion,
|
||||
importFailureInfo
|
||||
);
|
||||
@ -115,11 +104,7 @@ export function findPythonSearchPaths(
|
||||
const pathResult = host.getPythonSearchPaths(configOptions.pythonPath, importFailureInfo);
|
||||
if (includeWatchPathsOnly && workspaceRoot) {
|
||||
const paths = pathResult.paths
|
||||
.filter(
|
||||
(p) =>
|
||||
!containsPath(workspaceRoot, p, /* ignoreCase */ true) ||
|
||||
containsPath(pathResult.prefix, p, /* ignoreCase */ true)
|
||||
)
|
||||
.filter((p) => !p.startsWith(workspaceRoot) || p.startsWith(pathResult.prefix))
|
||||
.map((p) => fs.realCasePath(p));
|
||||
|
||||
return paths;
|
||||
@ -135,10 +120,10 @@ export function isPythonBinary(p: string): boolean {
|
||||
|
||||
function findSitePackagesPath(
|
||||
fs: FileSystem,
|
||||
libPath: string,
|
||||
libPath: Uri,
|
||||
pythonVersion: PythonVersion | undefined,
|
||||
importFailureInfo: string[]
|
||||
): string | undefined {
|
||||
): Uri | undefined {
|
||||
if (fs.existsSync(libPath)) {
|
||||
importFailureInfo.push(`Found path '${libPath}'; looking for ${pathConsts.sitePackages}`);
|
||||
} else {
|
||||
@ -146,7 +131,7 @@ function findSitePackagesPath(
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const sitePackagesPath = combinePaths(libPath, pathConsts.sitePackages);
|
||||
const sitePackagesPath = libPath.combinePaths(pathConsts.sitePackages);
|
||||
if (fs.existsSync(sitePackagesPath)) {
|
||||
importFailureInfo.push(`Found path '${sitePackagesPath}'`);
|
||||
return sitePackagesPath;
|
||||
@ -160,8 +145,8 @@ function findSitePackagesPath(
|
||||
|
||||
// Candidate directories start with "python3.".
|
||||
const candidateDirs = entries.directories.filter((dirName) => {
|
||||
if (dirName.startsWith('python3.')) {
|
||||
const dirPath = combinePaths(libPath, dirName, pathConsts.sitePackages);
|
||||
if (dirName.fileName.startsWith('python3.')) {
|
||||
const dirPath = dirName.combinePaths(pathConsts.sitePackages);
|
||||
return fs.existsSync(dirPath);
|
||||
}
|
||||
return false;
|
||||
@ -170,9 +155,11 @@ function findSitePackagesPath(
|
||||
// If there is a python3.X directory (where 3.X matches the configured python
|
||||
// version), prefer that over other python directories.
|
||||
if (pythonVersion) {
|
||||
const preferredDir = candidateDirs.find((dirName) => dirName === `python${versionToString(pythonVersion)}`);
|
||||
const preferredDir = candidateDirs.find(
|
||||
(dirName) => dirName.fileName === `python${versionToString(pythonVersion)}`
|
||||
);
|
||||
if (preferredDir) {
|
||||
const dirPath = combinePaths(libPath, preferredDir, pathConsts.sitePackages);
|
||||
const dirPath = preferredDir.combinePaths(pathConsts.sitePackages);
|
||||
importFailureInfo.push(`Found path '${dirPath}'`);
|
||||
return dirPath;
|
||||
}
|
||||
@ -182,7 +169,7 @@ function findSitePackagesPath(
|
||||
// first directory that starts with "python". Most of the time, there will be
|
||||
// only one.
|
||||
if (candidateDirs.length > 0) {
|
||||
const dirPath = combinePaths(libPath, candidateDirs[0], pathConsts.sitePackages);
|
||||
const dirPath = candidateDirs[0].combinePaths(pathConsts.sitePackages);
|
||||
importFailureInfo.push(`Found path '${dirPath}'`);
|
||||
return dirPath;
|
||||
}
|
||||
@ -190,8 +177,8 @@ function findSitePackagesPath(
|
||||
return undefined;
|
||||
}
|
||||
|
||||
export function getPathsFromPthFiles(fs: FileSystem, parentDir: string): string[] {
|
||||
const searchPaths: string[] = [];
|
||||
export function getPathsFromPthFiles(fs: FileSystem, parentDir: Uri): Uri[] {
|
||||
const searchPaths: Uri[] = [];
|
||||
|
||||
// Get a list of all *.pth files within the specified directory.
|
||||
const pthFiles = fs
|
||||
@ -200,7 +187,7 @@ export function getPathsFromPthFiles(fs: FileSystem, parentDir: string): string[
|
||||
.sort((a, b) => compareComparableValues(a.name, b.name));
|
||||
|
||||
pthFiles.forEach((pthFile) => {
|
||||
const filePath = fs.realCasePath(combinePaths(parentDir, pthFile.name));
|
||||
const filePath = fs.realCasePath(parentDir.combinePaths(pthFile.name));
|
||||
const fileStats = tryStat(fs, filePath);
|
||||
|
||||
// Skip all files that are much larger than expected.
|
||||
@ -210,7 +197,7 @@ export function getPathsFromPthFiles(fs: FileSystem, parentDir: string): string[
|
||||
lines.forEach((line) => {
|
||||
const trimmedLine = line.trim();
|
||||
if (trimmedLine.length > 0 && !trimmedLine.startsWith('#') && !trimmedLine.match(/^import\s/)) {
|
||||
const pthPath = combinePaths(parentDir, trimmedLine);
|
||||
const pthPath = parentDir.combinePaths(trimmedLine);
|
||||
if (fs.existsSync(pthPath) && isDirectory(fs, pthPath)) {
|
||||
searchPaths.push(fs.realCasePath(pthPath));
|
||||
}
|
||||
@ -222,8 +209,8 @@ export function getPathsFromPthFiles(fs: FileSystem, parentDir: string): string[
|
||||
return searchPaths;
|
||||
}
|
||||
|
||||
function addPathIfUnique(pathList: string[], pathToAdd: string) {
|
||||
if (!pathList.some((path) => path === pathToAdd)) {
|
||||
function addPathIfUnique(pathList: Uri[], pathToAdd: Uri) {
|
||||
if (!pathList.some((path) => path.key === pathToAdd.key)) {
|
||||
pathList.push(pathToAdd);
|
||||
return true;
|
||||
}
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -20,13 +20,14 @@ import { DiagnosticSink, TextRangeDiagnosticSink } from '../common/diagnosticSin
|
||||
import { ServiceProvider } from '../common/extensibility';
|
||||
import { FileSystem } from '../common/fileSystem';
|
||||
import { LogTracker, getPathForLogging } from '../common/logTracker';
|
||||
import { getFileName, normalizeSlashes, stripFileExtension } from '../common/pathUtils';
|
||||
import { stripFileExtension } from '../common/pathUtils';
|
||||
import { convertOffsetsToRange, convertTextRangeToRange } from '../common/positionUtils';
|
||||
import { ServiceKeys } from '../common/serviceProviderExtensions';
|
||||
import * as StringUtils from '../common/stringUtils';
|
||||
import { Range, TextRange, getEmptyRange } from '../common/textRange';
|
||||
import { TextRangeCollection } from '../common/textRangeCollection';
|
||||
import { Duration, timingStats } from '../common/timing';
|
||||
import { Uri } from '../common/uri/uri';
|
||||
import { Localizer } from '../localization/localize';
|
||||
import { ModuleNode } from '../parser/parseNodes';
|
||||
import { IParser, ModuleImport, ParseOptions, ParseResults, Parser } from '../parser/parser';
|
||||
@ -179,12 +180,9 @@ export class SourceFile {
|
||||
// Console interface to use for debugging.
|
||||
private _console: ConsoleInterface;
|
||||
|
||||
// File path unique to this file within the workspace. May not represent
|
||||
// Uri unique to this file within the workspace. May not represent
|
||||
// a real file on disk.
|
||||
private readonly _filePath: string;
|
||||
|
||||
// File path on disk. May not be unique.
|
||||
private readonly _realFilePath: string;
|
||||
private readonly _uri: Uri;
|
||||
|
||||
// Period-delimited import path for the module.
|
||||
private _moduleName: string;
|
||||
@ -232,46 +230,42 @@ export class SourceFile {
|
||||
|
||||
constructor(
|
||||
readonly serviceProvider: ServiceProvider,
|
||||
filePath: string,
|
||||
uri: Uri,
|
||||
moduleName: string,
|
||||
isThirdPartyImport: boolean,
|
||||
isThirdPartyPyTypedPresent: boolean,
|
||||
editMode: SourceFileEditMode,
|
||||
console?: ConsoleInterface,
|
||||
logTracker?: LogTracker,
|
||||
realFilePath?: string,
|
||||
ipythonMode?: IPythonMode
|
||||
) {
|
||||
this.fileSystem = serviceProvider.get(ServiceKeys.fs);
|
||||
this._console = console || new StandardConsole();
|
||||
this._editMode = editMode;
|
||||
this._filePath = filePath;
|
||||
this._realFilePath = realFilePath ?? filePath;
|
||||
this._uri = uri;
|
||||
this._moduleName = moduleName;
|
||||
this._isStubFile = filePath.endsWith('.pyi');
|
||||
this._isStubFile = uri.hasExtension('.pyi');
|
||||
this._isThirdPartyImport = isThirdPartyImport;
|
||||
this._isThirdPartyPyTypedPresent = isThirdPartyPyTypedPresent;
|
||||
const fileName = getFileName(filePath);
|
||||
const fileName = uri.fileName;
|
||||
this._isTypingStubFile =
|
||||
this._isStubFile &&
|
||||
(this._filePath.endsWith(normalizeSlashes('stdlib/typing.pyi')) || fileName === 'typing_extensions.pyi');
|
||||
this._isStubFile && (this._uri.pathEndsWith('stdlib/typing.pyi') || fileName === 'typing_extensions.pyi');
|
||||
this._isTypingExtensionsStubFile = this._isStubFile && fileName === 'typing_extensions.pyi';
|
||||
this._isTypeshedStubFile =
|
||||
this._isStubFile && this._filePath.endsWith(normalizeSlashes('stdlib/_typeshed/__init__.pyi'));
|
||||
this._isTypeshedStubFile = this._isStubFile && this._uri.pathEndsWith('stdlib/_typeshed/__init__.pyi');
|
||||
|
||||
this._isBuiltInStubFile = false;
|
||||
if (this._isStubFile) {
|
||||
if (
|
||||
this._filePath.endsWith(normalizeSlashes('stdlib/collections/__init__.pyi')) ||
|
||||
this._filePath.endsWith(normalizeSlashes('stdlib/asyncio/futures.pyi')) ||
|
||||
this._filePath.endsWith(normalizeSlashes('stdlib/asyncio/tasks.pyi')) ||
|
||||
this._filePath.endsWith(normalizeSlashes('stdlib/builtins.pyi')) ||
|
||||
this._filePath.endsWith(normalizeSlashes('stdlib/_importlib_modulespec.pyi')) ||
|
||||
this._filePath.endsWith(normalizeSlashes('stdlib/dataclasses.pyi')) ||
|
||||
this._filePath.endsWith(normalizeSlashes('stdlib/abc.pyi')) ||
|
||||
this._filePath.endsWith(normalizeSlashes('stdlib/enum.pyi')) ||
|
||||
this._filePath.endsWith(normalizeSlashes('stdlib/queue.pyi')) ||
|
||||
this._filePath.endsWith(normalizeSlashes('stdlib/types.pyi'))
|
||||
this._uri.pathEndsWith('stdlib/collections/__init__.pyi') ||
|
||||
this._uri.pathEndsWith('stdlib/asyncio/futures.pyi') ||
|
||||
this._uri.pathEndsWith('stdlib/asyncio/tasks.pyi') ||
|
||||
this._uri.pathEndsWith('stdlib/builtins.pyi') ||
|
||||
this._uri.pathEndsWith('stdlib/_importlib_modulespec.pyi') ||
|
||||
this._uri.pathEndsWith('stdlib/dataclasses.pyi') ||
|
||||
this._uri.pathEndsWith('stdlib/abc.pyi') ||
|
||||
this._uri.pathEndsWith('stdlib/enum.pyi') ||
|
||||
this._uri.pathEndsWith('stdlib/queue.pyi') ||
|
||||
this._uri.pathEndsWith('stdlib/types.pyi')
|
||||
) {
|
||||
this._isBuiltInStubFile = true;
|
||||
}
|
||||
@ -282,16 +276,12 @@ export class SourceFile {
|
||||
this._ipythonMode = ipythonMode ?? IPythonMode.None;
|
||||
}
|
||||
|
||||
getRealFilePath(): string {
|
||||
return this._realFilePath;
|
||||
}
|
||||
|
||||
getIPythonMode(): IPythonMode {
|
||||
return this._ipythonMode;
|
||||
}
|
||||
|
||||
getFilePath(): string {
|
||||
return this._filePath;
|
||||
getUri(): Uri {
|
||||
return this._uri;
|
||||
}
|
||||
|
||||
getModuleName(): string {
|
||||
@ -300,7 +290,7 @@ export class SourceFile {
|
||||
}
|
||||
|
||||
// Synthesize a module name using the file path.
|
||||
return stripFileExtension(getFileName(this._filePath));
|
||||
return stripFileExtension(this._uri.fileName);
|
||||
}
|
||||
|
||||
setModuleName(name: string) {
|
||||
@ -378,8 +368,8 @@ export class SourceFile {
|
||||
// that of the previous contents.
|
||||
try {
|
||||
// Read the file's contents.
|
||||
if (this.fileSystem.existsSync(this._filePath)) {
|
||||
const fileContents = this.fileSystem.readFileSync(this._filePath, 'utf8');
|
||||
if (this.fileSystem.existsSync(this._uri)) {
|
||||
const fileContents = this.fileSystem.readFileSync(this._uri, 'utf8');
|
||||
|
||||
if (fileContents.length !== this._writableData.lastFileContentLength) {
|
||||
return true;
|
||||
@ -465,16 +455,16 @@ export class SourceFile {
|
||||
// Otherwise, get content from file system.
|
||||
try {
|
||||
// Check the file's length before attempting to read its full contents.
|
||||
const fileStat = this.fileSystem.statSync(this._filePath);
|
||||
const fileStat = this.fileSystem.statSync(this._uri);
|
||||
if (fileStat.size > _maxSourceFileSize) {
|
||||
this._console.error(
|
||||
`File length of "${this._filePath}" is ${fileStat.size} ` +
|
||||
`File length of "${this._uri}" is ${fileStat.size} ` +
|
||||
`which exceeds the maximum supported file size of ${_maxSourceFileSize}`
|
||||
);
|
||||
throw new Error('File larger than max');
|
||||
}
|
||||
|
||||
return this.fileSystem.readFileSync(this._filePath, 'utf8');
|
||||
return this.fileSystem.readFileSync(this._uri, 'utf8');
|
||||
} catch (error) {
|
||||
return undefined;
|
||||
}
|
||||
@ -580,7 +570,7 @@ export class SourceFile {
|
||||
// (or at least cancel) prior to calling again. It returns true if a parse
|
||||
// was required and false if the parse information was up to date already.
|
||||
parse(configOptions: ConfigOptions, importResolver: ImportResolver, content?: string): boolean {
|
||||
return this._logTracker.log(`parsing: ${this._getPathForLogging(this._filePath)}`, (logState) => {
|
||||
return this._logTracker.log(`parsing: ${this._getPathForLogging(this._uri)}`, (logState) => {
|
||||
// If the file is already parsed, we can skip.
|
||||
if (!this.isParseRequired()) {
|
||||
logState.suppress();
|
||||
@ -608,7 +598,7 @@ export class SourceFile {
|
||||
diagSink.addError(`Source file could not be read`, getEmptyRange());
|
||||
fileContents = '';
|
||||
|
||||
if (!this.fileSystem.existsSync(this._realFilePath)) {
|
||||
if (!this.fileSystem.existsSync(this._uri)) {
|
||||
this._writableData.isFileDeleted = true;
|
||||
}
|
||||
}
|
||||
@ -618,7 +608,7 @@ export class SourceFile {
|
||||
// Parse the token stream, building the abstract syntax tree.
|
||||
const parseResults = this._parseFile(
|
||||
configOptions,
|
||||
this._filePath,
|
||||
this._uri,
|
||||
fileContents!,
|
||||
this._ipythonMode,
|
||||
diagSink
|
||||
@ -632,7 +622,7 @@ export class SourceFile {
|
||||
this._writableData.parseResults.tokenizerOutput.pyrightIgnoreLines;
|
||||
|
||||
// Resolve imports.
|
||||
const execEnvironment = configOptions.findExecEnvironment(this._filePath);
|
||||
const execEnvironment = configOptions.findExecEnvironment(this._uri);
|
||||
timingStats.resolveImportsTime.timeOperation(() => {
|
||||
const importResult = this._resolveImports(
|
||||
importResolver,
|
||||
@ -648,7 +638,7 @@ export class SourceFile {
|
||||
|
||||
// Is this file in a "strict" path?
|
||||
const useStrict =
|
||||
configOptions.strict.find((strictFileSpec) => strictFileSpec.regExp.test(this._realFilePath)) !==
|
||||
configOptions.strict.find((strictFileSpec) => this._uri.matchesRegex(strictFileSpec.regExp)) !==
|
||||
undefined;
|
||||
|
||||
const commentDiags: CommentUtils.CommentDiagnostic[] = [];
|
||||
@ -680,7 +670,10 @@ export class SourceFile {
|
||||
(typeof e.message === 'string' ? e.message : undefined) ||
|
||||
JSON.stringify(e);
|
||||
this._console.error(
|
||||
Localizer.Diagnostic.internalParseError().format({ file: this.getFilePath(), message })
|
||||
Localizer.Diagnostic.internalParseError().format({
|
||||
file: this.getUri().toUserVisibleString(),
|
||||
message,
|
||||
})
|
||||
);
|
||||
|
||||
// Create dummy parse results.
|
||||
@ -708,7 +701,10 @@ export class SourceFile {
|
||||
|
||||
const diagSink = this.createDiagnosticSink();
|
||||
diagSink.addError(
|
||||
Localizer.Diagnostic.internalParseError().format({ file: this.getFilePath(), message }),
|
||||
Localizer.Diagnostic.internalParseError().format({
|
||||
file: this.getUri().toUserVisibleString(),
|
||||
message,
|
||||
}),
|
||||
getEmptyRange()
|
||||
);
|
||||
this._writableData.parseDiagnostics = diagSink.fetchAndClear();
|
||||
@ -740,7 +736,7 @@ export class SourceFile {
|
||||
assert(!this._writableData.isBindingInProgress, 'Bind called while binding in progress');
|
||||
assert(this._writableData.parseResults !== undefined, 'Parse results not available');
|
||||
|
||||
return this._logTracker.log(`binding: ${this._getPathForLogging(this._filePath)}`, () => {
|
||||
return this._logTracker.log(`binding: ${this._getPathForLogging(this._uri)}`, () => {
|
||||
try {
|
||||
// Perform name binding.
|
||||
timingStats.bindTime.timeOperation(() => {
|
||||
@ -777,12 +773,18 @@ export class SourceFile {
|
||||
(typeof e.message === 'string' ? e.message : undefined) ||
|
||||
JSON.stringify(e);
|
||||
this._console.error(
|
||||
Localizer.Diagnostic.internalBindError().format({ file: this.getFilePath(), message })
|
||||
Localizer.Diagnostic.internalBindError().format({
|
||||
file: this.getUri().toUserVisibleString(),
|
||||
message,
|
||||
})
|
||||
);
|
||||
|
||||
const diagSink = this.createDiagnosticSink();
|
||||
diagSink.addError(
|
||||
Localizer.Diagnostic.internalBindError().format({ file: this.getFilePath(), message }),
|
||||
Localizer.Diagnostic.internalBindError().format({
|
||||
file: this.getUri().toUserVisibleString(),
|
||||
message,
|
||||
}),
|
||||
getEmptyRange()
|
||||
);
|
||||
this._writableData.bindDiagnostics = diagSink.fetchAndClear();
|
||||
@ -814,7 +816,7 @@ export class SourceFile {
|
||||
assert(this.isCheckingRequired(), 'Check called unnecessarily');
|
||||
assert(this._writableData.parseResults !== undefined, 'Parse results not available');
|
||||
|
||||
return this._logTracker.log(`checking: ${this._getPathForLogging(this._filePath)}`, () => {
|
||||
return this._logTracker.log(`checking: ${this._getPathForLogging(this._uri)}`, () => {
|
||||
try {
|
||||
timingStats.typeCheckerTime.timeOperation(() => {
|
||||
const checkDuration = new Duration();
|
||||
@ -840,11 +842,17 @@ export class SourceFile {
|
||||
(typeof e.message === 'string' ? e.message : undefined) ||
|
||||
JSON.stringify(e);
|
||||
this._console.error(
|
||||
Localizer.Diagnostic.internalTypeCheckingError().format({ file: this.getFilePath(), message })
|
||||
Localizer.Diagnostic.internalTypeCheckingError().format({
|
||||
file: this.getUri().toUserVisibleString(),
|
||||
message,
|
||||
})
|
||||
);
|
||||
const diagSink = this.createDiagnosticSink();
|
||||
diagSink.addError(
|
||||
Localizer.Diagnostic.internalTypeCheckingError().format({ file: this.getFilePath(), message }),
|
||||
Localizer.Diagnostic.internalTypeCheckingError().format({
|
||||
file: this.getUri().toUserVisibleString(),
|
||||
message,
|
||||
}),
|
||||
getEmptyRange()
|
||||
);
|
||||
|
||||
@ -1097,7 +1105,7 @@ export class SourceFile {
|
||||
'\n' +
|
||||
cirDep
|
||||
.getPaths()
|
||||
.map((path) => ' ' + path)
|
||||
.map((path) => ' ' + path.toUserVisibleString())
|
||||
.join('\n'),
|
||||
getEmptyRange()
|
||||
);
|
||||
@ -1120,7 +1128,7 @@ export class SourceFile {
|
||||
this._addTaskListDiagnostics(configOptions.taskListTokens, diagList);
|
||||
|
||||
// If the file is in the ignore list, clear the diagnostic list.
|
||||
if (configOptions.ignore.find((ignoreFileSpec) => ignoreFileSpec.regExp.test(this._realFilePath))) {
|
||||
if (configOptions.ignore.find((ignoreFileSpec) => this._uri.matchesRegex(ignoreFileSpec.regExp))) {
|
||||
diagList = [];
|
||||
}
|
||||
|
||||
@ -1243,13 +1251,13 @@ export class SourceFile {
|
||||
futureImports,
|
||||
builtinsScope,
|
||||
diagnosticSink: analysisDiagnostics,
|
||||
executionEnvironment: configOptions.findExecEnvironment(this._filePath),
|
||||
executionEnvironment: configOptions.findExecEnvironment(this._uri),
|
||||
diagnosticRuleSet: this._diagnosticRuleSet,
|
||||
fileContents,
|
||||
lines: this._writableData.parseResults!.tokenizerOutput.lines,
|
||||
typingSymbolAliases: this._writableData.parseResults!.typingSymbolAliases,
|
||||
definedConstants: configOptions.defineConstant,
|
||||
filePath: this._filePath,
|
||||
fileUri: this._uri,
|
||||
moduleName: this.getModuleName(),
|
||||
isStubFile: this._isStubFile,
|
||||
isTypingStubFile: this._isTypingStubFile,
|
||||
@ -1281,7 +1289,7 @@ export class SourceFile {
|
||||
const imports: ImportResult[] = [];
|
||||
|
||||
const resolveAndAddIfNotSelf = (nameParts: string[], skipMissingImport = false) => {
|
||||
const importResult = importResolver.resolveImport(this._filePath, execEnv, {
|
||||
const importResult = importResolver.resolveImport(this._uri, execEnv, {
|
||||
leadingDots: 0,
|
||||
nameParts,
|
||||
importedSymbols: undefined,
|
||||
@ -1292,7 +1300,7 @@ export class SourceFile {
|
||||
}
|
||||
|
||||
// Avoid importing module from the module file itself.
|
||||
if (importResult.resolvedPaths.length === 0 || importResult.resolvedPaths[0] !== this._filePath) {
|
||||
if (importResult.resolvedUris.length === 0 || importResult.resolvedUris[0] !== this._uri) {
|
||||
imports.push(importResult);
|
||||
return importResult;
|
||||
}
|
||||
@ -1314,7 +1322,7 @@ export class SourceFile {
|
||||
}
|
||||
|
||||
for (const moduleImport of moduleImports) {
|
||||
const importResult = importResolver.resolveImport(this._filePath, execEnv, {
|
||||
const importResult = importResolver.resolveImport(this._uri, execEnv, {
|
||||
leadingDots: moduleImport.leadingDots,
|
||||
nameParts: moduleImport.nameParts,
|
||||
importedSymbols: moduleImport.importedSymbols,
|
||||
@ -1347,24 +1355,24 @@ export class SourceFile {
|
||||
};
|
||||
}
|
||||
|
||||
private _getPathForLogging(filepath: string) {
|
||||
return getPathForLogging(this.fileSystem, filepath);
|
||||
private _getPathForLogging(fileUri: Uri) {
|
||||
return getPathForLogging(this.fileSystem, fileUri);
|
||||
}
|
||||
|
||||
private _parseFile(
|
||||
configOptions: ConfigOptions,
|
||||
filePath: string,
|
||||
fileUri: Uri,
|
||||
fileContents: string,
|
||||
ipythonMode: IPythonMode,
|
||||
diagSink: DiagnosticSink
|
||||
) {
|
||||
// Use the configuration options to determine the environment in which
|
||||
// this source file will be executed.
|
||||
const execEnvironment = configOptions.findExecEnvironment(filePath);
|
||||
const execEnvironment = configOptions.findExecEnvironment(fileUri);
|
||||
|
||||
const parseOptions = new ParseOptions();
|
||||
parseOptions.ipythonMode = ipythonMode;
|
||||
if (filePath.endsWith('pyi')) {
|
||||
if (fileUri.pathEndsWith('pyi')) {
|
||||
parseOptions.isStubFile = true;
|
||||
}
|
||||
parseOptions.pythonVersion = execEnvironment.pythonVersion;
|
||||
@ -1378,7 +1386,7 @@ export class SourceFile {
|
||||
private _fireFileDirtyEvent() {
|
||||
this.serviceProvider.tryGet(ServiceKeys.stateMutationListeners)?.forEach((l) => {
|
||||
try {
|
||||
l.fileDirty?.(this._filePath);
|
||||
l.fileDirty?.(this._uri);
|
||||
} catch (ex: any) {
|
||||
const console = this.serviceProvider.tryGet(ServiceKeys.console);
|
||||
if (console) {
|
||||
|
@ -34,9 +34,9 @@ export function verifyNoCyclesInChainedFiles<T extends SourceFileInfo>(program:
|
||||
return;
|
||||
}
|
||||
|
||||
const set = new Set<string>([fileInfo.sourceFile.getFilePath()]);
|
||||
const set = new Set<string>([fileInfo.sourceFile.getUri().key]);
|
||||
while (nextChainedFile) {
|
||||
const path = nextChainedFile.sourceFile.getFilePath();
|
||||
const path = nextChainedFile.sourceFile.getUri().key;
|
||||
if (set.has(path)) {
|
||||
// We found a cycle.
|
||||
fail(
|
||||
@ -90,7 +90,7 @@ function _parseAllOpenCells(program: ProgramView): void {
|
||||
continue;
|
||||
}
|
||||
|
||||
program.getParseResults(file.sourceFile.getFilePath());
|
||||
program.getParseResults(file.sourceFile.getUri());
|
||||
program.handleMemoryHighUsage();
|
||||
}
|
||||
}
|
||||
|
@ -14,31 +14,31 @@ import { appendArray } from '../common/collectionUtils';
|
||||
import { ExecutionEnvironment } from '../common/configOptions';
|
||||
import { isDefined } from '../common/core';
|
||||
import { assertNever } from '../common/debug';
|
||||
import { combinePaths, getAnyExtensionFromPath, stripFileExtension } from '../common/pathUtils';
|
||||
import { Uri } from '../common/uri/uri';
|
||||
import { ClassNode, ImportFromNode, ModuleNode, ParseNode, ParseNodeType } from '../parser/parseNodes';
|
||||
import {
|
||||
AliasDeclaration,
|
||||
ClassDeclaration,
|
||||
Declaration,
|
||||
FunctionDeclaration,
|
||||
ParameterDeclaration,
|
||||
SpecialBuiltInClassDeclaration,
|
||||
VariableDeclaration,
|
||||
isAliasDeclaration,
|
||||
isClassDeclaration,
|
||||
isFunctionDeclaration,
|
||||
isParameterDeclaration,
|
||||
isSpecialBuiltInClassDeclaration,
|
||||
isVariableDeclaration,
|
||||
ParameterDeclaration,
|
||||
SpecialBuiltInClassDeclaration,
|
||||
VariableDeclaration,
|
||||
} from './declaration';
|
||||
import { ImportResolver } from './importResolver';
|
||||
import { SourceFileInfo } from './sourceFileInfo';
|
||||
import { SourceFile } from './sourceFile';
|
||||
import { SourceFileInfo } from './sourceFileInfo';
|
||||
import { isUserCode } from './sourceFileInfoUtils';
|
||||
import { buildImportTree } from './sourceMapperUtils';
|
||||
import { TypeEvaluator } from './typeEvaluatorTypes';
|
||||
import { ClassType, isFunction, isInstantiableClass, isOverloadedFunction } from './types';
|
||||
import { lookUpClassMember } from './typeUtils';
|
||||
import { ClassType, isFunction, isInstantiableClass, isOverloadedFunction } from './types';
|
||||
|
||||
type ClassOrFunctionOrVariableDeclaration =
|
||||
| ClassDeclaration
|
||||
@ -47,8 +47,8 @@ type ClassOrFunctionOrVariableDeclaration =
|
||||
| VariableDeclaration;
|
||||
|
||||
// Creates and binds a shadowed file within the program.
|
||||
export type ShadowFileBinder = (stubFilePath: string, implFilePath: string) => SourceFile | undefined;
|
||||
export type BoundSourceGetter = (filePath: string) => SourceFileInfo | undefined;
|
||||
export type ShadowFileBinder = (stubFileUri: Uri, implFileUri: Uri) => SourceFile | undefined;
|
||||
export type BoundSourceGetter = (fileUri: Uri) => SourceFileInfo | undefined;
|
||||
|
||||
export class SourceMapper {
|
||||
constructor(
|
||||
@ -63,10 +63,10 @@ export class SourceMapper {
|
||||
private _cancelToken: CancellationToken
|
||||
) {}
|
||||
|
||||
findModules(stubFilePath: string): ModuleNode[] {
|
||||
const sourceFiles = this._isStubThatShouldBeMappedToImplementation(stubFilePath)
|
||||
? this._getBoundSourceFilesFromStubFile(stubFilePath)
|
||||
: [this._boundSourceGetter(stubFilePath)?.sourceFile];
|
||||
findModules(stubFileUri: Uri): ModuleNode[] {
|
||||
const sourceFiles = this._isStubThatShouldBeMappedToImplementation(stubFileUri)
|
||||
? this._getBoundSourceFilesFromStubFile(stubFileUri)
|
||||
: [this._boundSourceGetter(stubFileUri)?.sourceFile];
|
||||
|
||||
return sourceFiles
|
||||
.filter(isDefined)
|
||||
@ -74,8 +74,8 @@ export class SourceMapper {
|
||||
.filter(isDefined);
|
||||
}
|
||||
|
||||
getModuleNode(filePath: string): ModuleNode | undefined {
|
||||
return this._boundSourceGetter(filePath)?.sourceFile.getParseResults()?.parseTree;
|
||||
getModuleNode(fileUri: Uri): ModuleNode | undefined {
|
||||
return this._boundSourceGetter(fileUri)?.sourceFile.getParseResults()?.parseTree;
|
||||
}
|
||||
|
||||
findDeclarations(stubDecl: Declaration): Declaration[] {
|
||||
@ -94,13 +94,13 @@ export class SourceMapper {
|
||||
return [];
|
||||
}
|
||||
|
||||
findDeclarationsByType(originatedPath: string, type: ClassType, useTypeAlias = false): Declaration[] {
|
||||
findDeclarationsByType(originatedPath: Uri, type: ClassType, useTypeAlias = false): Declaration[] {
|
||||
const result: ClassOrFunctionOrVariableDeclaration[] = [];
|
||||
this._addClassTypeDeclarations(originatedPath, type, result, new Set<string>(), useTypeAlias);
|
||||
return result;
|
||||
}
|
||||
|
||||
findClassDeclarationsByType(originatedPath: string, type: ClassType): ClassDeclaration[] {
|
||||
findClassDeclarationsByType(originatedPath: Uri, type: ClassType): ClassDeclaration[] {
|
||||
const result = this.findDeclarationsByType(originatedPath, type);
|
||||
return result.filter((r) => isClassDeclaration(r)).map((r) => r as ClassDeclaration);
|
||||
}
|
||||
@ -111,17 +111,17 @@ export class SourceMapper {
|
||||
.map((d) => d as FunctionDeclaration);
|
||||
}
|
||||
|
||||
isUserCode(path: string): boolean {
|
||||
return isUserCode(this._boundSourceGetter(path));
|
||||
isUserCode(uri: Uri): boolean {
|
||||
return isUserCode(this._boundSourceGetter(uri));
|
||||
}
|
||||
|
||||
getNextFileName(path: string) {
|
||||
const withoutExtension = stripFileExtension(path);
|
||||
getNextFileName(uri: Uri) {
|
||||
const withoutExtension = uri.stripExtension();
|
||||
let suffix = 1;
|
||||
let result = `${withoutExtension}_${suffix}.py`;
|
||||
let result = withoutExtension.addExtension(`_${suffix}.py`);
|
||||
while (this.isUserCode(result) && suffix < 1000) {
|
||||
suffix += 1;
|
||||
result = `${withoutExtension}_${suffix}.py`;
|
||||
result = withoutExtension.addExtension(`_${suffix}.py`);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
@ -132,7 +132,7 @@ export class SourceMapper {
|
||||
) {
|
||||
if (stubDecl.node.valueExpression.nodeType === ParseNodeType.Name) {
|
||||
const className = stubDecl.node.valueExpression.value;
|
||||
const sourceFiles = this._getBoundSourceFilesFromStubFile(stubDecl.path);
|
||||
const sourceFiles = this._getBoundSourceFilesFromStubFile(stubDecl.uri);
|
||||
|
||||
return sourceFiles.flatMap((sourceFile) =>
|
||||
this._findClassDeclarationsByName(sourceFile, className, recursiveDeclCache)
|
||||
@ -144,7 +144,7 @@ export class SourceMapper {
|
||||
|
||||
private _findClassOrTypeAliasDeclarations(stubDecl: ClassDeclaration, recursiveDeclCache = new Set<string>()) {
|
||||
const className = this._getFullClassName(stubDecl.node);
|
||||
const sourceFiles = this._getBoundSourceFilesFromStubFile(stubDecl.path);
|
||||
const sourceFiles = this._getBoundSourceFilesFromStubFile(stubDecl.uri);
|
||||
|
||||
return sourceFiles.flatMap((sourceFile) =>
|
||||
this._findClassDeclarationsByName(sourceFile, className, recursiveDeclCache)
|
||||
@ -156,7 +156,7 @@ export class SourceMapper {
|
||||
recursiveDeclCache = new Set<string>()
|
||||
): ClassOrFunctionOrVariableDeclaration[] {
|
||||
const functionName = stubDecl.node.name.value;
|
||||
const sourceFiles = this._getBoundSourceFilesFromStubFile(stubDecl.path);
|
||||
const sourceFiles = this._getBoundSourceFilesFromStubFile(stubDecl.uri);
|
||||
|
||||
if (stubDecl.isMethod) {
|
||||
const classNode = ParseTreeUtils.getEnclosingClass(stubDecl.node);
|
||||
@ -184,7 +184,7 @@ export class SourceMapper {
|
||||
}
|
||||
|
||||
const variableName = stubDecl.node.value;
|
||||
const sourceFiles = this._getBoundSourceFilesFromStubFile(stubDecl.path);
|
||||
const sourceFiles = this._getBoundSourceFilesFromStubFile(stubDecl.uri);
|
||||
const classNode = ParseTreeUtils.getEnclosingClass(stubDecl.node);
|
||||
|
||||
if (classNode) {
|
||||
@ -270,7 +270,7 @@ export class SourceMapper {
|
||||
): VariableDeclaration[] {
|
||||
let result: VariableDeclaration[] = [];
|
||||
|
||||
const uniqueId = `@${sourceFile.getFilePath()}/c/${className}/v/${variableName}`;
|
||||
const uniqueId = `@${sourceFile.getUri()}/c/${className}/v/${variableName}`;
|
||||
if (recursiveDeclCache.has(uniqueId)) {
|
||||
return result;
|
||||
}
|
||||
@ -283,7 +283,7 @@ export class SourceMapper {
|
||||
variableName,
|
||||
(decl, cache, result) => {
|
||||
if (isVariableDeclaration(decl)) {
|
||||
if (this._isStubThatShouldBeMappedToImplementation(decl.path)) {
|
||||
if (this._isStubThatShouldBeMappedToImplementation(decl.uri)) {
|
||||
for (const implDecl of this._findVariableDeclarations(decl, cache)) {
|
||||
if (isVariableDeclaration(implDecl)) {
|
||||
result.push(implDecl);
|
||||
@ -309,7 +309,7 @@ export class SourceMapper {
|
||||
): ClassOrFunctionOrVariableDeclaration[] {
|
||||
let result: ClassOrFunctionOrVariableDeclaration[] = [];
|
||||
|
||||
const uniqueId = `@${sourceFile.getFilePath()}/c/${className}/f/${functionName}`;
|
||||
const uniqueId = `@${sourceFile.getUri()}/c/${className}/f/${functionName}`;
|
||||
if (recursiveDeclCache.has(uniqueId)) {
|
||||
return result;
|
||||
}
|
||||
@ -322,7 +322,7 @@ export class SourceMapper {
|
||||
functionName,
|
||||
(decl, cache, result) => {
|
||||
if (isFunctionDeclaration(decl)) {
|
||||
if (this._isStubThatShouldBeMappedToImplementation(decl.path)) {
|
||||
if (this._isStubThatShouldBeMappedToImplementation(decl.uri)) {
|
||||
appendArray(result, this._findFunctionOrTypeAliasDeclarations(decl, cache));
|
||||
} else {
|
||||
result.push(decl);
|
||||
@ -343,7 +343,7 @@ export class SourceMapper {
|
||||
): ClassOrFunctionOrVariableDeclaration[] {
|
||||
const result: ClassOrFunctionOrVariableDeclaration[] = [];
|
||||
|
||||
const uniqueId = `@${sourceFile.getFilePath()}/v/${variableName}`;
|
||||
const uniqueId = `@${sourceFile.getUri()}/v/${variableName}`;
|
||||
if (recursiveDeclCache.has(uniqueId)) {
|
||||
return result;
|
||||
}
|
||||
@ -377,7 +377,7 @@ export class SourceMapper {
|
||||
): ClassOrFunctionOrVariableDeclaration[] {
|
||||
const result: ClassOrFunctionOrVariableDeclaration[] = [];
|
||||
|
||||
const uniqueId = `@${sourceFile.getFilePath()}/f/${functionName}`;
|
||||
const uniqueId = `@${sourceFile.getUri()}/f/${functionName}`;
|
||||
if (recursiveDeclCache.has(uniqueId)) {
|
||||
return result;
|
||||
}
|
||||
@ -438,7 +438,7 @@ export class SourceMapper {
|
||||
): ClassOrFunctionOrVariableDeclaration[] {
|
||||
const result: ClassOrFunctionOrVariableDeclaration[] = [];
|
||||
|
||||
const uniqueId = `@${sourceFile.getFilePath()}[${parentNode.start}]${className}`;
|
||||
const uniqueId = `@${sourceFile.getUri()}[${parentNode.start}]${className}`;
|
||||
if (recursiveDeclCache.has(uniqueId)) {
|
||||
return result;
|
||||
}
|
||||
@ -464,7 +464,7 @@ export class SourceMapper {
|
||||
recursiveDeclCache: Set<string>
|
||||
) {
|
||||
if (isVariableDeclaration(decl)) {
|
||||
if (this._isStubThatShouldBeMappedToImplementation(decl.path)) {
|
||||
if (this._isStubThatShouldBeMappedToImplementation(decl.uri)) {
|
||||
appendArray(result, this._findVariableDeclarations(decl, recursiveDeclCache));
|
||||
} else {
|
||||
result.push(decl);
|
||||
@ -487,7 +487,7 @@ export class SourceMapper {
|
||||
recursiveDeclCache: Set<string>
|
||||
) {
|
||||
if (isClassDeclaration(decl)) {
|
||||
if (this._isStubThatShouldBeMappedToImplementation(decl.path)) {
|
||||
if (this._isStubThatShouldBeMappedToImplementation(decl.uri)) {
|
||||
appendArray(result, this._findClassOrTypeAliasDeclarations(decl, recursiveDeclCache));
|
||||
} else {
|
||||
result.push(decl);
|
||||
@ -495,7 +495,7 @@ export class SourceMapper {
|
||||
} else if (isSpecialBuiltInClassDeclaration(decl)) {
|
||||
result.push(decl);
|
||||
} else if (isFunctionDeclaration(decl)) {
|
||||
if (this._isStubThatShouldBeMappedToImplementation(decl.path)) {
|
||||
if (this._isStubThatShouldBeMappedToImplementation(decl.uri)) {
|
||||
appendArray(result, this._findFunctionOrTypeAliasDeclarations(decl, recursiveDeclCache));
|
||||
} else {
|
||||
result.push(decl);
|
||||
@ -525,7 +525,7 @@ export class SourceMapper {
|
||||
this._addClassOrFunctionDeclarations(overloadDecl, result, recursiveDeclCache);
|
||||
}
|
||||
} else if (isInstantiableClass(type)) {
|
||||
this._addClassTypeDeclarations(decl.path, type, result, recursiveDeclCache);
|
||||
this._addClassTypeDeclarations(decl.uri, type, result, recursiveDeclCache);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -541,7 +541,7 @@ export class SourceMapper {
|
||||
// and second, clone the given decl and set path to the generated pyi for the
|
||||
// builtin module (ex, _io) to make resolveAliasDeclaration to work.
|
||||
// once the path is set, our regular code path will work as expected.
|
||||
if (decl.path || !decl.node) {
|
||||
if (!decl.uri.isEmpty() || !decl.node) {
|
||||
// If module actually exists, nothing we need to do.
|
||||
return decl;
|
||||
}
|
||||
@ -563,20 +563,19 @@ export class SourceMapper {
|
||||
|
||||
// ImportResolver might be able to generate or extract builtin module's info
|
||||
// from runtime if we provide right synthesized stub path.
|
||||
const fakeStubPath = combinePaths(
|
||||
stdLibPath,
|
||||
const fakeStubPath = stdLibPath.combinePaths(
|
||||
getModuleName()
|
||||
.nameParts.map((n) => n.value)
|
||||
.join('.') + '.pyi'
|
||||
);
|
||||
|
||||
const sources = this._getSourceFiles(fakeStubPath, fileInfo.filePath);
|
||||
const sources = this._getSourceFiles(fakeStubPath, fileInfo.fileUri);
|
||||
if (sources.length === 0) {
|
||||
return decl;
|
||||
}
|
||||
|
||||
const synthesizedDecl = { ...decl };
|
||||
synthesizedDecl.path = sources[0].getFilePath();
|
||||
synthesizedDecl.uri = sources[0].getUri();
|
||||
|
||||
return synthesizedDecl;
|
||||
|
||||
@ -595,14 +594,14 @@ export class SourceMapper {
|
||||
}
|
||||
|
||||
private _addClassTypeDeclarations(
|
||||
originated: string,
|
||||
originated: Uri,
|
||||
type: ClassType,
|
||||
result: ClassOrFunctionOrVariableDeclaration[],
|
||||
recursiveDeclCache: Set<string>,
|
||||
useTypeAlias = false
|
||||
) {
|
||||
const filePath = type.details.filePath;
|
||||
const sourceFiles = this._getSourceFiles(filePath, /* stubToShadow */ undefined, originated);
|
||||
const fileUri = type.details.fileUri;
|
||||
const sourceFiles = this._getSourceFiles(fileUri, /* stubToShadow */ undefined, originated);
|
||||
|
||||
const fullName = useTypeAlias && type.typeAliasInfo ? type.typeAliasInfo.fullName : type.details.fullName;
|
||||
const fullClassName = fullName.substring(type.details.moduleName.length + 1 /* +1 for trailing dot */);
|
||||
@ -612,13 +611,13 @@ export class SourceMapper {
|
||||
}
|
||||
}
|
||||
|
||||
private _getSourceFiles(filePath: string, stubToShadow?: string, originated?: string) {
|
||||
private _getSourceFiles(fileUri: Uri, stubToShadow?: Uri, originated?: Uri) {
|
||||
const sourceFiles: SourceFile[] = [];
|
||||
|
||||
if (this._isStubThatShouldBeMappedToImplementation(filePath)) {
|
||||
appendArray(sourceFiles, this._getBoundSourceFilesFromStubFile(filePath, stubToShadow, originated));
|
||||
if (this._isStubThatShouldBeMappedToImplementation(fileUri)) {
|
||||
appendArray(sourceFiles, this._getBoundSourceFilesFromStubFile(fileUri, stubToShadow, originated));
|
||||
} else {
|
||||
const sourceFileInfo = this._boundSourceGetter(filePath);
|
||||
const sourceFileInfo = this._boundSourceGetter(fileUri);
|
||||
if (sourceFileInfo) {
|
||||
sourceFiles.push(sourceFileInfo.sourceFile);
|
||||
}
|
||||
@ -645,14 +644,14 @@ export class SourceMapper {
|
||||
for (const decl of symbol.getDeclarations()) {
|
||||
if (
|
||||
!isAliasDeclaration(decl) ||
|
||||
!decl.path ||
|
||||
decl.uri.isEmpty() ||
|
||||
decl.node.nodeType !== ParseNodeType.ImportFrom ||
|
||||
!decl.node.isWildcardImport
|
||||
) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const uniqueId = `@${decl.path}/l/${symbolName}`;
|
||||
const uniqueId = `@${decl.uri.key}/l/${symbolName}`;
|
||||
if (recursiveDeclCache.has(uniqueId)) {
|
||||
continue;
|
||||
}
|
||||
@ -667,7 +666,7 @@ export class SourceMapper {
|
||||
// function.
|
||||
recursiveDeclCache.add(uniqueId);
|
||||
|
||||
const sourceFiles = this._getSourceFiles(decl.path);
|
||||
const sourceFiles = this._getSourceFiles(decl.uri);
|
||||
for (const sourceFile of sourceFiles) {
|
||||
const moduleNode = sourceFile.getParseResults()?.parseTree;
|
||||
if (!moduleNode) {
|
||||
@ -728,28 +727,21 @@ export class SourceMapper {
|
||||
return fullName.reverse().join('.');
|
||||
}
|
||||
|
||||
private _getBoundSourceFilesFromStubFile(
|
||||
stubFilePath: string,
|
||||
stubToShadow?: string,
|
||||
originated?: string
|
||||
): SourceFile[] {
|
||||
const paths = this._getSourcePathsFromStub(
|
||||
stubFilePath,
|
||||
originated ?? this._fromFile?.sourceFile.getFilePath()
|
||||
);
|
||||
return paths.map((fp) => this._fileBinder(stubToShadow ?? stubFilePath, fp)).filter(isDefined);
|
||||
private _getBoundSourceFilesFromStubFile(stubFileUri: Uri, stubToShadow?: Uri, originated?: Uri): SourceFile[] {
|
||||
const paths = this._getSourcePathsFromStub(stubFileUri, originated ?? this._fromFile?.sourceFile.getUri());
|
||||
return paths.map((fp) => this._fileBinder(stubToShadow ?? stubFileUri, fp)).filter(isDefined);
|
||||
}
|
||||
|
||||
private _getSourcePathsFromStub(stubFilePath: string, fromFile: string | undefined): string[] {
|
||||
// Attempt our stubFilePath to see if we can resolve it as a source file path
|
||||
let results = this._importResolver.getSourceFilesFromStub(stubFilePath, this._execEnv, this._mapCompiled);
|
||||
private _getSourcePathsFromStub(stubFileUri: Uri, fromFile: Uri | undefined): Uri[] {
|
||||
// Attempt our stubFileUri to see if we can resolve it as a source file path
|
||||
let results = this._importResolver.getSourceFilesFromStub(stubFileUri, this._execEnv, this._mapCompiled);
|
||||
if (results.length > 0) {
|
||||
return results;
|
||||
}
|
||||
|
||||
// If that didn't work, try looking through the graph up to our fromFile.
|
||||
// One of them should be able to resolve to an actual file.
|
||||
const stubFileImportTree = this._getStubFileImportTree(stubFilePath, fromFile);
|
||||
const stubFileImportTree = this._getStubFileImportTree(stubFileUri, fromFile);
|
||||
|
||||
// Go through the items in this tree until we find at least one path.
|
||||
for (let i = 0; i < stubFileImportTree.length; i++) {
|
||||
@ -766,43 +758,41 @@ export class SourceMapper {
|
||||
return [];
|
||||
}
|
||||
|
||||
private _getStubFileImportTree(stubFilePath: string, fromFile: string | undefined): string[] {
|
||||
if (!fromFile || !this._isStubThatShouldBeMappedToImplementation(stubFilePath)) {
|
||||
private _getStubFileImportTree(stubFileUri: Uri, fromFile: Uri | undefined): Uri[] {
|
||||
if (!fromFile || !this._isStubThatShouldBeMappedToImplementation(stubFileUri)) {
|
||||
// No path to search, just return the starting point.
|
||||
return [stubFilePath];
|
||||
return [stubFileUri];
|
||||
} else {
|
||||
// Otherwise recurse through the importedBy list up to our 'fromFile'.
|
||||
return buildImportTree(
|
||||
fromFile,
|
||||
stubFilePath,
|
||||
stubFileUri,
|
||||
(p) => {
|
||||
const boundSourceInfo = this._boundSourceGetter(p);
|
||||
return boundSourceInfo
|
||||
? boundSourceInfo.importedBy.map((info) => info.sourceFile.getFilePath())
|
||||
: [];
|
||||
return boundSourceInfo ? boundSourceInfo.importedBy.map((info) => info.sourceFile.getUri()) : [];
|
||||
},
|
||||
this._cancelToken
|
||||
).filter((p) => this._isStubThatShouldBeMappedToImplementation(p));
|
||||
}
|
||||
}
|
||||
|
||||
private _isStubThatShouldBeMappedToImplementation(filePath: string): boolean {
|
||||
private _isStubThatShouldBeMappedToImplementation(fileUri: Uri): boolean {
|
||||
if (this._preferStubs) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const stub = isStubFile(filePath);
|
||||
const stub = isStubFile(fileUri);
|
||||
if (!stub) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// If we get the same file as a source file, then we treat the file as a regular file even if it has "pyi" extension.
|
||||
return this._importResolver
|
||||
.getSourceFilesFromStub(filePath, this._execEnv, this._mapCompiled)
|
||||
.every((f) => f !== filePath);
|
||||
.getSourceFilesFromStub(fileUri, this._execEnv, this._mapCompiled)
|
||||
.every((f) => f !== fileUri);
|
||||
}
|
||||
}
|
||||
|
||||
export function isStubFile(filePath: string): boolean {
|
||||
return getAnyExtensionFromPath(filePath, ['.pyi'], /* ignoreCase */ false) === '.pyi';
|
||||
export function isStubFile(uri: Uri): boolean {
|
||||
return uri.lastExtension === '.pyi';
|
||||
}
|
||||
|
@ -5,6 +5,7 @@
|
||||
*/
|
||||
|
||||
import { CancellationToken } from 'vscode-jsonrpc';
|
||||
import { Uri } from '../common/uri/uri';
|
||||
|
||||
const MAX_TREE_SEARCH_COUNT = 1000;
|
||||
|
||||
@ -15,12 +16,7 @@ class NumberReference {
|
||||
// Builds an array of imports from the 'from' to the 'to' entry where 'from'
|
||||
// is on the front of the array and the item just before 'to' is on the
|
||||
// back of the array.
|
||||
export function buildImportTree(
|
||||
to: string,
|
||||
from: string,
|
||||
next: (from: string) => string[],
|
||||
token: CancellationToken
|
||||
): string[] {
|
||||
export function buildImportTree(to: Uri, from: Uri, next: (from: Uri) => Uri[], token: CancellationToken): Uri[] {
|
||||
const totalCountRef = new NumberReference();
|
||||
const results = _buildImportTreeImpl(to, from, next, [], totalCountRef, token);
|
||||
|
||||
@ -29,25 +25,25 @@ export function buildImportTree(
|
||||
}
|
||||
|
||||
function _buildImportTreeImpl(
|
||||
to: string,
|
||||
from: string,
|
||||
next: (from: string) => string[],
|
||||
previous: string[],
|
||||
to: Uri,
|
||||
from: Uri,
|
||||
next: (from: Uri) => Uri[],
|
||||
previous: Uri[],
|
||||
totalSearched: NumberReference,
|
||||
token: CancellationToken
|
||||
): string[] {
|
||||
): Uri[] {
|
||||
// Exit early if cancellation is requested or we've exceeded max count
|
||||
if (totalSearched.value > MAX_TREE_SEARCH_COUNT || token.isCancellationRequested) {
|
||||
return [];
|
||||
}
|
||||
totalSearched.value += 1;
|
||||
|
||||
if (from === to) {
|
||||
if (from.equals(to)) {
|
||||
// At the top, previous should have our way into this recursion.
|
||||
return previous.length ? previous : [from];
|
||||
}
|
||||
|
||||
if (previous.length > 1 && previous.find((s) => s === from)) {
|
||||
if (previous.length > 1 && previous.find((s) => s.equals(from))) {
|
||||
// Fail the search, we're stuck in a loop.
|
||||
return [];
|
||||
}
|
||||
|
@ -8,8 +8,9 @@
|
||||
|
||||
import { isNumber, isString } from '../common/core';
|
||||
import { assertNever } from '../common/debug';
|
||||
import { ensureTrailingDirectorySeparator, stripFileExtension } from '../common/pathUtils';
|
||||
import { stripFileExtension } from '../common/pathUtils';
|
||||
import { convertOffsetToPosition } from '../common/positionUtils';
|
||||
import { Uri } from '../common/uri/uri';
|
||||
import { ParseNode, ParseNodeType, isExpressionNode } from '../parser/parseNodes';
|
||||
import { AbsoluteModuleDescriptor } from './analyzerFileInfo';
|
||||
import * as AnalyzerNodeInfo from './analyzerNodeInfo';
|
||||
@ -22,10 +23,10 @@ export type PrintableType = ParseNode | Declaration | Symbol | Type | undefined;
|
||||
|
||||
export interface TracePrinter {
|
||||
print(o: PrintableType): string;
|
||||
printFileOrModuleName(filePathOrModule: string | AbsoluteModuleDescriptor): string;
|
||||
printFileOrModuleName(fileUriOrModule: Uri | AbsoluteModuleDescriptor): string;
|
||||
}
|
||||
|
||||
export function createTracePrinter(roots: string[]): TracePrinter {
|
||||
export function createTracePrinter(roots: Uri[]): TracePrinter {
|
||||
function wrap(value: string | undefined, ch = "'") {
|
||||
return value ? `${ch}${value}${ch}` : '';
|
||||
}
|
||||
@ -33,25 +34,22 @@ export function createTracePrinter(roots: string[]): TracePrinter {
|
||||
// Sort roots in desc order so that we compare longer path first
|
||||
// when getting relative path.
|
||||
// ex) d:/root/.env/lib/site-packages, d:/root/.env
|
||||
roots = roots
|
||||
.map((r) => ensureTrailingDirectorySeparator(r))
|
||||
.sort((a, b) => a.localeCompare(b))
|
||||
.reverse();
|
||||
roots = roots.sort((a, b) => a.key.localeCompare(b.key)).reverse();
|
||||
|
||||
const separatorRegExp = /[\\/]/g;
|
||||
function printFileOrModuleName(filePathOrModule: string | AbsoluteModuleDescriptor | undefined) {
|
||||
if (filePathOrModule) {
|
||||
if (typeof filePathOrModule === 'string') {
|
||||
function printFileOrModuleName(fileUriOrModule: Uri | AbsoluteModuleDescriptor | undefined) {
|
||||
if (fileUriOrModule) {
|
||||
if (Uri.isUri(fileUriOrModule)) {
|
||||
for (const root of roots) {
|
||||
if (filePathOrModule.startsWith(root)) {
|
||||
const subFile = filePathOrModule.substring(root.length);
|
||||
return stripFileExtension(subFile).replace(separatorRegExp, '.');
|
||||
if (fileUriOrModule.isChild(root)) {
|
||||
const subFile = root.getRelativePath(fileUriOrModule);
|
||||
return stripFileExtension(subFile!).replace(separatorRegExp, '.');
|
||||
}
|
||||
}
|
||||
|
||||
return filePathOrModule;
|
||||
} else if (filePathOrModule.nameParts) {
|
||||
return filePathOrModule.nameParts.join('.');
|
||||
return fileUriOrModule.toUserVisibleString();
|
||||
} else if (fileUriOrModule.nameParts) {
|
||||
return fileUriOrModule.nameParts.join('.');
|
||||
}
|
||||
}
|
||||
return '';
|
||||
@ -117,33 +115,33 @@ export function createTracePrinter(roots: string[]): TracePrinter {
|
||||
if (decl) {
|
||||
switch (decl.type) {
|
||||
case DeclarationType.Alias:
|
||||
return `Alias, ${printNode(decl.node)} (${printFileOrModuleName(decl.path)})`;
|
||||
return `Alias, ${printNode(decl.node)} (${printFileOrModuleName(decl.uri)})`;
|
||||
|
||||
case DeclarationType.Class:
|
||||
return `Class, ${printNode(decl.node)} (${printFileOrModuleName(decl.path)})`;
|
||||
return `Class, ${printNode(decl.node)} (${printFileOrModuleName(decl.uri)})`;
|
||||
|
||||
case DeclarationType.Function:
|
||||
return `Function, ${printNode(decl.node)} (${printFileOrModuleName(decl.path)})`;
|
||||
return `Function, ${printNode(decl.node)} (${printFileOrModuleName(decl.uri)})`;
|
||||
|
||||
case DeclarationType.Intrinsic:
|
||||
return `Intrinsic, ${printNode(decl.node)} ${decl.intrinsicType} (${printFileOrModuleName(
|
||||
decl.path
|
||||
decl.uri
|
||||
)})`;
|
||||
|
||||
case DeclarationType.Parameter:
|
||||
return `Parameter, ${printNode(decl.node)} (${printFileOrModuleName(decl.path)})`;
|
||||
return `Parameter, ${printNode(decl.node)} (${printFileOrModuleName(decl.uri)})`;
|
||||
|
||||
case DeclarationType.TypeParameter:
|
||||
return `TypeParameter, ${printNode(decl.node)} (${printFileOrModuleName(decl.path)})`;
|
||||
return `TypeParameter, ${printNode(decl.node)} (${printFileOrModuleName(decl.uri)})`;
|
||||
|
||||
case DeclarationType.SpecialBuiltInClass:
|
||||
return `SpecialBuiltInClass, ${printNode(decl.node)} (${printFileOrModuleName(decl.path)})`;
|
||||
return `SpecialBuiltInClass, ${printNode(decl.node)} (${printFileOrModuleName(decl.uri)})`;
|
||||
|
||||
case DeclarationType.Variable:
|
||||
return `Variable, ${printNode(decl.node)} (${printFileOrModuleName(decl.path)})`;
|
||||
return `Variable, ${printNode(decl.node)} (${printFileOrModuleName(decl.uri)})`;
|
||||
|
||||
case DeclarationType.TypeAlias:
|
||||
return `TypeAlias, ${printNode(decl.node)} (${printFileOrModuleName(decl.path)})`;
|
||||
return `TypeAlias, ${printNode(decl.node)} (${printFileOrModuleName(decl.uri)})`;
|
||||
|
||||
default:
|
||||
assertNever(decl);
|
||||
@ -174,7 +172,7 @@ export function createTracePrinter(roots: string[]): TracePrinter {
|
||||
return '';
|
||||
}
|
||||
|
||||
let path = printPath ? `(${printFileOrModuleName(getFileInfo(node)?.filePath)})` : '';
|
||||
let path = printPath ? `(${printFileOrModuleName(getFileInfo(node)?.fileUri)})` : '';
|
||||
|
||||
const fileInfo = getFileInfo(node);
|
||||
if (fileInfo?.lines) {
|
||||
@ -228,7 +226,7 @@ export function createTracePrinter(roots: string[]): TracePrinter {
|
||||
|
||||
function isDeclaration(o: any): o is Declaration {
|
||||
const d = o as Declaration;
|
||||
return d && isNumber(d.type) && isString(d.path) && isString(d.moduleName);
|
||||
return d && isNumber(d.type) && Uri.isUri(d.uri) && isString(d.moduleName);
|
||||
}
|
||||
|
||||
function isType(o: any): o is Type {
|
||||
|
@ -33,6 +33,7 @@ import {
|
||||
TypeCategory,
|
||||
} from '../analyzer/types';
|
||||
import { addIfNotNull, appendArray } from '../common/collectionUtils';
|
||||
import { Uri } from '../common/uri/uri';
|
||||
import { ModuleNode, ParseNodeType } from '../parser/parseNodes';
|
||||
import { TypeEvaluator } from './typeEvaluatorTypes';
|
||||
import {
|
||||
@ -160,7 +161,7 @@ export function getPropertyDocStringInherited(
|
||||
|
||||
export function getVariableInStubFileDocStrings(decl: VariableDeclaration, sourceMapper: SourceMapper) {
|
||||
const docStrings: string[] = [];
|
||||
if (!isStubFile(decl.path)) {
|
||||
if (!isStubFile(decl.uri)) {
|
||||
return docStrings;
|
||||
}
|
||||
|
||||
@ -193,14 +194,14 @@ export function getModuleDocStringFromModuleNodes(modules: ModuleNode[]): string
|
||||
return undefined;
|
||||
}
|
||||
|
||||
export function getModuleDocStringFromPaths(filePaths: string[], sourceMapper: SourceMapper) {
|
||||
export function getModuleDocStringFromUris(uris: Uri[], sourceMapper: SourceMapper) {
|
||||
const modules: ModuleNode[] = [];
|
||||
for (const filePath of filePaths) {
|
||||
if (isStubFile(filePath)) {
|
||||
addIfNotNull(modules, sourceMapper.getModuleNode(filePath));
|
||||
for (const uri of uris) {
|
||||
if (isStubFile(uri)) {
|
||||
addIfNotNull(modules, sourceMapper.getModuleNode(uri));
|
||||
}
|
||||
|
||||
appendArray(modules, sourceMapper.findModules(filePath));
|
||||
appendArray(modules, sourceMapper.findModules(uri));
|
||||
}
|
||||
|
||||
return getModuleDocStringFromModuleNodes(modules);
|
||||
@ -213,8 +214,8 @@ export function getModuleDocString(
|
||||
) {
|
||||
let docString = type.docString;
|
||||
if (!docString) {
|
||||
const filePath = resolvedDecl?.path ?? type.filePath;
|
||||
docString = getModuleDocStringFromPaths([filePath], sourceMapper);
|
||||
const uri = resolvedDecl?.uri ?? type.fileUri;
|
||||
docString = getModuleDocStringFromUris([uri], sourceMapper);
|
||||
}
|
||||
|
||||
return docString;
|
||||
@ -228,12 +229,7 @@ export function getClassDocString(
|
||||
let docString = classType.details.docString;
|
||||
if (!docString && resolvedDecl && isClassDeclaration(resolvedDecl)) {
|
||||
docString = _getFunctionOrClassDeclsDocString([resolvedDecl]);
|
||||
if (
|
||||
!docString &&
|
||||
resolvedDecl &&
|
||||
isStubFile(resolvedDecl.path) &&
|
||||
resolvedDecl.type === DeclarationType.Class
|
||||
) {
|
||||
if (!docString && resolvedDecl && isStubFile(resolvedDecl.uri) && resolvedDecl.type === DeclarationType.Class) {
|
||||
for (const implDecl of sourceMapper.findDeclarations(resolvedDecl)) {
|
||||
if (isVariableDeclaration(implDecl) && !!implDecl.docString) {
|
||||
docString = implDecl.docString;
|
||||
@ -249,7 +245,7 @@ export function getClassDocString(
|
||||
}
|
||||
|
||||
if (!docString && resolvedDecl) {
|
||||
const implDecls = sourceMapper.findClassDeclarationsByType(resolvedDecl.path, classType);
|
||||
const implDecls = sourceMapper.findClassDeclarationsByType(resolvedDecl.uri, classType);
|
||||
if (implDecls) {
|
||||
const classDecls = implDecls.filter((d) => isClassDeclaration(d)).map((d) => d);
|
||||
docString = _getFunctionOrClassDeclsDocString(classDecls);
|
||||
@ -294,7 +290,7 @@ function _getOverloadedFunctionDocStrings(
|
||||
docStrings.push(overload.details.docString);
|
||||
}
|
||||
});
|
||||
} else if (resolvedDecl && isStubFile(resolvedDecl.path) && isFunctionDeclaration(resolvedDecl)) {
|
||||
} else if (resolvedDecl && isStubFile(resolvedDecl.uri) && isFunctionDeclaration(resolvedDecl)) {
|
||||
const implDecls = sourceMapper.findFunctionDeclarations(resolvedDecl);
|
||||
const docString = _getFunctionOrClassDeclsDocString(implDecls);
|
||||
if (docString) {
|
||||
@ -372,7 +368,7 @@ function _getFunctionDocString(type: Type, resolvedDecl: FunctionDeclaration | u
|
||||
|
||||
function _getFunctionDocStringFromDeclaration(resolvedDecl: FunctionDeclaration, sourceMapper: SourceMapper) {
|
||||
let docString = _getFunctionOrClassDeclsDocString([resolvedDecl]);
|
||||
if (!docString && isStubFile(resolvedDecl.path)) {
|
||||
if (!docString && isStubFile(resolvedDecl.uri)) {
|
||||
const implDecls = sourceMapper.findFunctionDeclarations(resolvedDecl);
|
||||
docString = _getFunctionOrClassDeclsDocString(implDecls);
|
||||
}
|
||||
|
@ -26,6 +26,7 @@ import { DiagnosticRule } from '../common/diagnosticRules';
|
||||
import { convertOffsetToPosition, convertOffsetsToRange } from '../common/positionUtils';
|
||||
import { PythonVersion } from '../common/pythonVersion';
|
||||
import { TextRange } from '../common/textRange';
|
||||
import { Uri } from '../common/uri/uri';
|
||||
import { Localizer, ParameterizedString } from '../localization/localize';
|
||||
import {
|
||||
ArgumentCategory,
|
||||
@ -665,7 +666,7 @@ export function createTypeEvaluator(importLookup: ImportLookup, evaluatorOptions
|
||||
`Type cache flag mismatch for node type ${node.nodeType} ` +
|
||||
`(parent ${node.parent?.nodeType ?? 'none'}): ` +
|
||||
`cached flags = ${expectedFlags}, access flags = ${flags}, ` +
|
||||
`file = {${fileInfo.filePath} [${position.line + 1}:${position.character + 1}]}`;
|
||||
`file = {${fileInfo.fileUri} [${position.line + 1}:${position.character + 1}]}`;
|
||||
if (evaluatorOptions.verifyTypeCacheEvaluatorFlags) {
|
||||
fail(message);
|
||||
} else {
|
||||
@ -2895,7 +2896,7 @@ export function createTypeEvaluator(importLookup: ImportLookup, evaluatorOptions
|
||||
|
||||
function getTypeOfModule(node: ParseNode, symbolName: string, nameParts: string[]) {
|
||||
const fileInfo = AnalyzerNodeInfo.getFileInfo(node);
|
||||
const lookupResult = importLookup({ nameParts, importingFilePath: fileInfo.filePath });
|
||||
const lookupResult = importLookup({ nameParts, importingFileUri: fileInfo.fileUri });
|
||||
|
||||
if (!lookupResult) {
|
||||
return undefined;
|
||||
@ -5355,9 +5356,7 @@ export function createTypeEvaluator(importLookup: ImportLookup, evaluatorOptions
|
||||
if (getAttrSymbol) {
|
||||
const isModuleGetAttrSupported =
|
||||
fileInfo.executionEnvironment.pythonVersion >= PythonVersion.V3_7 ||
|
||||
getAttrSymbol
|
||||
.getDeclarations()
|
||||
.some((decl) => decl.path.toLowerCase().endsWith('.pyi'));
|
||||
getAttrSymbol.getDeclarations().some((decl) => decl.uri.hasExtension('.pyi'));
|
||||
|
||||
if (isModuleGetAttrSupported) {
|
||||
const getAttrTypeResult = getEffectiveTypeOfSymbolForUsage(getAttrSymbol);
|
||||
@ -8904,7 +8903,7 @@ export function createTypeEvaluator(importLookup: ImportLookup, evaluatorOptions
|
||||
if (diagnostic && overrideDecl) {
|
||||
diagnostic.addRelatedInfo(
|
||||
Localizer.DiagnosticAddendum.overloadIndex().format({ index: bestMatch.overloadIndex + 1 }),
|
||||
overrideDecl.path,
|
||||
overrideDecl.uri,
|
||||
overrideDecl.range
|
||||
);
|
||||
}
|
||||
@ -9717,7 +9716,7 @@ export function createTypeEvaluator(importLookup: ImportLookup, evaluatorOptions
|
||||
newClassName,
|
||||
'',
|
||||
'',
|
||||
AnalyzerNodeInfo.getFileInfo(errorNode).filePath,
|
||||
AnalyzerNodeInfo.getFileInfo(errorNode).fileUri,
|
||||
ClassTypeFlags.None,
|
||||
ParseTreeUtils.getTypeSourceId(errorNode),
|
||||
ClassType.cloneAsInstantiable(returnType),
|
||||
@ -12628,7 +12627,7 @@ export function createTypeEvaluator(importLookup: ImportLookup, evaluatorOptions
|
||||
className,
|
||||
ParseTreeUtils.getClassFullName(errorNode, fileInfo.moduleName, className),
|
||||
fileInfo.moduleName,
|
||||
fileInfo.filePath,
|
||||
fileInfo.fileUri,
|
||||
classFlags,
|
||||
ParseTreeUtils.getTypeSourceId(errorNode),
|
||||
/* declaredMetaclass */ undefined,
|
||||
@ -12689,7 +12688,7 @@ export function createTypeEvaluator(importLookup: ImportLookup, evaluatorOptions
|
||||
className,
|
||||
ParseTreeUtils.getClassFullName(errorNode, fileInfo.moduleName, className),
|
||||
fileInfo.moduleName,
|
||||
fileInfo.filePath,
|
||||
fileInfo.fileUri,
|
||||
ClassTypeFlags.None,
|
||||
ParseTreeUtils.getTypeSourceId(errorNode),
|
||||
/* declaredMetaclass */ undefined,
|
||||
@ -15269,7 +15268,7 @@ export function createTypeEvaluator(importLookup: ImportLookup, evaluatorOptions
|
||||
assignedName,
|
||||
ParseTreeUtils.getClassFullName(node, fileInfo.moduleName, assignedName),
|
||||
fileInfo.moduleName,
|
||||
fileInfo.filePath,
|
||||
fileInfo.fileUri,
|
||||
ClassTypeFlags.BuiltInClass | ClassTypeFlags.SpecialBuiltIn,
|
||||
/* typeSourceId */ 0,
|
||||
/* declaredMetaclass */ undefined,
|
||||
@ -15835,7 +15834,7 @@ export function createTypeEvaluator(importLookup: ImportLookup, evaluatorOptions
|
||||
node.name.value,
|
||||
ParseTreeUtils.getClassFullName(node, fileInfo.moduleName, node.name.value),
|
||||
fileInfo.moduleName,
|
||||
fileInfo.filePath,
|
||||
fileInfo.fileUri,
|
||||
classFlags,
|
||||
/* typeSourceId */ 0,
|
||||
/* declaredMetaclass */ undefined,
|
||||
@ -16615,7 +16614,16 @@ export function createTypeEvaluator(importLookup: ImportLookup, evaluatorOptions
|
||||
);
|
||||
const updatedClassType = ClassType.cloneWithNewTypeParameters(classType, updatedTypeParams);
|
||||
|
||||
const dummyTypeObject = ClassType.createInstantiable('__varianceDummy', '', '', '', 0, 0, undefined, undefined);
|
||||
const dummyTypeObject = ClassType.createInstantiable(
|
||||
'__varianceDummy',
|
||||
'',
|
||||
'',
|
||||
Uri.empty(),
|
||||
0,
|
||||
0,
|
||||
undefined,
|
||||
undefined
|
||||
);
|
||||
|
||||
updatedTypeParams.forEach((param, paramIndex) => {
|
||||
// Skip variadics and ParamSpecs.
|
||||
@ -16999,7 +17007,7 @@ export function createTypeEvaluator(importLookup: ImportLookup, evaluatorOptions
|
||||
Localizer.DiagnosticAddendum.initSubclassLocation().format({
|
||||
name: printType(convertToInstance(initSubclassMethodInfo.classType)),
|
||||
}),
|
||||
initSubclassDecl.path,
|
||||
initSubclassDecl.uri,
|
||||
initSubclassDecl.range
|
||||
);
|
||||
}
|
||||
@ -18381,7 +18389,7 @@ export function createTypeEvaluator(importLookup: ImportLookup, evaluatorOptions
|
||||
|
||||
const importInfo = AnalyzerNodeInfo.getImportInfo(parentNode.module);
|
||||
if (importInfo && importInfo.isImportFound && !importInfo.isNativeLib) {
|
||||
const resolvedPath = importInfo.resolvedPaths[importInfo.resolvedPaths.length - 1];
|
||||
const resolvedPath = importInfo.resolvedUris[importInfo.resolvedUris.length - 1];
|
||||
|
||||
const importLookupInfo = importLookup(resolvedPath);
|
||||
let reportError = false;
|
||||
@ -18403,7 +18411,7 @@ export function createTypeEvaluator(importLookup: ImportLookup, evaluatorOptions
|
||||
}
|
||||
}
|
||||
}
|
||||
} else if (!resolvedPath) {
|
||||
} else if (resolvedPath.isEmpty()) {
|
||||
// This corresponds to the "from . import a" form.
|
||||
reportError = true;
|
||||
}
|
||||
@ -20207,15 +20215,15 @@ export function createTypeEvaluator(importLookup: ImportLookup, evaluatorOptions
|
||||
namePartIndex >= 0 &&
|
||||
importInfo &&
|
||||
!importInfo.isNativeLib &&
|
||||
namePartIndex < importInfo.resolvedPaths.length
|
||||
namePartIndex < importInfo.resolvedUris.length
|
||||
) {
|
||||
if (importInfo.resolvedPaths[namePartIndex]) {
|
||||
if (importInfo.resolvedUris[namePartIndex]) {
|
||||
evaluateTypesForStatement(node);
|
||||
|
||||
// Synthesize an alias declaration for this name part. The only
|
||||
// time this case is used is for IDE services such as
|
||||
// the find all references, hover provider and etc.
|
||||
declarations.push(createSynthesizedAliasDeclaration(importInfo.resolvedPaths[namePartIndex]));
|
||||
declarations.push(createSynthesizedAliasDeclaration(importInfo.resolvedUris[namePartIndex]));
|
||||
}
|
||||
}
|
||||
} else if (node.parent && node.parent.nodeType === ParseNodeType.Argument && node === node.parent.name) {
|
||||
@ -20634,8 +20642,8 @@ export function createTypeEvaluator(importLookup: ImportLookup, evaluatorOptions
|
||||
loaderActions: ModuleLoaderActions,
|
||||
importLookup: ImportLookup
|
||||
): Type {
|
||||
if (loaderActions.path && loaderActions.loadSymbolsFromPath) {
|
||||
const lookupResults = importLookup(loaderActions.path);
|
||||
if (!loaderActions.uri.isEmpty() && loaderActions.loadSymbolsFromPath) {
|
||||
const lookupResults = importLookup(loaderActions.uri);
|
||||
if (lookupResults) {
|
||||
moduleType.fields = lookupResults.symbolTable;
|
||||
moduleType.docString = lookupResults.docString;
|
||||
@ -20658,7 +20666,7 @@ export function createTypeEvaluator(importLookup: ImportLookup, evaluatorOptions
|
||||
symbolType = UnknownType.create();
|
||||
} else {
|
||||
const moduleName = moduleType.moduleName ? moduleType.moduleName + '.' + name : '';
|
||||
const importedModuleType = ModuleType.create(moduleName, implicitImport.path);
|
||||
const importedModuleType = ModuleType.create(moduleName, implicitImport.uri);
|
||||
symbolType = applyLoaderActionsToModuleType(importedModuleType, implicitImport, importLookup);
|
||||
}
|
||||
|
||||
@ -20676,7 +20684,7 @@ export function createTypeEvaluator(importLookup: ImportLookup, evaluatorOptions
|
||||
if (resolvedDecl.type === DeclarationType.Alias) {
|
||||
// Build a module type that corresponds to the declaration and
|
||||
// its associated loader actions.
|
||||
const moduleType = ModuleType.create(resolvedDecl.moduleName, resolvedDecl.path);
|
||||
const moduleType = ModuleType.create(resolvedDecl.moduleName, resolvedDecl.uri);
|
||||
if (resolvedDecl.symbolName && resolvedDecl.submoduleFallback) {
|
||||
return applyLoaderActionsToModuleType(moduleType, resolvedDecl.submoduleFallback, importLookup);
|
||||
} else {
|
||||
|
@ -1480,7 +1480,7 @@ function narrowTypeForIsInstance(
|
||||
className,
|
||||
ParseTreeUtils.getClassFullName(errorNode, fileInfo.moduleName, className),
|
||||
fileInfo.moduleName,
|
||||
fileInfo.filePath,
|
||||
fileInfo.fileUri,
|
||||
ClassTypeFlags.None,
|
||||
ParseTreeUtils.getTypeSourceId(errorNode),
|
||||
/* declaredMetaclass */ undefined,
|
||||
@ -2545,7 +2545,7 @@ function narrowTypeForCallable(
|
||||
className,
|
||||
ParseTreeUtils.getClassFullName(errorNode, fileInfo.moduleName, className),
|
||||
fileInfo.moduleName,
|
||||
fileInfo.filePath,
|
||||
fileInfo.fileUri,
|
||||
ClassTypeFlags.None,
|
||||
ParseTreeUtils.getTypeSourceId(errorNode),
|
||||
/* declaredMetaclass */ undefined,
|
||||
|
@ -8,6 +8,7 @@
|
||||
* and analyzed python source file.
|
||||
*/
|
||||
|
||||
import { Uri } from '../common/uri/uri';
|
||||
import {
|
||||
ArgumentCategory,
|
||||
AssignmentNode,
|
||||
@ -158,13 +159,13 @@ export class TypeStubWriter extends ParseTreeWalker {
|
||||
private _trackedImportFrom = new Map<string, TrackedImportFrom>();
|
||||
private _accessedImportedSymbols = new Set<string>();
|
||||
|
||||
constructor(private _stubPath: string, private _sourceFile: SourceFile, private _evaluator: TypeEvaluator) {
|
||||
constructor(private _stubPath: Uri, private _sourceFile: SourceFile, private _evaluator: TypeEvaluator) {
|
||||
super();
|
||||
|
||||
// As a heuristic, we'll include all of the import statements
|
||||
// in "__init__.pyi" files even if they're not locally referenced
|
||||
// because these are often used as ways to re-export symbols.
|
||||
if (this._stubPath.endsWith('__init__.pyi')) {
|
||||
if (this._stubPath.fileName === '__init__.pyi') {
|
||||
this._includeAllImports = true;
|
||||
}
|
||||
}
|
||||
|
@ -97,7 +97,7 @@ export function createTypedDictType(
|
||||
className,
|
||||
ParseTreeUtils.getClassFullName(errorNode, fileInfo.moduleName, className),
|
||||
fileInfo.moduleName,
|
||||
fileInfo.filePath,
|
||||
fileInfo.fileUri,
|
||||
ClassTypeFlags.TypedDictClass,
|
||||
ParseTreeUtils.getTypeSourceId(errorNode),
|
||||
/* declaredMetaclass */ undefined,
|
||||
@ -147,7 +147,7 @@ export function createTypedDictType(
|
||||
const declaration: VariableDeclaration = {
|
||||
type: DeclarationType.Variable,
|
||||
node: entry.name,
|
||||
path: fileInfo.filePath,
|
||||
uri: fileInfo.fileUri,
|
||||
typeAnnotationNode: entry.valueExpression,
|
||||
isRuntimeTypeExpression: true,
|
||||
range: convertOffsetsToRange(
|
||||
@ -209,7 +209,7 @@ export function createTypedDictTypeInlined(
|
||||
className,
|
||||
ParseTreeUtils.getClassFullName(dictNode, fileInfo.moduleName, className),
|
||||
fileInfo.moduleName,
|
||||
fileInfo.filePath,
|
||||
fileInfo.fileUri,
|
||||
ClassTypeFlags.TypedDictClass,
|
||||
ParseTreeUtils.getTypeSourceId(dictNode),
|
||||
/* declaredMetaclass */ undefined,
|
||||
@ -773,7 +773,7 @@ function getTypedDictFieldsFromDictSyntax(
|
||||
const declaration: VariableDeclaration = {
|
||||
type: DeclarationType.Variable,
|
||||
node: entry.keyExpression,
|
||||
path: fileInfo.filePath,
|
||||
uri: fileInfo.fileUri,
|
||||
typeAnnotationNode: entry.valueExpression,
|
||||
isRuntimeTypeExpression: !isInline,
|
||||
range: convertOffsetsToRange(
|
||||
|
@ -8,6 +8,7 @@
|
||||
*/
|
||||
|
||||
import { assert } from '../common/debug';
|
||||
import { Uri } from '../common/uri/uri';
|
||||
import { ArgumentNode, ExpressionNode, NameNode, ParameterCategory } from '../parser/parseNodes';
|
||||
import { FunctionDeclaration } from './declaration';
|
||||
import { Symbol, SymbolTable } from './symbol';
|
||||
@ -383,18 +384,18 @@ export interface ModuleType extends TypeBase {
|
||||
// The period-delimited import name of this module.
|
||||
moduleName: string;
|
||||
|
||||
filePath: string;
|
||||
fileUri: Uri;
|
||||
}
|
||||
|
||||
export namespace ModuleType {
|
||||
export function create(moduleName: string, filePath: string, symbolTable?: SymbolTable) {
|
||||
export function create(moduleName: string, fileUri: Uri, symbolTable?: SymbolTable) {
|
||||
const newModuleType: ModuleType = {
|
||||
category: TypeCategory.Module,
|
||||
fields: symbolTable || new Map<string, Symbol>(),
|
||||
loaderFields: new Map<string, Symbol>(),
|
||||
flags: TypeFlags.Instantiable | TypeFlags.Instantiable,
|
||||
moduleName,
|
||||
filePath,
|
||||
fileUri,
|
||||
};
|
||||
return newModuleType;
|
||||
}
|
||||
@ -561,7 +562,7 @@ interface ClassDetails {
|
||||
name: string;
|
||||
fullName: string;
|
||||
moduleName: string;
|
||||
filePath: string;
|
||||
fileUri: Uri;
|
||||
flags: ClassTypeFlags;
|
||||
typeSourceId: TypeSourceId;
|
||||
baseClasses: Type[];
|
||||
@ -709,7 +710,7 @@ export namespace ClassType {
|
||||
name: string,
|
||||
fullName: string,
|
||||
moduleName: string,
|
||||
filePath: string,
|
||||
fileUri: Uri,
|
||||
flags: ClassTypeFlags,
|
||||
typeSourceId: TypeSourceId,
|
||||
declaredMetaclass: ClassType | UnknownType | undefined,
|
||||
@ -722,7 +723,7 @@ export namespace ClassType {
|
||||
name,
|
||||
fullName,
|
||||
moduleName,
|
||||
filePath,
|
||||
fileUri,
|
||||
flags,
|
||||
typeSourceId,
|
||||
baseClasses: [],
|
||||
|
@ -13,17 +13,17 @@ import { BackgroundAnalysisBase, BackgroundAnalysisRunnerBase } from './backgrou
|
||||
import { InitializationData } from './backgroundThreadBase';
|
||||
import { getCancellationFolderName } from './common/cancellationUtils';
|
||||
import { ConfigOptions } from './common/configOptions';
|
||||
import { ConsoleInterface } from './common/console';
|
||||
import { FullAccessHost } from './common/fullAccessHost';
|
||||
import { Host } from './common/host';
|
||||
import { ServiceProvider } from './common/serviceProvider';
|
||||
import { getRootUri } from './common/uri/uriUtils';
|
||||
|
||||
export class BackgroundAnalysis extends BackgroundAnalysisBase {
|
||||
constructor(console: ConsoleInterface) {
|
||||
super(console);
|
||||
constructor(serviceProvider: ServiceProvider) {
|
||||
super(serviceProvider.console());
|
||||
|
||||
const initialData: InitializationData = {
|
||||
rootDirectory: (global as any).__rootDirectory as string,
|
||||
rootUri: getRootUri(serviceProvider.fs().isCaseSensitive)?.toString() ?? '',
|
||||
cancellationFolderName: getCancellationFolderName(),
|
||||
runner: undefined,
|
||||
};
|
||||
@ -40,7 +40,7 @@ export class BackgroundAnalysisRunner extends BackgroundAnalysisRunnerBase {
|
||||
}
|
||||
|
||||
protected override createHost(): Host {
|
||||
return new FullAccessHost(this.fs);
|
||||
return new FullAccessHost(this.getServiceProvider());
|
||||
}
|
||||
|
||||
protected override createImportResolver(
|
||||
|
@ -10,6 +10,7 @@ import { CancellationToken } from 'vscode-languageserver';
|
||||
import { MessageChannel, MessagePort, Worker, parentPort, threadId, workerData } from 'worker_threads';
|
||||
|
||||
import { AnalysisCompleteCallback, AnalysisResults, analyzeProgram, nullCallback } from './analyzer/analysis';
|
||||
import { BackgroundAnalysisProgram, InvalidatedReason } from './analyzer/backgroundAnalysisProgram';
|
||||
import { ImportResolver } from './analyzer/importResolver';
|
||||
import { OpenFileOptions, Program } from './analyzer/program';
|
||||
import {
|
||||
@ -33,9 +34,9 @@ import { FileDiagnostics } from './common/diagnosticSink';
|
||||
import { disposeCancellationToken, getCancellationTokenFromId } from './common/fileBasedCancellationUtils';
|
||||
import { Host, HostKind } from './common/host';
|
||||
import { LogTracker } from './common/logTracker';
|
||||
import { Range } from './common/textRange';
|
||||
import { BackgroundAnalysisProgram, InvalidatedReason } from './analyzer/backgroundAnalysisProgram';
|
||||
import { ServiceProvider } from './common/serviceProvider';
|
||||
import { Range } from './common/textRange';
|
||||
import { Uri } from './common/uri/uri';
|
||||
|
||||
export class BackgroundAnalysisBase {
|
||||
private _worker: Worker | undefined;
|
||||
@ -57,8 +58,8 @@ export class BackgroundAnalysisBase {
|
||||
this.enqueueRequest({ requestType: 'setConfigOptions', data: configOptions });
|
||||
}
|
||||
|
||||
setTrackedFiles(filePaths: string[]) {
|
||||
this.enqueueRequest({ requestType: 'setTrackedFiles', data: filePaths });
|
||||
setTrackedFiles(fileUris: Uri[]) {
|
||||
this.enqueueRequest({ requestType: 'setTrackedFiles', data: fileUris.map((f) => f.toString()) });
|
||||
}
|
||||
|
||||
setAllowedThirdPartyImports(importNames: string[]) {
|
||||
@ -69,36 +70,36 @@ export class BackgroundAnalysisBase {
|
||||
this.enqueueRequest({ requestType: 'ensurePartialStubPackages', data: { executionRoot } });
|
||||
}
|
||||
|
||||
setFileOpened(filePath: string, version: number | null, contents: string, options: OpenFileOptions) {
|
||||
setFileOpened(fileUri: Uri, version: number | null, contents: string, options: OpenFileOptions) {
|
||||
this.enqueueRequest({
|
||||
requestType: 'setFileOpened',
|
||||
data: { filePath, version, contents, options },
|
||||
data: { fileUri: fileUri.toString(), version, contents, options },
|
||||
});
|
||||
}
|
||||
|
||||
updateChainedFilePath(filePath: string, chainedFilePath: string | undefined) {
|
||||
updateChainedUri(fileUri: Uri, chainedUri: Uri | undefined) {
|
||||
this.enqueueRequest({
|
||||
requestType: 'updateChainedFilePath',
|
||||
data: { filePath, chainedFilePath },
|
||||
requestType: 'updateChainedFileUri',
|
||||
data: { fileUri: fileUri.toString(), chainedUri: chainedUri?.toString() },
|
||||
});
|
||||
}
|
||||
|
||||
setFileClosed(filePath: string, isTracked?: boolean) {
|
||||
this.enqueueRequest({ requestType: 'setFileClosed', data: { filePath, isTracked } });
|
||||
setFileClosed(fileUri: Uri, isTracked?: boolean) {
|
||||
this.enqueueRequest({ requestType: 'setFileClosed', data: { fileUri, isTracked } });
|
||||
}
|
||||
|
||||
addInterimFile(filePath: string) {
|
||||
this.enqueueRequest({ requestType: 'addInterimFile', data: { filePath } });
|
||||
addInterimFile(fileUri: Uri) {
|
||||
this.enqueueRequest({ requestType: 'addInterimFile', data: { fileUri: fileUri.toString() } });
|
||||
}
|
||||
|
||||
markAllFilesDirty(evenIfContentsAreSame: boolean) {
|
||||
this.enqueueRequest({ requestType: 'markAllFilesDirty', data: { evenIfContentsAreSame } });
|
||||
}
|
||||
|
||||
markFilesDirty(filePaths: string[], evenIfContentsAreSame: boolean) {
|
||||
markFilesDirty(fileUris: Uri[], evenIfContentsAreSame: boolean) {
|
||||
this.enqueueRequest({
|
||||
requestType: 'markFilesDirty',
|
||||
data: { filePaths, evenIfContentsAreSame },
|
||||
data: { fileUris: fileUris.map((f) => f.toString()), evenIfContentsAreSame },
|
||||
});
|
||||
}
|
||||
|
||||
@ -106,7 +107,7 @@ export class BackgroundAnalysisBase {
|
||||
this._startOrResumeAnalysis('analyze', program, token);
|
||||
}
|
||||
|
||||
async analyzeFile(filePath: string, token: CancellationToken): Promise<boolean> {
|
||||
async analyzeFile(fileUri: Uri, token: CancellationToken): Promise<boolean> {
|
||||
throwIfCancellationRequested(token);
|
||||
|
||||
const { port1, port2 } = new MessageChannel();
|
||||
@ -115,7 +116,7 @@ export class BackgroundAnalysisBase {
|
||||
const cancellationId = getCancellationTokenId(token);
|
||||
this.enqueueRequest({
|
||||
requestType: 'analyzeFile',
|
||||
data: { filePath, cancellationId },
|
||||
data: { fileUri: fileUri.toString(), cancellationId },
|
||||
port: port2,
|
||||
});
|
||||
|
||||
@ -127,7 +128,7 @@ export class BackgroundAnalysisBase {
|
||||
return result;
|
||||
}
|
||||
|
||||
async getDiagnosticsForRange(filePath: string, range: Range, token: CancellationToken): Promise<Diagnostic[]> {
|
||||
async getDiagnosticsForRange(fileUri: Uri, range: Range, token: CancellationToken): Promise<Diagnostic[]> {
|
||||
throwIfCancellationRequested(token);
|
||||
|
||||
const { port1, port2 } = new MessageChannel();
|
||||
@ -136,7 +137,7 @@ export class BackgroundAnalysisBase {
|
||||
const cancellationId = getCancellationTokenId(token);
|
||||
this.enqueueRequest({
|
||||
requestType: 'getDiagnosticsForRange',
|
||||
data: { filePath, range, cancellationId },
|
||||
data: { fileUri: fileUri.toString(), range, cancellationId },
|
||||
port: port2,
|
||||
});
|
||||
|
||||
@ -149,9 +150,9 @@ export class BackgroundAnalysisBase {
|
||||
}
|
||||
|
||||
async writeTypeStub(
|
||||
targetImportPath: string,
|
||||
targetImportPath: Uri,
|
||||
targetIsSingleFile: boolean,
|
||||
stubPath: string,
|
||||
stubPath: Uri,
|
||||
token: CancellationToken
|
||||
): Promise<any> {
|
||||
throwIfCancellationRequested(token);
|
||||
@ -162,7 +163,12 @@ export class BackgroundAnalysisBase {
|
||||
const cancellationId = getCancellationTokenId(token);
|
||||
this.enqueueRequest({
|
||||
requestType: 'writeTypeStub',
|
||||
data: { targetImportPath, targetIsSingleFile, stubPath, cancellationId },
|
||||
data: {
|
||||
targetImportPath: targetImportPath.toString(),
|
||||
targetIsSingleFile,
|
||||
stubPath: stubPath.toString(),
|
||||
cancellationId,
|
||||
},
|
||||
port: port2,
|
||||
});
|
||||
|
||||
@ -283,15 +289,15 @@ export abstract class BackgroundAnalysisRunnerBase extends BackgroundThreadBase
|
||||
|
||||
protected importResolver: ImportResolver;
|
||||
protected logTracker: LogTracker;
|
||||
protected isCaseSensitive = true;
|
||||
|
||||
protected constructor(serviceProvider: ServiceProvider) {
|
||||
super(workerData as InitializationData, serviceProvider);
|
||||
|
||||
// Stash the base directory into a global variable.
|
||||
const data = workerData as InitializationData;
|
||||
this.log(LogLevel.Info, `Background analysis(${threadId}) root directory: ${data.rootDirectory}`);
|
||||
|
||||
this._configOptions = new ConfigOptions(data.rootDirectory);
|
||||
this.log(LogLevel.Info, `Background analysis(${threadId}) root directory: ${data.rootUri}`);
|
||||
this._configOptions = new ConfigOptions(Uri.parse(data.rootUri, serviceProvider.fs().isCaseSensitive));
|
||||
this.importResolver = this.createImportResolver(serviceProvider, this._configOptions, this.createHost());
|
||||
|
||||
const console = this.getConsole();
|
||||
@ -339,20 +345,20 @@ export abstract class BackgroundAnalysisRunnerBase extends BackgroundThreadBase
|
||||
|
||||
case 'analyzeFile': {
|
||||
run(() => {
|
||||
const { filePath, cancellationId } = msg.data;
|
||||
const { fileUri, cancellationId } = msg.data;
|
||||
const token = getCancellationTokenFromId(cancellationId);
|
||||
|
||||
return this.handleAnalyzeFile(filePath, token);
|
||||
return this.handleAnalyzeFile(fileUri, token);
|
||||
}, msg.port!);
|
||||
break;
|
||||
}
|
||||
|
||||
case 'getDiagnosticsForRange': {
|
||||
run(() => {
|
||||
const { filePath, range, cancellationId } = msg.data;
|
||||
const { fileUri, range, cancellationId } = msg.data;
|
||||
const token = getCancellationTokenFromId(cancellationId);
|
||||
|
||||
return this.handleGetDiagnosticsForRange(filePath, range, token);
|
||||
return this.handleGetDiagnosticsForRange(fileUri, range, token);
|
||||
}, msg.port!);
|
||||
break;
|
||||
}
|
||||
@ -394,26 +400,26 @@ export abstract class BackgroundAnalysisRunnerBase extends BackgroundThreadBase
|
||||
}
|
||||
|
||||
case 'setFileOpened': {
|
||||
const { filePath, version, contents, options } = msg.data;
|
||||
this.handleSetFileOpened(filePath, version, contents, options);
|
||||
const { fileUri, version, contents, options } = msg.data;
|
||||
this.handleSetFileOpened(fileUri, version, contents, options);
|
||||
break;
|
||||
}
|
||||
|
||||
case 'updateChainedFilePath': {
|
||||
const { filePath, chainedFilePath } = msg.data;
|
||||
this.handleUpdateChainedFilePath(filePath, chainedFilePath);
|
||||
case 'updateChainedFileUri': {
|
||||
const { fileUri, chainedUri } = msg.data;
|
||||
this.handleUpdateChainedfileUri(fileUri, chainedUri);
|
||||
break;
|
||||
}
|
||||
|
||||
case 'setFileClosed': {
|
||||
const { filePath, isTracked } = msg.data;
|
||||
this.handleSetFileClosed(filePath, isTracked);
|
||||
const { fileUri, isTracked } = msg.data;
|
||||
this.handleSetFileClosed(fileUri, isTracked);
|
||||
break;
|
||||
}
|
||||
|
||||
case 'addInterimFile': {
|
||||
const { filePath } = msg.data;
|
||||
this.handleAddInterimFile(filePath);
|
||||
const { fileUri } = msg.data;
|
||||
this.handleAddInterimFile(fileUri);
|
||||
break;
|
||||
}
|
||||
|
||||
@ -424,8 +430,8 @@ export abstract class BackgroundAnalysisRunnerBase extends BackgroundThreadBase
|
||||
}
|
||||
|
||||
case 'markFilesDirty': {
|
||||
const { filePaths, evenIfContentsAreSame } = msg.data;
|
||||
this.handleMarkFilesDirty(filePaths, evenIfContentsAreSame);
|
||||
const { fileUris, evenIfContentsAreSame } = msg.data;
|
||||
this.handleMarkFilesDirty(fileUris, evenIfContentsAreSame);
|
||||
break;
|
||||
}
|
||||
|
||||
@ -499,14 +505,14 @@ export abstract class BackgroundAnalysisRunnerBase extends BackgroundThreadBase
|
||||
}
|
||||
}
|
||||
|
||||
protected handleAnalyzeFile(filePath: string, token: CancellationToken) {
|
||||
protected handleAnalyzeFile(fileUri: string, token: CancellationToken) {
|
||||
throwIfCancellationRequested(token);
|
||||
return this.program.analyzeFile(filePath, token);
|
||||
return this.program.analyzeFile(Uri.parse(fileUri, this.isCaseSensitive), token);
|
||||
}
|
||||
|
||||
protected handleGetDiagnosticsForRange(filePath: string, range: Range, token: CancellationToken) {
|
||||
protected handleGetDiagnosticsForRange(fileUri: string, range: Range, token: CancellationToken) {
|
||||
throwIfCancellationRequested(token);
|
||||
return this.program.getDiagnosticsForRange(filePath, range);
|
||||
return this.program.getDiagnosticsForRange(Uri.parse(fileUri, this.isCaseSensitive), range);
|
||||
}
|
||||
|
||||
protected handleWriteTypeStub(
|
||||
@ -524,7 +530,12 @@ export abstract class BackgroundAnalysisRunnerBase extends BackgroundThreadBase
|
||||
token
|
||||
);
|
||||
|
||||
this.program.writeTypeStub(targetImportPath, targetIsSingleFile, stubPath, token);
|
||||
this.program.writeTypeStub(
|
||||
Uri.parse(targetImportPath, this.isCaseSensitive),
|
||||
targetIsSingleFile,
|
||||
Uri.parse(stubPath, this.isCaseSensitive),
|
||||
token
|
||||
);
|
||||
}
|
||||
|
||||
protected handleSetImportResolver(hostKind: HostKind) {
|
||||
@ -548,8 +559,8 @@ export abstract class BackgroundAnalysisRunnerBase extends BackgroundThreadBase
|
||||
this.program.setImportResolver(this.importResolver);
|
||||
}
|
||||
|
||||
protected handleSetTrackedFiles(filePaths: string[]) {
|
||||
const diagnostics = this.program.setTrackedFiles(filePaths);
|
||||
protected handleSetTrackedFiles(fileUris: string[]) {
|
||||
const diagnostics = this.program.setTrackedFiles(fileUris.map((f) => Uri.parse(f, this.isCaseSensitive)));
|
||||
this._reportDiagnostics(diagnostics, this.program.getFilesToAnalyzeCount(), 0);
|
||||
}
|
||||
|
||||
@ -565,29 +576,35 @@ export abstract class BackgroundAnalysisRunnerBase extends BackgroundThreadBase
|
||||
}
|
||||
|
||||
protected handleSetFileOpened(
|
||||
filePath: string,
|
||||
fileUri: string,
|
||||
version: number | null,
|
||||
contents: string,
|
||||
options: OpenFileOptions | undefined
|
||||
) {
|
||||
this.program.setFileOpened(filePath, version, contents, options);
|
||||
this.program.setFileOpened(Uri.parse(fileUri, this.isCaseSensitive), version, contents, options);
|
||||
}
|
||||
|
||||
protected handleUpdateChainedFilePath(filePath: string, chainedFilePath: string | undefined) {
|
||||
this.program.updateChainedFilePath(filePath, chainedFilePath);
|
||||
protected handleUpdateChainedfileUri(fileUri: string, chainedfileUri: string | undefined) {
|
||||
this.program.updateChainedUri(
|
||||
Uri.parse(fileUri, this.isCaseSensitive),
|
||||
chainedfileUri ? Uri.parse(chainedfileUri, this.isCaseSensitive) : undefined
|
||||
);
|
||||
}
|
||||
|
||||
protected handleSetFileClosed(filePath: string, isTracked: boolean | undefined) {
|
||||
const diagnostics = this.program.setFileClosed(filePath, isTracked);
|
||||
protected handleSetFileClosed(fileUri: string, isTracked: boolean | undefined) {
|
||||
const diagnostics = this.program.setFileClosed(Uri.parse(fileUri, this.isCaseSensitive), isTracked);
|
||||
this._reportDiagnostics(diagnostics, this.program.getFilesToAnalyzeCount(), 0);
|
||||
}
|
||||
|
||||
protected handleAddInterimFile(filePath: string) {
|
||||
this.program.addInterimFile(filePath);
|
||||
protected handleAddInterimFile(fileUri: string) {
|
||||
this.program.addInterimFile(Uri.parse(fileUri, this.isCaseSensitive));
|
||||
}
|
||||
|
||||
protected handleMarkFilesDirty(filePaths: string[], evenIfContentsAreSame: boolean) {
|
||||
this.program.markFilesDirty(filePaths, evenIfContentsAreSame);
|
||||
protected handleMarkFilesDirty(fileUris: string[], evenIfContentsAreSame: boolean) {
|
||||
this.program.markFilesDirty(
|
||||
fileUris.map((f) => Uri.parse(f, this.isCaseSensitive)),
|
||||
evenIfContentsAreSame
|
||||
);
|
||||
}
|
||||
|
||||
protected handleMarkAllFilesDirty(evenIfContentsAreSame: boolean) {
|
||||
@ -666,7 +683,7 @@ export abstract class BackgroundAnalysisRunnerBase extends BackgroundThreadBase
|
||||
function convertAnalysisResults(result: AnalysisResults): AnalysisResults {
|
||||
result.diagnostics = result.diagnostics.map((f: FileDiagnostics) => {
|
||||
return {
|
||||
filePath: f.filePath,
|
||||
fileUri: f.fileUri,
|
||||
version: f.version,
|
||||
diagnostics: convertDiagnostics(f.diagnostics),
|
||||
};
|
||||
@ -692,7 +709,7 @@ function convertDiagnostics(diagnostics: Diagnostic[]) {
|
||||
|
||||
if (d._relatedInfo) {
|
||||
for (const info of d._relatedInfo) {
|
||||
diag.addRelatedInfo(info.message, info.filePath, info.range);
|
||||
diag.addRelatedInfo(info.message, info.fileUri, info.range);
|
||||
}
|
||||
}
|
||||
|
||||
@ -708,7 +725,7 @@ export type AnalysisRequestKind =
|
||||
| 'setAllowedThirdPartyImports'
|
||||
| 'ensurePartialStubPackages'
|
||||
| 'setFileOpened'
|
||||
| 'updateChainedFilePath'
|
||||
| 'updateChainedFileUri'
|
||||
| 'setFileClosed'
|
||||
| 'markAllFilesDirty'
|
||||
| 'markFilesDirty'
|
||||
|
@ -12,11 +12,12 @@ import { OperationCanceledException, setCancellationFolderName } from './common/
|
||||
import { ConfigOptions } from './common/configOptions';
|
||||
import { ConsoleInterface, LogLevel } from './common/console';
|
||||
import * as debug from './common/debug';
|
||||
import { FileSpec } from './common/pathUtils';
|
||||
import { createFromRealFileSystem, RealTempFile } from './common/realFileSystem';
|
||||
import { ServiceProvider } from './common/serviceProvider';
|
||||
import './common/serviceProviderExtensions';
|
||||
import { ServiceKeys } from './common/serviceProviderExtensions';
|
||||
import { Uri } from './common/uri/uri';
|
||||
import { FileSpec } from './common/uri/uriUtils';
|
||||
|
||||
export class BackgroundConsole implements ConsoleInterface {
|
||||
// We always generate logs in the background. For the foreground,
|
||||
@ -48,9 +49,6 @@ export class BackgroundThreadBase {
|
||||
protected constructor(data: InitializationData, serviceProvider?: ServiceProvider) {
|
||||
setCancellationFolderName(data.cancellationFolderName);
|
||||
|
||||
// Stash the base directory into a global variable.
|
||||
(global as any).__rootDirectory = data.rootDirectory;
|
||||
|
||||
// Make sure there's a file system and a console interface.
|
||||
this._serviceProvider = serviceProvider ?? new ServiceProvider();
|
||||
if (!this._serviceProvider.tryGet(ServiceKeys.console)) {
|
||||
@ -60,8 +58,17 @@ export class BackgroundThreadBase {
|
||||
this._serviceProvider.add(ServiceKeys.fs, createFromRealFileSystem(this.getConsole()));
|
||||
}
|
||||
if (!this._serviceProvider.tryGet(ServiceKeys.tempFile)) {
|
||||
this._serviceProvider.add(ServiceKeys.tempFile, new RealTempFile());
|
||||
this._serviceProvider.add(
|
||||
ServiceKeys.tempFile,
|
||||
new RealTempFile(this._serviceProvider.fs().isCaseSensitive)
|
||||
);
|
||||
}
|
||||
|
||||
// Stash the base directory into a global variable.
|
||||
(global as any).__rootDirectory = Uri.parse(
|
||||
data.rootUri,
|
||||
this._serviceProvider.fs().isCaseSensitive
|
||||
).getFilePath();
|
||||
}
|
||||
|
||||
protected get fs() {
|
||||
@ -170,7 +177,7 @@ export function getBackgroundWaiter<T>(port: MessagePort): Promise<T> {
|
||||
}
|
||||
|
||||
export interface InitializationData {
|
||||
rootDirectory: string;
|
||||
rootUri: string;
|
||||
cancellationFolderName: string | undefined;
|
||||
runner: string | undefined;
|
||||
title?: string;
|
||||
|
@ -9,6 +9,7 @@
|
||||
import { CancellationToken, ExecuteCommandParams } from 'vscode-languageserver';
|
||||
|
||||
import { OperationCanceledException } from '../common/cancellationUtils';
|
||||
import { Uri } from '../common/uri/uri';
|
||||
import { LanguageServerInterface } from '../languageServerBase';
|
||||
import { AnalyzerServiceExecutor } from '../languageService/analyzerServiceExecutor';
|
||||
import { ServerCommand } from './commandController';
|
||||
@ -18,9 +19,9 @@ export class CreateTypeStubCommand implements ServerCommand {
|
||||
|
||||
async execute(cmdParams: ExecuteCommandParams, token: CancellationToken): Promise<any> {
|
||||
if (cmdParams.arguments && cmdParams.arguments.length >= 2) {
|
||||
const workspaceRoot = cmdParams.arguments[0] as string;
|
||||
const workspaceRoot = Uri.parse(cmdParams.arguments[0] as string, this._ls.rootUri.isCaseSensitive);
|
||||
const importName = cmdParams.arguments[1] as string;
|
||||
const callingFile = cmdParams.arguments[2] as string;
|
||||
const callingFile = Uri.parse(cmdParams.arguments[2] as string, this._ls.rootUri.isCaseSensitive);
|
||||
|
||||
const service = await AnalyzerServiceExecutor.cloneService(
|
||||
this._ls,
|
||||
|
@ -29,6 +29,7 @@ import { isNumber, isString } from '../common/core';
|
||||
import { convertOffsetToPosition, convertOffsetsToRange } from '../common/positionUtils';
|
||||
import { TextRange } from '../common/textRange';
|
||||
import { TextRangeCollection } from '../common/textRangeCollection';
|
||||
import { Uri } from '../common/uri/uri';
|
||||
import { LanguageServerInterface } from '../languageServerBase';
|
||||
import {
|
||||
ArgumentCategory,
|
||||
@ -132,11 +133,11 @@ export class DumpFileDebugInfoCommand implements ServerCommand {
|
||||
return [];
|
||||
}
|
||||
|
||||
const filePath = params.arguments[0] as string;
|
||||
const fileUri = Uri.parse(params.arguments[0] as string, this._ls.rootUri.isCaseSensitive);
|
||||
const kind = params.arguments[1];
|
||||
|
||||
const workspace = await this._ls.getWorkspaceForFile(filePath);
|
||||
const parseResults = workspace.service.getParseResult(workspace.service.fs.realCasePath(filePath));
|
||||
const workspace = await this._ls.getWorkspaceForFile(fileUri);
|
||||
const parseResults = workspace.service.getParseResult(workspace.service.fs.realCasePath(fileUri));
|
||||
if (!parseResults) {
|
||||
return [];
|
||||
}
|
||||
@ -157,7 +158,7 @@ export class DumpFileDebugInfoCommand implements ServerCommand {
|
||||
},
|
||||
};
|
||||
|
||||
collectingConsole.info(`* Dump debug info for '${filePath}'`);
|
||||
collectingConsole.info(`* Dump debug info for '${fileUri.toUserVisibleString()}'`);
|
||||
|
||||
switch (kind) {
|
||||
case 'tokens': {
|
||||
@ -166,7 +167,7 @@ export class DumpFileDebugInfoCommand implements ServerCommand {
|
||||
for (let i = 0; i < parseResults.tokenizerOutput.tokens.count; i++) {
|
||||
const token = parseResults.tokenizerOutput.tokens.getItemAt(i);
|
||||
collectingConsole.info(
|
||||
`[${i}] ${getTokenString(filePath, token, parseResults.tokenizerOutput.lines)}`
|
||||
`[${i}] ${getTokenString(fileUri, token, parseResults.tokenizerOutput.lines)}`
|
||||
);
|
||||
}
|
||||
break;
|
||||
@ -174,7 +175,7 @@ export class DumpFileDebugInfoCommand implements ServerCommand {
|
||||
case 'nodes': {
|
||||
collectingConsole.info(`* Node info`);
|
||||
|
||||
const dumper = new TreeDumper(filePath, parseResults.tokenizerOutput.lines);
|
||||
const dumper = new TreeDumper(fileUri, parseResults.tokenizerOutput.lines);
|
||||
dumper.walk(parseResults.parseTree);
|
||||
|
||||
collectingConsole.info(dumper.output);
|
||||
@ -189,7 +190,7 @@ export class DumpFileDebugInfoCommand implements ServerCommand {
|
||||
}
|
||||
|
||||
collectingConsole.info(`* Type info`);
|
||||
collectingConsole.info(`${getTypeEvaluatorString(filePath, evaluator, parseResults, start, end)}`);
|
||||
collectingConsole.info(`${getTypeEvaluatorString(fileUri, evaluator, parseResults, start, end)}`);
|
||||
break;
|
||||
}
|
||||
case 'cachedtypes': {
|
||||
@ -201,9 +202,7 @@ export class DumpFileDebugInfoCommand implements ServerCommand {
|
||||
}
|
||||
|
||||
collectingConsole.info(`* Cached Type info`);
|
||||
collectingConsole.info(
|
||||
`${getTypeEvaluatorString(filePath, evaluator, parseResults, start, end, true)}`
|
||||
);
|
||||
collectingConsole.info(`${getTypeEvaluatorString(fileUri, evaluator, parseResults, start, end, true)}`);
|
||||
break;
|
||||
}
|
||||
|
||||
@ -239,14 +238,14 @@ function stringify(value: any, replacer: (this: any, key: string, value: any) =>
|
||||
}
|
||||
|
||||
function getTypeEvaluatorString(
|
||||
file: string,
|
||||
uri: Uri,
|
||||
evaluator: TypeEvaluator,
|
||||
results: ParseResults,
|
||||
start: number,
|
||||
end: number,
|
||||
cacheOnly?: boolean
|
||||
) {
|
||||
const dumper = new TreeDumper(file, results.tokenizerOutput.lines);
|
||||
const dumper = new TreeDumper(uri, results.tokenizerOutput.lines);
|
||||
const node = findNodeByOffset(results.parseTree, start) ?? findNodeByOffset(results.parseTree, end);
|
||||
if (!node) {
|
||||
return 'N/A';
|
||||
@ -549,7 +548,7 @@ class TreeDumper extends ParseTreeWalker {
|
||||
private _indentation = '';
|
||||
private _output = '';
|
||||
|
||||
constructor(private _file: string, private _lines: TextRangeCollection<TextRange>) {
|
||||
constructor(private _uri: Uri, private _lines: TextRangeCollection<TextRange>) {
|
||||
super();
|
||||
}
|
||||
|
||||
@ -604,7 +603,7 @@ class TreeDumper extends ParseTreeWalker {
|
||||
override visitBinaryOperation(node: BinaryOperationNode) {
|
||||
this._log(
|
||||
`${this._getPrefix(node)} ${getTokenString(
|
||||
this._file,
|
||||
this._uri,
|
||||
node.operatorToken,
|
||||
this._lines
|
||||
)} ${getOperatorTypeString(node.operator)}} parenthesized:(${node.parenthesized})`
|
||||
@ -697,7 +696,7 @@ class TreeDumper extends ParseTreeWalker {
|
||||
`${this._getPrefix(node)} wildcard import:(${node.isWildcardImport}) paren:(${
|
||||
node.usesParens
|
||||
}) wildcard token:(${
|
||||
node.wildcardToken ? getTokenString(this._file, node.wildcardToken, this._lines) : 'N/A'
|
||||
node.wildcardToken ? getTokenString(this._uri, node.wildcardToken, this._lines) : 'N/A'
|
||||
}) missing import keyword:(${node.missingImportKeyword})`
|
||||
);
|
||||
return true;
|
||||
@ -784,7 +783,7 @@ class TreeDumper extends ParseTreeWalker {
|
||||
}
|
||||
|
||||
override visitName(node: NameNode) {
|
||||
this._log(`${this._getPrefix(node)} ${getTokenString(this._file, node.token, this._lines)} ${node.value}`);
|
||||
this._log(`${this._getPrefix(node)} ${getTokenString(this._uri, node.token, this._lines)} ${node.value}`);
|
||||
return true;
|
||||
}
|
||||
|
||||
@ -834,7 +833,7 @@ class TreeDumper extends ParseTreeWalker {
|
||||
}
|
||||
|
||||
override visitString(node: StringNode) {
|
||||
this._log(`${this._getPrefix(node)} ${getTokenString(this._file, node.token, this._lines)} ${node.value}`);
|
||||
this._log(`${this._getPrefix(node)} ${getTokenString(this._uri, node.token, this._lines)} ${node.value}`);
|
||||
return true;
|
||||
}
|
||||
|
||||
@ -866,7 +865,7 @@ class TreeDumper extends ParseTreeWalker {
|
||||
override visitUnaryOperation(node: UnaryOperationNode) {
|
||||
this._log(
|
||||
`${this._getPrefix(node)} ${getTokenString(
|
||||
this._file,
|
||||
this._uri,
|
||||
node.operatorToken,
|
||||
this._lines
|
||||
)} ${getOperatorTypeString(node.operator)}`
|
||||
@ -988,7 +987,7 @@ class TreeDumper extends ParseTreeWalker {
|
||||
private _getPrefix(node: ParseNode) {
|
||||
const pos = convertOffsetToPosition(node.start, this._lines);
|
||||
// VS code's output window expects 1 based values, print the line/char with 1 based.
|
||||
return `[${node.id}] '${this._file}:${pos.line + 1}:${pos.character + 1}' => ${printParseNodeType(
|
||||
return `[${node.id}] '${this._uri.toString()}:${pos.line + 1}:${pos.character + 1}' => ${printParseNodeType(
|
||||
node.nodeType
|
||||
)} ${getTextSpanString(node, this._lines)} =>`;
|
||||
}
|
||||
@ -1066,9 +1065,9 @@ function getErrorExpressionCategoryString(type: ErrorExpressionCategory) {
|
||||
}
|
||||
}
|
||||
|
||||
function getTokenString(file: string, token: Token, lines: TextRangeCollection<TextRange>) {
|
||||
function getTokenString(uri: Uri, token: Token, lines: TextRangeCollection<TextRange>) {
|
||||
const pos = convertOffsetToPosition(token.start, lines);
|
||||
let str = `'${file}:${pos.line + 1}:${pos.character + 1}' (`;
|
||||
let str = `'${uri.toUserVisibleString()}:${pos.line + 1}:${pos.character + 1}' (`;
|
||||
str += getTokenTypeString(token.type);
|
||||
str += getNewLineInfo(token);
|
||||
str += getOperatorInfo(token);
|
||||
|
@ -8,6 +8,7 @@
|
||||
|
||||
import { CancellationToken, ExecuteCommandParams } from 'vscode-languageserver';
|
||||
|
||||
import { Uri } from '../common/uri/uri';
|
||||
import { convertToFileTextEdits, convertToWorkspaceEdit } from '../common/workspaceEditUtils';
|
||||
import { LanguageServerInterface } from '../languageServerBase';
|
||||
import { performQuickAction } from '../languageService/quickActions';
|
||||
@ -19,20 +20,19 @@ export class QuickActionCommand implements ServerCommand {
|
||||
|
||||
async execute(params: ExecuteCommandParams, token: CancellationToken): Promise<any> {
|
||||
if (params.arguments && params.arguments.length >= 1) {
|
||||
const docUri = params.arguments[0] as string;
|
||||
const docUri = Uri.parse(params.arguments[0] as string, this._ls.rootUri.isCaseSensitive);
|
||||
const otherArgs = params.arguments.slice(1);
|
||||
const filePath = this._ls.decodeTextDocumentUri(docUri);
|
||||
const workspace = await this._ls.getWorkspaceForFile(filePath);
|
||||
const workspace = await this._ls.getWorkspaceForFile(docUri);
|
||||
|
||||
if (params.command === Commands.orderImports && workspace.disableOrganizeImports) {
|
||||
return [];
|
||||
}
|
||||
|
||||
const editActions = workspace.service.run((p) => {
|
||||
return performQuickAction(p, filePath, params.command, otherArgs, token);
|
||||
return performQuickAction(p, docUri, params.command, otherArgs, token);
|
||||
}, token);
|
||||
|
||||
return convertToWorkspaceEdit(workspace.service.fs, convertToFileTextEdits(filePath, editActions ?? []));
|
||||
return convertToWorkspaceEdit(convertToFileTextEdits(docUri, editActions ?? []));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -18,19 +18,11 @@ import { TaskListToken } from './diagnostic';
|
||||
import { DiagnosticRule } from './diagnosticRules';
|
||||
import { FileSystem } from './fileSystem';
|
||||
import { Host } from './host';
|
||||
import {
|
||||
FileSpec,
|
||||
combinePaths,
|
||||
ensureTrailingDirectorySeparator,
|
||||
getFileSpec,
|
||||
isDirectory,
|
||||
normalizePath,
|
||||
realCasePath,
|
||||
resolvePaths,
|
||||
} from './pathUtils';
|
||||
import { PythonVersion, latestStablePythonVersion, versionFromString, versionToString } from './pythonVersion';
|
||||
import { ServiceProvider } from './serviceProvider';
|
||||
import { ServiceKeys } from './serviceProviderExtensions';
|
||||
import { Uri } from './uri/uri';
|
||||
import { FileSpec, getFileSpec, isDirectory } from './uri/uriUtils';
|
||||
|
||||
export enum PythonPlatform {
|
||||
Darwin = 'Darwin',
|
||||
@ -39,10 +31,9 @@ export enum PythonPlatform {
|
||||
}
|
||||
|
||||
export class ExecutionEnvironment {
|
||||
// Root directory for execution - absolute or relative to the
|
||||
// project root.
|
||||
// Root directory for execution.
|
||||
// Undefined if this is a rootless environment (e.g., open file mode).
|
||||
root?: string;
|
||||
root?: Uri;
|
||||
|
||||
// Name of a virtual environment if there is one, otherwise
|
||||
// just the path to the python executable.
|
||||
@ -55,18 +46,18 @@ export class ExecutionEnvironment {
|
||||
pythonPlatform?: string | undefined;
|
||||
|
||||
// Default to no extra paths.
|
||||
extraPaths: string[] = [];
|
||||
extraPaths: Uri[] = [];
|
||||
|
||||
// Default to "." which indicates every file in the project.
|
||||
constructor(
|
||||
name: string,
|
||||
root: string,
|
||||
root: Uri,
|
||||
defaultPythonVersion: PythonVersion | undefined,
|
||||
defaultPythonPlatform: string | undefined,
|
||||
defaultExtraPaths: string[] | undefined
|
||||
defaultExtraPaths: Uri[] | undefined
|
||||
) {
|
||||
this.name = name;
|
||||
this.root = root || undefined;
|
||||
this.root = root;
|
||||
this.pythonVersion = defaultPythonVersion || latestStablePythonVersion;
|
||||
this.pythonPlatform = defaultPythonPlatform;
|
||||
this.extraPaths = Array.from(defaultExtraPaths ?? []);
|
||||
@ -780,9 +771,9 @@ export function getStrictDiagnosticRuleSet(): DiagnosticRuleSet {
|
||||
return diagSettings;
|
||||
}
|
||||
|
||||
export function matchFileSpecs(configOptions: ConfigOptions, filePath: string, isFile = true) {
|
||||
export function matchFileSpecs(configOptions: ConfigOptions, uri: Uri, isFile = true) {
|
||||
for (const includeSpec of configOptions.include) {
|
||||
if (FileSpec.matchIncludeFileSpec(includeSpec.regExp, configOptions.exclude, filePath, isFile)) {
|
||||
if (FileSpec.matchIncludeFileSpec(includeSpec.regExp, configOptions.exclude, uri, isFile)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@ -795,19 +786,19 @@ export function matchFileSpecs(configOptions: ConfigOptions, filePath: string, i
|
||||
export class ConfigOptions {
|
||||
// Absolute directory of project. All relative paths in the config
|
||||
// are based on this path.
|
||||
projectRoot: string;
|
||||
projectRoot: Uri;
|
||||
|
||||
// Path to python interpreter.
|
||||
pythonPath?: string | undefined;
|
||||
pythonPath?: Uri | undefined;
|
||||
|
||||
// Name of the python environment.
|
||||
pythonEnvironmentName?: string | undefined;
|
||||
|
||||
// Path to use for typeshed definitions.
|
||||
typeshedPath?: string | undefined;
|
||||
typeshedPath?: Uri | undefined;
|
||||
|
||||
// Path to custom typings (stub) modules.
|
||||
stubPath?: string | undefined;
|
||||
stubPath?: Uri | undefined;
|
||||
|
||||
// A list of file specs to include in the analysis. Can contain
|
||||
// directories, in which case all "*.py" files within those directories
|
||||
@ -887,7 +878,7 @@ export class ConfigOptions {
|
||||
// directories. This is used in conjunction with the "venv" name in
|
||||
// the config file to identify the python environment used for resolving
|
||||
// third-party modules.
|
||||
venvPath?: string | undefined;
|
||||
venvPath?: Uri | undefined;
|
||||
|
||||
// Default venv environment.
|
||||
venv?: string | undefined;
|
||||
@ -899,7 +890,7 @@ export class ConfigOptions {
|
||||
defaultPythonPlatform?: string | undefined;
|
||||
|
||||
// Default extraPaths. Can be overridden by executionEnvironment.
|
||||
defaultExtraPaths?: string[] | undefined;
|
||||
defaultExtraPaths?: Uri[] | undefined;
|
||||
|
||||
//---------------------------------------------------------------
|
||||
// Internal-only switches
|
||||
@ -917,7 +908,7 @@ export class ConfigOptions {
|
||||
// Controls how hover and completion function signatures are displayed.
|
||||
functionSignatureDisplay: SignatureDisplayType;
|
||||
|
||||
constructor(projectRoot: string, typeCheckingMode?: string) {
|
||||
constructor(projectRoot: Uri, typeCheckingMode?: string) {
|
||||
this.projectRoot = projectRoot;
|
||||
this.typeCheckingMode = typeCheckingMode;
|
||||
this.diagnosticRuleSet = ConfigOptions.getDiagnosticRuleSet(typeCheckingMode);
|
||||
@ -950,17 +941,15 @@ export class ConfigOptions {
|
||||
);
|
||||
}
|
||||
|
||||
// Finds the best execution environment for a given file path. The
|
||||
// Finds the best execution environment for a given file uri. The
|
||||
// specified file path should be absolute.
|
||||
// If no matching execution environment can be found, a default
|
||||
// execution environment is used.
|
||||
findExecEnvironment(filePath: string): ExecutionEnvironment {
|
||||
findExecEnvironment(file: Uri): ExecutionEnvironment {
|
||||
return (
|
||||
this.executionEnvironments.find((env) => {
|
||||
const envRoot = ensureTrailingDirectorySeparator(
|
||||
normalizePath(combinePaths(this.projectRoot, env.root))
|
||||
);
|
||||
return filePath.startsWith(envRoot);
|
||||
const envRoot = Uri.isUri(env.root) ? env.root : this.projectRoot.combinePaths(env.root || '');
|
||||
return file.startsWith(envRoot);
|
||||
}) ?? this.getDefaultExecEnvironment()
|
||||
);
|
||||
}
|
||||
@ -997,7 +986,7 @@ export class ConfigOptions {
|
||||
} else if (isAbsolute(fileSpec)) {
|
||||
console.error(`Ignoring path "${fileSpec}" in "include" array because it is not relative.`);
|
||||
} else {
|
||||
this.include.push(getFileSpec(serviceProvider, this.projectRoot, fileSpec));
|
||||
this.include.push(getFileSpec(this.projectRoot, fileSpec));
|
||||
}
|
||||
});
|
||||
}
|
||||
@ -1016,7 +1005,7 @@ export class ConfigOptions {
|
||||
} else if (isAbsolute(fileSpec)) {
|
||||
console.error(`Ignoring path "${fileSpec}" in "exclude" array because it is not relative.`);
|
||||
} else {
|
||||
this.exclude.push(getFileSpec(serviceProvider, this.projectRoot, fileSpec));
|
||||
this.exclude.push(getFileSpec(this.projectRoot, fileSpec));
|
||||
}
|
||||
});
|
||||
}
|
||||
@ -1035,7 +1024,7 @@ export class ConfigOptions {
|
||||
} else if (isAbsolute(fileSpec)) {
|
||||
console.error(`Ignoring path "${fileSpec}" in "ignore" array because it is not relative.`);
|
||||
} else {
|
||||
this.ignore.push(getFileSpec(serviceProvider, this.projectRoot, fileSpec));
|
||||
this.ignore.push(getFileSpec(this.projectRoot, fileSpec));
|
||||
}
|
||||
});
|
||||
}
|
||||
@ -1054,7 +1043,7 @@ export class ConfigOptions {
|
||||
} else if (isAbsolute(fileSpec)) {
|
||||
console.error(`Ignoring path "${fileSpec}" in "strict" array because it is not relative.`);
|
||||
} else {
|
||||
this.strict.push(getFileSpec(serviceProvider, this.projectRoot, fileSpec));
|
||||
this.strict.push(getFileSpec(this.projectRoot, fileSpec));
|
||||
}
|
||||
});
|
||||
}
|
||||
@ -1116,7 +1105,7 @@ export class ConfigOptions {
|
||||
if (typeof configObj.venvPath !== 'string') {
|
||||
console.error(`Config "venvPath" field must contain a string.`);
|
||||
} else {
|
||||
this.venvPath = normalizePath(combinePaths(this.projectRoot, configObj.venvPath));
|
||||
this.venvPath = this.projectRoot.combinePaths(configObj.venvPath);
|
||||
}
|
||||
}
|
||||
|
||||
@ -1141,7 +1130,7 @@ export class ConfigOptions {
|
||||
if (typeof path !== 'string') {
|
||||
console.error(`Config "extraPaths" field ${pathIndex} must be a string.`);
|
||||
} else {
|
||||
this.defaultExtraPaths!.push(normalizePath(combinePaths(this.projectRoot, path)));
|
||||
this.defaultExtraPaths!.push(this.projectRoot.combinePaths(path));
|
||||
}
|
||||
});
|
||||
}
|
||||
@ -1181,8 +1170,8 @@ export class ConfigOptions {
|
||||
console.error(`Config "typeshedPath" field must contain a string.`);
|
||||
} else {
|
||||
this.typeshedPath = configObj.typeshedPath
|
||||
? normalizePath(combinePaths(this.projectRoot, configObj.typeshedPath))
|
||||
: '';
|
||||
? this.projectRoot.combinePaths(configObj.typeshedPath)
|
||||
: undefined;
|
||||
}
|
||||
}
|
||||
|
||||
@ -1195,7 +1184,7 @@ export class ConfigOptions {
|
||||
console.error(`Config "typingsPath" field must contain a string.`);
|
||||
} else {
|
||||
console.error(`Config "typingsPath" is now deprecated. Please, use stubPath instead.`);
|
||||
this.stubPath = normalizePath(combinePaths(this.projectRoot, configObj.typingsPath));
|
||||
this.stubPath = this.projectRoot.combinePaths(configObj.typingsPath);
|
||||
}
|
||||
}
|
||||
|
||||
@ -1203,7 +1192,7 @@ export class ConfigOptions {
|
||||
if (typeof configObj.stubPath !== 'string') {
|
||||
console.error(`Config "stubPath" field must contain a string.`);
|
||||
} else {
|
||||
this.stubPath = normalizePath(combinePaths(this.projectRoot, configObj.stubPath));
|
||||
this.stubPath = this.projectRoot.combinePaths(configObj.stubPath);
|
||||
}
|
||||
}
|
||||
|
||||
@ -1345,20 +1334,20 @@ export class ConfigOptions {
|
||||
}
|
||||
|
||||
ensureDefaultExtraPaths(fs: FileSystem, autoSearchPaths: boolean, extraPaths: string[] | undefined) {
|
||||
const paths: string[] = [];
|
||||
const paths: Uri[] = [];
|
||||
|
||||
if (autoSearchPaths) {
|
||||
// Auto-detect the common scenario where the sources are under the src folder
|
||||
const srcPath = resolvePaths(this.projectRoot, pathConsts.src);
|
||||
if (fs.existsSync(srcPath) && !fs.existsSync(resolvePaths(srcPath, '__init__.py'))) {
|
||||
paths.push(realCasePath(srcPath, fs));
|
||||
const srcPath = this.projectRoot.combinePaths(pathConsts.src);
|
||||
if (fs.existsSync(srcPath) && !fs.existsSync(srcPath.combinePaths('__init__.py'))) {
|
||||
paths.push(fs.realCasePath(srcPath));
|
||||
}
|
||||
}
|
||||
|
||||
if (extraPaths && extraPaths.length > 0) {
|
||||
for (const p of extraPaths) {
|
||||
const path = resolvePaths(this.projectRoot, p);
|
||||
paths.push(realCasePath(path, fs));
|
||||
const path = this.projectRoot.combinePaths(p);
|
||||
paths.push(fs.realCasePath(path));
|
||||
if (isDirectory(fs, path)) {
|
||||
appendArray(paths, getPathsFromPthFiles(fs, path));
|
||||
}
|
||||
@ -1384,7 +1373,7 @@ export class ConfigOptions {
|
||||
}
|
||||
|
||||
private _getEnvironmentName(): string {
|
||||
return this.pythonEnvironmentName || this.pythonPath || 'python';
|
||||
return this.pythonEnvironmentName || this.pythonPath?.toString() || 'python';
|
||||
}
|
||||
|
||||
private _convertBoolean(value: any, fieldName: string, defaultValue: boolean): boolean {
|
||||
@ -1429,7 +1418,7 @@ export class ConfigOptions {
|
||||
|
||||
// Validate the root.
|
||||
if (envObj.root && typeof envObj.root === 'string') {
|
||||
newExecEnv.root = normalizePath(combinePaths(this.projectRoot, envObj.root));
|
||||
newExecEnv.root = this.projectRoot.combinePaths(envObj.root);
|
||||
} else {
|
||||
console.error(`Config executionEnvironments index ${index}: missing root value.`);
|
||||
}
|
||||
@ -1449,7 +1438,7 @@ export class ConfigOptions {
|
||||
` extraPaths field ${pathIndex} must be a string.`
|
||||
);
|
||||
} else {
|
||||
newExecEnv.extraPaths.push(normalizePath(combinePaths(this.projectRoot, path)));
|
||||
newExecEnv.extraPaths.push(this.projectRoot.combinePaths(path));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
@ -11,6 +11,7 @@ import { Commands } from '../commands/commands';
|
||||
import { appendArray } from './collectionUtils';
|
||||
import { DiagnosticLevel } from './configOptions';
|
||||
import { Range, TextRange } from './textRange';
|
||||
import { Uri } from './uri/uri';
|
||||
|
||||
const defaultMaxDepth = 5;
|
||||
const defaultMaxLineCount = 8;
|
||||
@ -63,7 +64,7 @@ export interface DiagnosticAction {
|
||||
}
|
||||
|
||||
export interface DiagnosticWithinFile {
|
||||
filePath: string;
|
||||
uri: Uri;
|
||||
diagnostic: Diagnostic;
|
||||
}
|
||||
|
||||
@ -74,13 +75,13 @@ export interface CreateTypeStubFileAction extends DiagnosticAction {
|
||||
|
||||
export interface RenameShadowedFileAction extends DiagnosticAction {
|
||||
action: ActionKind.RenameShadowedFileAction;
|
||||
oldFile: string;
|
||||
newFile: string;
|
||||
oldUri: Uri;
|
||||
newUri: Uri;
|
||||
}
|
||||
|
||||
export interface DiagnosticRelatedInfo {
|
||||
message: string;
|
||||
filePath: string;
|
||||
uri: Uri;
|
||||
range: Range;
|
||||
priority: TaskListPriority;
|
||||
}
|
||||
@ -118,13 +119,8 @@ export class Diagnostic {
|
||||
return this._rule;
|
||||
}
|
||||
|
||||
addRelatedInfo(
|
||||
message: string,
|
||||
filePath: string,
|
||||
range: Range,
|
||||
priority: TaskListPriority = TaskListPriority.Normal
|
||||
) {
|
||||
this._relatedInfo.push({ filePath, message, range, priority });
|
||||
addRelatedInfo(message: string, fileUri: Uri, range: Range, priority: TaskListPriority = TaskListPriority.Normal) {
|
||||
this._relatedInfo.push({ uri: fileUri, message, range, priority });
|
||||
}
|
||||
|
||||
getRelatedInfo() {
|
||||
|
@ -14,10 +14,11 @@ import { convertOffsetsToRange } from './positionUtils';
|
||||
import { hashString } from './stringUtils';
|
||||
import { Range, TextRange } from './textRange';
|
||||
import { TextRangeCollection } from './textRangeCollection';
|
||||
import { Uri } from './uri/uri';
|
||||
|
||||
// Represents a collection of diagnostics within a file.
|
||||
export interface FileDiagnostics {
|
||||
filePath: string;
|
||||
fileUri: Uri;
|
||||
version: number | undefined;
|
||||
diagnostics: Diagnostic[];
|
||||
}
|
||||
|
@ -8,6 +8,7 @@
|
||||
*/
|
||||
|
||||
import { Range, rangesAreEqual } from './textRange';
|
||||
import { Uri } from './uri/uri';
|
||||
|
||||
export interface TextEditAction {
|
||||
range: Range;
|
||||
@ -15,7 +16,7 @@ export interface TextEditAction {
|
||||
}
|
||||
|
||||
export interface FileEditAction extends TextEditAction {
|
||||
filePath: string;
|
||||
fileUri: Uri;
|
||||
}
|
||||
|
||||
export interface FileEditActions {
|
||||
@ -31,18 +32,18 @@ export interface FileOperation {
|
||||
|
||||
export interface RenameFileOperation extends FileOperation {
|
||||
kind: 'rename';
|
||||
oldFilePath: string;
|
||||
newFilePath: string;
|
||||
oldFileUri: Uri;
|
||||
newFileUri: Uri;
|
||||
}
|
||||
|
||||
export interface CreateFileOperation extends FileOperation {
|
||||
kind: 'create';
|
||||
filePath: string;
|
||||
fileUri: Uri;
|
||||
}
|
||||
|
||||
export interface DeleteFileOperation extends FileOperation {
|
||||
kind: 'delete';
|
||||
filePath: string;
|
||||
fileUri: Uri;
|
||||
}
|
||||
|
||||
export namespace TextEditAction {
|
||||
@ -53,13 +54,13 @@ export namespace TextEditAction {
|
||||
|
||||
export namespace FileEditAction {
|
||||
export function is(value: any): value is FileEditAction {
|
||||
return value.filePath !== undefined && TextEditAction.is(value);
|
||||
return value.fileUri !== undefined && TextEditAction.is(value);
|
||||
}
|
||||
|
||||
export function areEqual(e1: FileEditAction, e2: FileEditAction) {
|
||||
return (
|
||||
e1 === e2 ||
|
||||
(e1.filePath === e2.filePath &&
|
||||
(e1.fileUri.equals(e2.fileUri) &&
|
||||
rangesAreEqual(e1.range, e2.range) &&
|
||||
e1.replacementText === e2.replacementText)
|
||||
);
|
||||
|
@ -8,43 +8,35 @@
|
||||
|
||||
import * as os from 'os';
|
||||
|
||||
import {
|
||||
combinePaths,
|
||||
ensureTrailingDirectorySeparator,
|
||||
getPathComponents,
|
||||
hasTrailingDirectorySeparator,
|
||||
} from './pathUtils';
|
||||
import { Uri } from './uri/uri';
|
||||
|
||||
// Expands certain predefined variables supported within VS Code settings.
|
||||
// Ideally, VS Code would provide an API for doing this expansion, but
|
||||
// it doesn't. We'll handle the most common variables here as a convenience.
|
||||
export function expandPathVariables(rootPath: string, path: string): string {
|
||||
const pathParts = getPathComponents(path);
|
||||
export function expandPathVariables(rootPath: Uri, path: string): string {
|
||||
// Make sure the pathStr looks like a URI path.
|
||||
let pathStr = path.replace(/\\/g, '/');
|
||||
|
||||
const expandedParts: string[] = [];
|
||||
for (const part of pathParts) {
|
||||
const trimmedPart = part.trim();
|
||||
// Make sure all replacements look like URI paths too.
|
||||
const replace = (match: RegExp, replaceValue: string) => {
|
||||
pathStr = pathStr.replace(match, replaceValue.replace(/\\/g, '/'));
|
||||
};
|
||||
|
||||
if (trimmedPart === '${workspaceFolder}') {
|
||||
expandedParts.push(rootPath);
|
||||
} else if (trimmedPart === '${env:HOME}' && process.env.HOME !== undefined) {
|
||||
expandedParts.push(process.env.HOME);
|
||||
} else if (trimmedPart === '${env:USERNAME}' && process.env.USERNAME !== undefined) {
|
||||
expandedParts.push(process.env.USERNAME);
|
||||
} else if (trimmedPart === '${env:VIRTUAL_ENV}' && process.env.VIRTUAL_ENV !== undefined) {
|
||||
expandedParts.push(process.env.VIRTUAL_ENV);
|
||||
} else if (trimmedPart === '~' && os.homedir) {
|
||||
expandedParts.push(os.homedir() || process.env.HOME || process.env.USERPROFILE || '~');
|
||||
} else {
|
||||
expandedParts.push(part);
|
||||
// Replace everything inline.
|
||||
pathStr = pathStr.replace(/\$\{workspaceFolder\}/g, rootPath.getPath());
|
||||
if (process.env.HOME !== undefined) {
|
||||
replace(/\$\{env:HOME\}/g, process.env.HOME || '');
|
||||
}
|
||||
if (process.env.USERNAME !== undefined) {
|
||||
replace(/\$\{env:USERNAME\}/g, process.env.USERNAME || '');
|
||||
}
|
||||
if (process.env.VIRTUAL_ENV !== undefined) {
|
||||
replace(/\$\{env:VIRTUAL_ENV\}/g, process.env.VIRTUAL_ENV || '');
|
||||
}
|
||||
if (os.homedir) {
|
||||
replace(/\/~/g, os.homedir() || process.env.HOME || process.env.USERPROFILE || '~');
|
||||
replace(/^~/g, os.homedir() || process.env.HOME || process.env.USERPROFILE || '~');
|
||||
}
|
||||
|
||||
if (expandedParts.length === 0) {
|
||||
return path;
|
||||
}
|
||||
|
||||
const root = expandedParts.shift()!;
|
||||
const expandedPath = combinePaths(root, ...expandedParts);
|
||||
return hasTrailingDirectorySeparator(path) ? ensureTrailingDirectorySeparator(expandedPath) : expandedPath;
|
||||
return pathStr;
|
||||
}
|
||||
|
@ -11,19 +11,20 @@ import { CancellationToken } from 'vscode-languageserver';
|
||||
import { Declaration } from '../analyzer/declaration';
|
||||
import { ImportResolver } from '../analyzer/importResolver';
|
||||
import * as prog from '../analyzer/program';
|
||||
import { IPythonMode } from '../analyzer/sourceFile';
|
||||
import { SourceMapper } from '../analyzer/sourceMapper';
|
||||
import { SymbolTable } from '../analyzer/symbol';
|
||||
import { TypeEvaluator } from '../analyzer/typeEvaluatorTypes';
|
||||
import { Diagnostic } from '../common/diagnostic';
|
||||
import { ServerSettings } from '../languageServerBase';
|
||||
import { ParseNode } from '../parser/parseNodes';
|
||||
import { ParseResults } from '../parser/parser';
|
||||
import { ConfigOptions } from './configOptions';
|
||||
import { ConsoleInterface } from './console';
|
||||
import { ReadOnlyFileSystem } from './fileSystem';
|
||||
import { Range } from './textRange';
|
||||
import { SymbolTable } from '../analyzer/symbol';
|
||||
import { Diagnostic } from '../common/diagnostic';
|
||||
import { IPythonMode } from '../analyzer/sourceFile';
|
||||
import { GroupServiceKey, ServiceKey } from './serviceProvider';
|
||||
import { Range } from './textRange';
|
||||
import { Uri } from './uri/uri';
|
||||
|
||||
export interface SourceFile {
|
||||
// See whether we can convert these to regular properties.
|
||||
@ -31,9 +32,8 @@ export interface SourceFile {
|
||||
isThirdPartyPyTypedPresent(): boolean;
|
||||
|
||||
getIPythonMode(): IPythonMode;
|
||||
getFilePath(): string;
|
||||
getUri(): Uri;
|
||||
getFileContent(): string | undefined;
|
||||
getRealFilePath(): string | undefined;
|
||||
getClientVersion(): number | undefined;
|
||||
getOpenFileContents(): string | undefined;
|
||||
getModuleSymbolTable(): SymbolTable | undefined;
|
||||
@ -73,7 +73,7 @@ export interface ServiceProvider {
|
||||
// Readonly wrapper around a Program. Makes sure it doesn't mutate the program.
|
||||
export interface ProgramView {
|
||||
readonly id: string;
|
||||
readonly rootPath: string;
|
||||
readonly rootPath: Uri;
|
||||
readonly console: ConsoleInterface;
|
||||
readonly evaluator: TypeEvaluator | undefined;
|
||||
readonly configOptions: ConfigOptions;
|
||||
@ -81,21 +81,16 @@ export interface ProgramView {
|
||||
readonly fileSystem: ReadOnlyFileSystem;
|
||||
readonly serviceProvider: ServiceProvider;
|
||||
|
||||
owns(file: string): boolean;
|
||||
owns(uri: Uri): boolean;
|
||||
getSourceFileInfoList(): readonly SourceFileInfo[];
|
||||
getParseResults(filePath: string): ParseResults | undefined;
|
||||
getSourceFileInfo(filePath: string): SourceFileInfo | undefined;
|
||||
getChainedFilePath(filePath: string): string | undefined;
|
||||
getSourceMapper(
|
||||
filePath: string,
|
||||
token: CancellationToken,
|
||||
mapCompiled?: boolean,
|
||||
preferStubs?: boolean
|
||||
): SourceMapper;
|
||||
getParseResults(fileUri: Uri): ParseResults | undefined;
|
||||
getSourceFileInfo(fileUri: Uri): SourceFileInfo | undefined;
|
||||
getChainedUri(fileUri: Uri): Uri | undefined;
|
||||
getSourceMapper(fileUri: Uri, token: CancellationToken, mapCompiled?: boolean, preferStubs?: boolean): SourceMapper;
|
||||
|
||||
// Consider getDiagnosticsForRange to call `analyzeFile` automatically if the file is not analyzed.
|
||||
analyzeFile(filePath: string, token: CancellationToken): boolean;
|
||||
getDiagnosticsForRange(filePath: string, range: Range): Diagnostic[];
|
||||
analyzeFile(fileUri: Uri, token: CancellationToken): boolean;
|
||||
getDiagnosticsForRange(fileUri: Uri, range: Range): Diagnostic[];
|
||||
|
||||
// See whether we can get rid of these methods
|
||||
handleMemoryHighUsage(): void;
|
||||
@ -106,9 +101,9 @@ export interface ProgramView {
|
||||
// and doesn't forward the request to the BG thread.
|
||||
// One can use this when edits are temporary such as `runEditMode` or `test`
|
||||
export interface EditableProgram extends ProgramView {
|
||||
addInterimFile(file: string): void;
|
||||
setFileOpened(filePath: string, version: number | null, contents: string, options?: prog.OpenFileOptions): void;
|
||||
updateChainedFilePath(filePath: string, chainedFilePath: string | undefined): void;
|
||||
addInterimFile(uri: Uri): void;
|
||||
setFileOpened(fileUri: Uri, version: number | null, contents: string, options?: prog.OpenFileOptions): void;
|
||||
updateChainedUri(fileUri: Uri, chainedUri: Uri | undefined): void;
|
||||
}
|
||||
|
||||
// Mutable wrapper around a program. Allows the FG thread to forward this request to the BG thread
|
||||
@ -116,7 +111,7 @@ export interface EditableProgram extends ProgramView {
|
||||
export interface ProgramMutator {
|
||||
addInterimFile(file: string): void;
|
||||
setFileOpened(
|
||||
filePath: string,
|
||||
fileUri: Uri,
|
||||
version: number | null,
|
||||
contents: string,
|
||||
ipythonMode: IPythonMode,
|
||||
@ -162,7 +157,7 @@ export interface SymbolUsageProvider {
|
||||
}
|
||||
|
||||
export interface StatusMutationListener {
|
||||
fileDirty?: (filePath: string) => void;
|
||||
fileDirty?: (fileUri: Uri) => void;
|
||||
clearCache?: () => void;
|
||||
updateSettings?: <T extends ServerSettings>(settings: T) => void;
|
||||
}
|
||||
|
@ -11,6 +11,7 @@
|
||||
// * NOTE * except tests, this should be only file that import "fs"
|
||||
import type * as fs from 'fs';
|
||||
import { FileWatcher, FileWatcherEventHandler } from './fileWatcher';
|
||||
import { Uri } from './uri/uri';
|
||||
|
||||
export interface Stats {
|
||||
size: number;
|
||||
@ -33,48 +34,47 @@ export interface MkDirOptions {
|
||||
}
|
||||
|
||||
export interface ReadOnlyFileSystem {
|
||||
existsSync(path: string): boolean;
|
||||
chdir(path: string): void;
|
||||
readdirEntriesSync(path: string): fs.Dirent[];
|
||||
readdirSync(path: string): string[];
|
||||
readFileSync(path: string, encoding?: null): Buffer;
|
||||
readFileSync(path: string, encoding: BufferEncoding): string;
|
||||
readFileSync(path: string, encoding?: BufferEncoding | null): string | Buffer;
|
||||
readonly isCaseSensitive: boolean;
|
||||
existsSync(uri: Uri): boolean;
|
||||
chdir(uri: Uri): void;
|
||||
readdirEntriesSync(uri: Uri): fs.Dirent[];
|
||||
readdirSync(uri: Uri): string[];
|
||||
readFileSync(uri: Uri, encoding?: null): Buffer;
|
||||
readFileSync(uri: Uri, encoding: BufferEncoding): string;
|
||||
readFileSync(uri: Uri, encoding?: BufferEncoding | null): string | Buffer;
|
||||
|
||||
statSync(path: string): Stats;
|
||||
realpathSync(path: string): string;
|
||||
getModulePath(): string;
|
||||
statSync(uri: Uri): Stats;
|
||||
realpathSync(uri: Uri): Uri;
|
||||
getModulePath(): Uri;
|
||||
// Async I/O
|
||||
readFile(path: string): Promise<Buffer>;
|
||||
readFileText(path: string, encoding?: BufferEncoding): Promise<string>;
|
||||
readFile(uri: Uri): Promise<Buffer>;
|
||||
readFileText(uri: Uri, encoding?: BufferEncoding): Promise<string>;
|
||||
// Return path in casing on OS.
|
||||
realCasePath(path: string): string;
|
||||
realCasePath(uri: Uri): Uri;
|
||||
|
||||
// See whether the file is mapped to another location.
|
||||
isMappedFilePath(filepath: string): boolean;
|
||||
isMappedUri(uri: Uri): boolean;
|
||||
|
||||
// Get original filepath if the given filepath is mapped.
|
||||
getOriginalFilePath(mappedFilePath: string): string;
|
||||
// Get original uri if the given uri is mapped.
|
||||
getOriginalUri(mappedUri: Uri): Uri;
|
||||
|
||||
// Get mapped filepath if the given filepath is mapped.
|
||||
getMappedFilePath(originalFilepath: string): string;
|
||||
// Get mapped uri if the given uri is mapped.
|
||||
getMappedUri(originalUri: Uri): Uri;
|
||||
|
||||
getUri(path: string): string;
|
||||
|
||||
isInZip(path: string): boolean;
|
||||
isInZip(uri: Uri): boolean;
|
||||
}
|
||||
|
||||
export interface FileSystem extends ReadOnlyFileSystem {
|
||||
mkdirSync(path: string, options?: MkDirOptions): void;
|
||||
writeFileSync(path: string, data: string | Buffer, encoding: BufferEncoding | null): void;
|
||||
mkdirSync(uri: Uri, options?: MkDirOptions): void;
|
||||
writeFileSync(uri: Uri, data: string | Buffer, encoding: BufferEncoding | null): void;
|
||||
|
||||
unlinkSync(path: string): void;
|
||||
rmdirSync(path: string): void;
|
||||
unlinkSync(uri: Uri): void;
|
||||
rmdirSync(uri: Uri): void;
|
||||
|
||||
createFileSystemWatcher(paths: string[], listener: FileWatcherEventHandler): FileWatcher;
|
||||
createReadStream(path: string): fs.ReadStream;
|
||||
createWriteStream(path: string): fs.WriteStream;
|
||||
copyFileSync(src: string, dst: string): void;
|
||||
createFileSystemWatcher(uris: Uri[], listener: FileWatcherEventHandler): FileWatcher;
|
||||
createReadStream(uri: Uri): fs.ReadStream;
|
||||
createWriteStream(uri: Uri): fs.WriteStream;
|
||||
copyFileSync(uri: Uri, dst: Uri): void;
|
||||
}
|
||||
|
||||
export interface TmpfileOptions {
|
||||
@ -84,8 +84,8 @@ export interface TmpfileOptions {
|
||||
|
||||
export interface TempFile {
|
||||
// The directory returned by tmpdir must exist and be the same each time tmpdir is called.
|
||||
tmpdir(): string;
|
||||
tmpfile(options?: TmpfileOptions): string;
|
||||
tmpdir(): Uri;
|
||||
tmpfile(options?: TmpfileOptions): Uri;
|
||||
dispose(): void;
|
||||
}
|
||||
|
||||
|
@ -6,16 +6,17 @@
|
||||
* file watcher related functionality.
|
||||
*/
|
||||
import { Stats } from './fileSystem';
|
||||
import { Uri } from './uri/uri';
|
||||
|
||||
export type FileWatcherEventType = 'add' | 'addDir' | 'change' | 'unlink' | 'unlinkDir';
|
||||
export type FileWatcherEventHandler = (eventName: FileWatcherEventType, path: string, stats?: Stats) => void;
|
||||
export type FileWatcherEventHandler = (eventName: FileWatcherEventType, uri: Uri, stats?: Stats) => void;
|
||||
|
||||
export interface FileWatcher {
|
||||
close(): void;
|
||||
}
|
||||
|
||||
export interface FileWatcherHandler {
|
||||
onFileChange(eventType: FileWatcherEventType, path: string): void;
|
||||
onFileChange(eventType: FileWatcherEventType, uri: Uri): void;
|
||||
}
|
||||
|
||||
export interface FileWatcherProvider {
|
||||
@ -23,7 +24,7 @@ export interface FileWatcherProvider {
|
||||
}
|
||||
|
||||
export const nullFileWatcherHandler: FileWatcherHandler = {
|
||||
onFileChange(_1: FileWatcherEventType, _2: string): void {
|
||||
onFileChange(_1: FileWatcherEventType, _2: Uri): void {
|
||||
// do nothing
|
||||
},
|
||||
};
|
||||
|
@ -13,10 +13,12 @@ import { PythonPathResult } from '../analyzer/pythonPathUtils';
|
||||
import { OperationCanceledException, throwIfCancellationRequested } from './cancellationUtils';
|
||||
import { PythonPlatform } from './configOptions';
|
||||
import { assertNever } from './debug';
|
||||
import { FileSystem } from './fileSystem';
|
||||
import { HostKind, NoAccessHost, ScriptOutput } from './host';
|
||||
import { isDirectory, normalizePath } from './pathUtils';
|
||||
import { normalizePath } from './pathUtils';
|
||||
import { PythonVersion, versionFromMajorMinor } from './pythonVersion';
|
||||
import { ServiceProvider } from './serviceProvider';
|
||||
import { Uri } from './uri/uri';
|
||||
import { isDirectory } from './uri/uriUtils';
|
||||
|
||||
// preventLocalImports removes the working directory from sys.path.
|
||||
// The -c flag adds it automatically, which can allow some stdlib
|
||||
@ -60,7 +62,7 @@ export class LimitedAccessHost extends NoAccessHost {
|
||||
}
|
||||
|
||||
export class FullAccessHost extends LimitedAccessHost {
|
||||
constructor(protected fs: FileSystem) {
|
||||
constructor(protected serviceProvider: ServiceProvider) {
|
||||
super();
|
||||
}
|
||||
|
||||
@ -68,29 +70,29 @@ export class FullAccessHost extends LimitedAccessHost {
|
||||
return HostKind.FullAccess;
|
||||
}
|
||||
|
||||
static createHost(kind: HostKind, fs: FileSystem) {
|
||||
static createHost(kind: HostKind, serviceProvider: ServiceProvider) {
|
||||
switch (kind) {
|
||||
case HostKind.NoAccess:
|
||||
return new NoAccessHost();
|
||||
case HostKind.LimitedAccess:
|
||||
return new LimitedAccessHost();
|
||||
case HostKind.FullAccess:
|
||||
return new FullAccessHost(fs);
|
||||
return new FullAccessHost(serviceProvider);
|
||||
default:
|
||||
assertNever(kind);
|
||||
}
|
||||
}
|
||||
|
||||
override getPythonSearchPaths(pythonPath?: string, logInfo?: string[]): PythonPathResult {
|
||||
override getPythonSearchPaths(pythonPath?: Uri, logInfo?: string[]): PythonPathResult {
|
||||
const importFailureInfo = logInfo ?? [];
|
||||
let result = this._executePythonInterpreter(pythonPath, (p) =>
|
||||
this._getSearchPathResultFromInterpreter(this.fs, p, importFailureInfo)
|
||||
let result = this._executePythonInterpreter(pythonPath?.getFilePath(), (p) =>
|
||||
this._getSearchPathResultFromInterpreter(p, importFailureInfo)
|
||||
);
|
||||
|
||||
if (!result) {
|
||||
result = {
|
||||
paths: [],
|
||||
prefix: '',
|
||||
prefix: undefined,
|
||||
};
|
||||
}
|
||||
|
||||
@ -102,12 +104,12 @@ export class FullAccessHost extends LimitedAccessHost {
|
||||
return result;
|
||||
}
|
||||
|
||||
override getPythonVersion(pythonPath?: string, logInfo?: string[]): PythonVersion | undefined {
|
||||
override getPythonVersion(pythonPath?: Uri, logInfo?: string[]): PythonVersion | undefined {
|
||||
const importFailureInfo = logInfo ?? [];
|
||||
|
||||
try {
|
||||
const commandLineArgs: string[] = ['-c', extractVersion];
|
||||
const execOutput = this._executePythonInterpreter(pythonPath, (p) =>
|
||||
const execOutput = this._executePythonInterpreter(pythonPath?.getFilePath(), (p) =>
|
||||
child_process.execFileSync(p, commandLineArgs, { encoding: 'utf8' })
|
||||
);
|
||||
|
||||
@ -128,8 +130,8 @@ export class FullAccessHost extends LimitedAccessHost {
|
||||
}
|
||||
|
||||
override runScript(
|
||||
pythonPath: string | undefined,
|
||||
script: string,
|
||||
pythonPath: Uri | undefined,
|
||||
script: Uri,
|
||||
args: string[],
|
||||
cwd: string,
|
||||
token: CancellationToken
|
||||
@ -141,9 +143,9 @@ export class FullAccessHost extends LimitedAccessHost {
|
||||
return new Promise<{ stdout: string; stderr: string }>((resolve, reject) => {
|
||||
let stdout = '';
|
||||
let stderr = '';
|
||||
const commandLineArgs = [script, ...args];
|
||||
const commandLineArgs = [script.getFilePath(), ...args];
|
||||
|
||||
const child = this._executePythonInterpreter(pythonPath, (p) =>
|
||||
const child = this._executePythonInterpreter(pythonPath?.getFilePath(), (p) =>
|
||||
child_process.spawn(p, commandLineArgs, { cwd })
|
||||
);
|
||||
const tokenWatch = token.onCancellationRequested(() => {
|
||||
@ -210,20 +212,19 @@ export class FullAccessHost extends LimitedAccessHost {
|
||||
}
|
||||
|
||||
private _getSearchPathResultFromInterpreter(
|
||||
fs: FileSystem,
|
||||
interpreter: string,
|
||||
interpreterPath: string,
|
||||
importFailureInfo: string[]
|
||||
): PythonPathResult | undefined {
|
||||
const result: PythonPathResult = {
|
||||
paths: [],
|
||||
prefix: '',
|
||||
prefix: undefined,
|
||||
};
|
||||
|
||||
try {
|
||||
const commandLineArgs: string[] = ['-c', extractSys];
|
||||
|
||||
importFailureInfo.push(`Executing interpreter: '${interpreter}'`);
|
||||
const execOutput = child_process.execFileSync(interpreter, commandLineArgs, { encoding: 'utf8' });
|
||||
importFailureInfo.push(`Executing interpreter: '${interpreterPath}'`);
|
||||
const execOutput = child_process.execFileSync(interpreterPath, commandLineArgs, { encoding: 'utf8' });
|
||||
const isCaseSensitive = this.serviceProvider.fs().isCaseSensitive;
|
||||
|
||||
// Parse the execOutput. It should be a JSON-encoded array of paths.
|
||||
try {
|
||||
@ -232,16 +233,20 @@ export class FullAccessHost extends LimitedAccessHost {
|
||||
execSplitEntry = execSplitEntry.trim();
|
||||
if (execSplitEntry) {
|
||||
const normalizedPath = normalizePath(execSplitEntry);
|
||||
const normalizedUri = Uri.file(normalizedPath, isCaseSensitive);
|
||||
// Skip non-existent paths and broken zips/eggs.
|
||||
if (fs.existsSync(normalizedPath) && isDirectory(fs, normalizedPath)) {
|
||||
result.paths.push(normalizedPath);
|
||||
if (
|
||||
this.serviceProvider.fs().existsSync(normalizedUri) &&
|
||||
isDirectory(this.serviceProvider.fs(), normalizedUri)
|
||||
) {
|
||||
result.paths.push(normalizedUri);
|
||||
} else {
|
||||
importFailureInfo.push(`Skipping '${normalizedPath}' because it is not a valid directory`);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
result.prefix = execSplit.prefix;
|
||||
result.prefix = Uri.file(execSplit.prefix, isCaseSensitive);
|
||||
|
||||
if (result.paths.length === 0) {
|
||||
importFailureInfo.push(`Found no valid directories`);
|
||||
|
@ -11,6 +11,7 @@ import { CancellationToken } from 'vscode-languageserver';
|
||||
import { PythonPathResult } from '../analyzer/pythonPathUtils';
|
||||
import { PythonPlatform } from './configOptions';
|
||||
import { PythonVersion } from './pythonVersion';
|
||||
import { Uri } from './uri/uri';
|
||||
|
||||
export const enum HostKind {
|
||||
FullAccess,
|
||||
@ -25,12 +26,12 @@ export interface ScriptOutput {
|
||||
|
||||
export interface Host {
|
||||
readonly kind: HostKind;
|
||||
getPythonSearchPaths(pythonPath?: string, logInfo?: string[]): PythonPathResult;
|
||||
getPythonVersion(pythonPath?: string, logInfo?: string[]): PythonVersion | undefined;
|
||||
getPythonSearchPaths(pythonPath?: Uri, logInfo?: string[]): PythonPathResult;
|
||||
getPythonVersion(pythonPath?: Uri, logInfo?: string[]): PythonVersion | undefined;
|
||||
getPythonPlatform(logInfo?: string[]): PythonPlatform | undefined;
|
||||
runScript(
|
||||
pythonPath: string | undefined,
|
||||
script: string,
|
||||
pythonPath: Uri | undefined,
|
||||
script: Uri,
|
||||
args: string[],
|
||||
cwd: string,
|
||||
token: CancellationToken
|
||||
@ -42,16 +43,16 @@ export class NoAccessHost implements Host {
|
||||
return HostKind.NoAccess;
|
||||
}
|
||||
|
||||
getPythonSearchPaths(pythonPath?: string, logInfo?: string[]): PythonPathResult {
|
||||
getPythonSearchPaths(pythonPath?: Uri, logInfo?: string[]): PythonPathResult {
|
||||
logInfo?.push('No access to python executable.');
|
||||
|
||||
return {
|
||||
paths: [],
|
||||
prefix: '',
|
||||
prefix: undefined,
|
||||
};
|
||||
}
|
||||
|
||||
getPythonVersion(pythonPath?: string, logInfo?: string[]): PythonVersion | undefined {
|
||||
getPythonVersion(pythonPath?: Uri, logInfo?: string[]): PythonVersion | undefined {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
@ -60,8 +61,8 @@ export class NoAccessHost implements Host {
|
||||
}
|
||||
|
||||
async runScript(
|
||||
pythonPath: string | undefined,
|
||||
scriptPath: string,
|
||||
pythonPath: Uri | undefined,
|
||||
scriptPath: Uri,
|
||||
args: string[],
|
||||
cwd: string,
|
||||
token: CancellationToken
|
||||
|
@ -9,16 +9,17 @@
|
||||
import { ConsoleInterface, LogLevel } from './console';
|
||||
import { ReadOnlyFileSystem } from './fileSystem';
|
||||
import { Duration, timingStats } from './timing';
|
||||
import { Uri } from './uri/uri';
|
||||
|
||||
// Consider an operation "long running" if it goes longer than this.
|
||||
const durationThresholdForInfoInMs = 2000;
|
||||
|
||||
export function getPathForLogging(fs: ReadOnlyFileSystem, filepath: string) {
|
||||
if (fs.isMappedFilePath(filepath)) {
|
||||
return fs.getOriginalFilePath(filepath);
|
||||
export function getPathForLogging(fs: ReadOnlyFileSystem, fileUri: Uri) {
|
||||
if (fs.isMappedUri(fileUri)) {
|
||||
return fs.getOriginalUri(fileUri);
|
||||
}
|
||||
|
||||
return filepath;
|
||||
return fileUri;
|
||||
}
|
||||
|
||||
export class LogTracker {
|
||||
|
@ -7,24 +7,14 @@
|
||||
* Pathname utility functions.
|
||||
*/
|
||||
|
||||
import type { Dirent } from 'fs';
|
||||
import * as path from 'path';
|
||||
import { URI, Utils } from 'vscode-uri';
|
||||
|
||||
import { Char } from './charCodes';
|
||||
import { some } from './collectionUtils';
|
||||
import { GetCanonicalFileName, identity } from './core';
|
||||
import { randomBytesHex } from './crypto';
|
||||
import * as debug from './debug';
|
||||
import { ServiceProvider } from './extensibility';
|
||||
import { FileSystem, ReadOnlyFileSystem, Stats, TempFile } from './fileSystem';
|
||||
import { ServiceKeys } from './serviceProviderExtensions';
|
||||
import { equateStringsCaseInsensitive, equateStringsCaseSensitive } from './stringUtils';
|
||||
|
||||
let _fsCaseSensitivity: boolean | undefined = undefined;
|
||||
let _underTest: boolean = false;
|
||||
const _uriSchemePattern = /^\w[\w\d+.-]*$/;
|
||||
|
||||
export interface FileSpec {
|
||||
// File specs can contain wildcard characters (**, *, ?). This
|
||||
// specifies the first portion of the file spec that contains
|
||||
@ -72,52 +62,26 @@ export interface FileSystemEntries {
|
||||
directories: string[];
|
||||
}
|
||||
|
||||
export function forEachAncestorDirectory(
|
||||
directory: string,
|
||||
callback: (directory: string) => string | undefined
|
||||
): string | undefined {
|
||||
while (true) {
|
||||
const result = callback(directory);
|
||||
if (result !== undefined) {
|
||||
return result;
|
||||
}
|
||||
|
||||
const parentPath = getDirectoryPath(directory);
|
||||
if (parentPath === directory) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
directory = parentPath;
|
||||
}
|
||||
}
|
||||
|
||||
export function getDirectoryPath(pathString: string): string {
|
||||
if (isUri(pathString)) {
|
||||
return Utils.dirname(URI.parse(pathString).with({ fragment: '', query: '' })).toString();
|
||||
}
|
||||
return pathString.substr(0, Math.max(getRootLength(pathString), pathString.lastIndexOf(path.sep)));
|
||||
}
|
||||
|
||||
export function isUri(pathString: string) {
|
||||
return pathString.indexOf(':') > 1 && _uriSchemePattern.test(pathString.split(':')[0]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns length of the root part of a path or URL (i.e. length of "/", "x:/", "//server/").
|
||||
*/
|
||||
export function getRootLength(pathString: string, checkUri = true): number {
|
||||
if (pathString.charAt(0) === path.sep) {
|
||||
if (pathString.charAt(1) !== path.sep) {
|
||||
export function getRootLength(pathString: string, sep = path.sep): number {
|
||||
if (pathString.charAt(0) === sep) {
|
||||
if (pathString.charAt(1) !== sep) {
|
||||
return 1; // POSIX: "/" (or non-normalized "\")
|
||||
}
|
||||
const p1 = pathString.indexOf(path.sep, 2);
|
||||
const p1 = pathString.indexOf(sep, 2);
|
||||
if (p1 < 0) {
|
||||
return pathString.length; // UNC: "//server" or "\\server"
|
||||
}
|
||||
return p1 + 1; // UNC: "//server/" or "\\server\"
|
||||
}
|
||||
if (pathString.charAt(1) === ':') {
|
||||
if (pathString.charAt(2) === path.sep) {
|
||||
if (pathString.charAt(2) === sep) {
|
||||
return 3; // DOS: "c:/" or "c:\"
|
||||
}
|
||||
if (pathString.length === 2) {
|
||||
@ -125,25 +89,16 @@ export function getRootLength(pathString: string, checkUri = true): number {
|
||||
}
|
||||
}
|
||||
|
||||
if (checkUri && isUri(pathString)) {
|
||||
const uri = URI.parse(pathString);
|
||||
if (uri.authority) {
|
||||
return uri.scheme.length + 3; // URI: "file://"
|
||||
} else {
|
||||
return uri.scheme.length + 1; // URI: "untitled:"
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
export function getPathSeparator(pathString: string) {
|
||||
return isUri(pathString) ? '/' : path.sep;
|
||||
return path.sep;
|
||||
}
|
||||
|
||||
export function getPathComponents(pathString: string) {
|
||||
const normalizedPath = normalizeSlashes(pathString);
|
||||
const rootLength = getRootLength(normalizedPath, /* checkUri */ isUri(normalizedPath));
|
||||
const rootLength = getRootLength(normalizedPath);
|
||||
const root = normalizedPath.substring(0, rootLength);
|
||||
const sep = getPathSeparator(pathString);
|
||||
const rest = normalizedPath.substring(rootLength).split(sep);
|
||||
@ -211,48 +166,12 @@ export function getRelativePath(dirPath: string, relativeTo: string) {
|
||||
return relativePath;
|
||||
}
|
||||
|
||||
// Creates a directory hierarchy for a path, starting from some ancestor path.
|
||||
export function makeDirectories(fs: FileSystem, dirPath: string, startingFromDirPath: string) {
|
||||
if (!dirPath.startsWith(startingFromDirPath)) {
|
||||
return;
|
||||
}
|
||||
|
||||
const pathComponents = getPathComponents(dirPath);
|
||||
const relativeToComponents = getPathComponents(startingFromDirPath);
|
||||
let curPath = startingFromDirPath;
|
||||
|
||||
for (let i = relativeToComponents.length; i < pathComponents.length; i++) {
|
||||
curPath = combinePaths(curPath, pathComponents[i]);
|
||||
if (!fs.existsSync(curPath)) {
|
||||
fs.mkdirSync(curPath);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export function getFileSize(fs: ReadOnlyFileSystem, path: string) {
|
||||
const stat = tryStat(fs, path);
|
||||
if (stat?.isFile()) {
|
||||
return stat.size;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
export function fileExists(fs: ReadOnlyFileSystem, path: string): boolean {
|
||||
return fileSystemEntryExists(fs, path, FileSystemEntryKind.File);
|
||||
}
|
||||
|
||||
export function directoryExists(fs: ReadOnlyFileSystem, path: string): boolean {
|
||||
return fileSystemEntryExists(fs, path, FileSystemEntryKind.Directory);
|
||||
}
|
||||
|
||||
const getInvalidSeparator = (sep: string) => (sep === '/' ? '\\' : '/');
|
||||
export function normalizeSlashes(pathString: string, sep = path.sep): string {
|
||||
if (!isUri(pathString)) {
|
||||
if (pathString.includes(getInvalidSeparator(sep))) {
|
||||
const separatorRegExp = /[\\/]/g;
|
||||
return pathString.replace(separatorRegExp, sep);
|
||||
}
|
||||
}
|
||||
|
||||
return pathString;
|
||||
}
|
||||
@ -271,7 +190,7 @@ export function resolvePaths(path: string, ...paths: (string | undefined)[]): st
|
||||
return normalizePath(some(paths) ? combinePaths(path, ...paths) : normalizeSlashes(path));
|
||||
}
|
||||
|
||||
function combineFilePaths(pathString: string, ...paths: (string | undefined)[]): string {
|
||||
export function combinePaths(pathString: string, ...paths: (string | undefined)[]): string {
|
||||
if (pathString) {
|
||||
pathString = normalizeSlashes(pathString);
|
||||
}
|
||||
@ -283,7 +202,7 @@ function combineFilePaths(pathString: string, ...paths: (string | undefined)[]):
|
||||
|
||||
relativePath = normalizeSlashes(relativePath);
|
||||
|
||||
if (!pathString || getRootLength(relativePath, /* checkUri */ false) !== 0) {
|
||||
if (!pathString || getRootLength(relativePath) !== 0) {
|
||||
pathString = relativePath;
|
||||
} else {
|
||||
pathString = ensureTrailingDirectorySeparator(pathString) + relativePath;
|
||||
@ -293,29 +212,6 @@ function combineFilePaths(pathString: string, ...paths: (string | undefined)[]):
|
||||
return pathString;
|
||||
}
|
||||
|
||||
export function combinePaths(pathString: string, ...paths: (string | undefined)[]): string {
|
||||
if (!isUri(pathString)) {
|
||||
// Not a URI, or a URI with a single letter scheme.
|
||||
return combineFilePaths(pathString, ...paths);
|
||||
}
|
||||
|
||||
// Go through the paths to see if any are rooted. If so, treat as
|
||||
// a file path. On linux this might be wrong if a path starts with '/'.
|
||||
if (some(paths, (p) => !!p && getRootLength(p, /* checkUri */ false) !== 0)) {
|
||||
return combineFilePaths(pathString, ...paths);
|
||||
}
|
||||
|
||||
// Otherwise this is a URI
|
||||
const nonEmptyPaths = paths.filter((p) => !!p) as string[];
|
||||
const uri = URI.parse(pathString);
|
||||
|
||||
// Make sure we have a path to append to.
|
||||
if (uri.path === '' || uri.path === undefined) {
|
||||
nonEmptyPaths.unshift('/');
|
||||
}
|
||||
return Utils.joinPath(uri.with({ fragment: '', query: '' }), ...nonEmptyPaths).toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines whether a `parent` path contains a `child` path using the provide case sensitivity.
|
||||
*/
|
||||
@ -485,9 +381,7 @@ export function getBaseFileName(pathString: string, extensions?: string | readon
|
||||
// return the trailing portion of the path starting after the last (non-terminal) directory
|
||||
// separator but not including any trailing directory separator.
|
||||
pathString = stripTrailingDirectorySeparator(pathString);
|
||||
const name = isUri(pathString)
|
||||
? Utils.basename(URI.parse(pathString).with({ fragment: '', query: '' }))
|
||||
: pathString.slice(Math.max(getRootLength(pathString), pathString.lastIndexOf(path.sep) + 1));
|
||||
const name = pathString.slice(Math.max(getRootLength(pathString), pathString.lastIndexOf(path.sep) + 1));
|
||||
const extension =
|
||||
extensions !== undefined && ignoreCase !== undefined
|
||||
? getAnyExtensionFromPath(name, extensions, ignoreCase)
|
||||
@ -561,7 +455,7 @@ export function stripTrailingDirectorySeparator(pathString: string) {
|
||||
if (!hasTrailingDirectorySeparator(pathString)) {
|
||||
return pathString;
|
||||
}
|
||||
return pathString.substr(0, pathString.length - 1);
|
||||
return pathString.slice(0, pathString.length - 1);
|
||||
}
|
||||
|
||||
export function getFileExtension(fileName: string, multiDotExtension = false) {
|
||||
@ -571,7 +465,7 @@ export function getFileExtension(fileName: string, multiDotExtension = false) {
|
||||
|
||||
fileName = getFileName(fileName);
|
||||
const firstDotIndex = fileName.indexOf('.');
|
||||
return fileName.substr(firstDotIndex);
|
||||
return fileName.slice(firstDotIndex);
|
||||
}
|
||||
|
||||
export function getFileName(pathString: string) {
|
||||
@ -592,104 +486,10 @@ export function stripFileExtension(fileName: string, multiDotExtension = false)
|
||||
return fileName.substr(0, fileName.length - ext.length);
|
||||
}
|
||||
|
||||
export function realCasePath(pathString: string, fileSystem: ReadOnlyFileSystem): string {
|
||||
return isUri(pathString) ? pathString : fileSystem.realCasePath(pathString);
|
||||
}
|
||||
|
||||
export function normalizePath(pathString: string): string {
|
||||
if (!isUri(pathString)) {
|
||||
return normalizeSlashes(path.normalize(pathString));
|
||||
}
|
||||
|
||||
// Must be a URI, already normalized.
|
||||
return pathString;
|
||||
}
|
||||
|
||||
export function isDirectory(fs: ReadOnlyFileSystem, path: string): boolean {
|
||||
return tryStat(fs, path)?.isDirectory() ?? false;
|
||||
}
|
||||
|
||||
export function isFile(fs: ReadOnlyFileSystem, path: string, treatZipDirectoryAsFile = false): boolean {
|
||||
const stats = tryStat(fs, path);
|
||||
if (stats?.isFile()) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (!treatZipDirectoryAsFile) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return stats?.isZipDirectory?.() ?? false;
|
||||
}
|
||||
|
||||
export function tryStat(fs: ReadOnlyFileSystem, path: string): Stats | undefined {
|
||||
try {
|
||||
if (fs.existsSync(path)) {
|
||||
return fs.statSync(path);
|
||||
}
|
||||
} catch (e: any) {
|
||||
return undefined;
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
|
||||
export function tryRealpath(fs: ReadOnlyFileSystem, path: string): string | undefined {
|
||||
try {
|
||||
return fs.realCasePath(path);
|
||||
} catch (e: any) {
|
||||
return undefined;
|
||||
}
|
||||
}
|
||||
|
||||
export function getFileSystemEntries(fs: ReadOnlyFileSystem, path: string): FileSystemEntries {
|
||||
try {
|
||||
return getFileSystemEntriesFromDirEntries(fs.readdirEntriesSync(path || '.'), fs, path);
|
||||
} catch (e: any) {
|
||||
return { files: [], directories: [] };
|
||||
}
|
||||
}
|
||||
|
||||
// Sorts the entires into files and directories, including any symbolic links.
|
||||
export function getFileSystemEntriesFromDirEntries(
|
||||
dirEntries: Dirent[],
|
||||
fs: ReadOnlyFileSystem,
|
||||
path: string
|
||||
): FileSystemEntries {
|
||||
const entries = dirEntries.sort((a, b) => {
|
||||
if (a.name < b.name) {
|
||||
return -1;
|
||||
} else if (a.name > b.name) {
|
||||
return 1;
|
||||
} else {
|
||||
return 0;
|
||||
}
|
||||
});
|
||||
const files: string[] = [];
|
||||
const directories: string[] = [];
|
||||
for (const entry of entries) {
|
||||
// This is necessary because on some file system node fails to exclude
|
||||
// "." and "..". See https://github.com/nodejs/node/issues/4002
|
||||
if (entry.name === '.' || entry.name === '..') {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (entry.isFile()) {
|
||||
files.push(entry.name);
|
||||
} else if (entry.isDirectory()) {
|
||||
directories.push(entry.name);
|
||||
} else if (entry.isSymbolicLink()) {
|
||||
const entryPath = combinePaths(path, entry.name);
|
||||
const stat = tryStat(fs, entryPath);
|
||||
if (stat?.isFile()) {
|
||||
files.push(entry.name);
|
||||
} else if (stat?.isDirectory()) {
|
||||
directories.push(entry.name);
|
||||
}
|
||||
}
|
||||
}
|
||||
return { files, directories };
|
||||
}
|
||||
|
||||
// Transforms a relative file spec (one that potentially contains
|
||||
// escape characters **, * or ?) and returns a regular expression
|
||||
// that can be used for matching against.
|
||||
@ -803,25 +603,6 @@ export function hasPythonExtension(path: string) {
|
||||
return path.endsWith('.py') || path.endsWith('.pyi');
|
||||
}
|
||||
|
||||
export function getFileSpec(sp: ServiceProvider, rootPath: string, fileSpec: string): FileSpec {
|
||||
let regExPattern = getWildcardRegexPattern(rootPath, fileSpec);
|
||||
const escapedSeparator = getRegexEscapedSeparator(getPathSeparator(rootPath));
|
||||
regExPattern = `^(${regExPattern})($|${escapedSeparator})`;
|
||||
|
||||
const fs = sp.get(ServiceKeys.fs);
|
||||
const tmp = sp.tryGet(ServiceKeys.tempFile);
|
||||
|
||||
const regExp = new RegExp(regExPattern, isFileSystemCaseSensitive(fs, tmp) ? undefined : 'i');
|
||||
const wildcardRoot = getWildcardRoot(rootPath, fileSpec);
|
||||
const hasDirectoryWildcard = isDirectoryWildcardPatternPresent(fileSpec);
|
||||
|
||||
return {
|
||||
wildcardRoot,
|
||||
regExp,
|
||||
hasDirectoryWildcard,
|
||||
};
|
||||
}
|
||||
|
||||
export function getRegexEscapedSeparator(pathSep: string = path.sep) {
|
||||
// we don't need to escape "/" in typescript regular expression
|
||||
return pathSep === '/' ? '/' : '\\\\';
|
||||
@ -908,168 +689,3 @@ function getPathComponentsRelativeTo(
|
||||
}
|
||||
return ['', ...relative, ...components];
|
||||
}
|
||||
|
||||
const enum FileSystemEntryKind {
|
||||
File,
|
||||
Directory,
|
||||
}
|
||||
|
||||
function fileSystemEntryExists(fs: ReadOnlyFileSystem, path: string, entryKind: FileSystemEntryKind): boolean {
|
||||
try {
|
||||
const stat = fs.statSync(path);
|
||||
switch (entryKind) {
|
||||
case FileSystemEntryKind.File:
|
||||
return stat.isFile();
|
||||
case FileSystemEntryKind.Directory:
|
||||
return stat.isDirectory();
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
} catch (e: any) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
export function convertUriToPath(fs: ReadOnlyFileSystem, uriString: string): string {
|
||||
return realCasePath(fs.getMappedFilePath(extractPathFromUri(uriString)), fs);
|
||||
}
|
||||
|
||||
export function extractPathFromUri(uriString: string) {
|
||||
const uri = URI.parse(uriString);
|
||||
|
||||
// Only for file scheme do we actually modify anything. All other uri strings
|
||||
// maintain the same value they started with.
|
||||
if (uri.scheme === 'file' && !uri.fragment) {
|
||||
// When schema is "file", we use fsPath so that we can handle things like UNC paths.
|
||||
let convertedPath = normalizePath(uri.fsPath);
|
||||
|
||||
// If this is a DOS-style path with a drive letter, remove
|
||||
// the leading slash.
|
||||
if (convertedPath.match(/^\\[a-zA-Z]:\\/)) {
|
||||
convertedPath = convertedPath.slice(1);
|
||||
}
|
||||
|
||||
return convertedPath;
|
||||
}
|
||||
|
||||
return uriString;
|
||||
}
|
||||
|
||||
export function convertPathToUri(fs: ReadOnlyFileSystem, path: string): string {
|
||||
return fs.getUri(fs.getOriginalFilePath(path));
|
||||
}
|
||||
|
||||
export function setTestingMode(underTest: boolean) {
|
||||
_underTest = underTest;
|
||||
}
|
||||
|
||||
const isFileSystemCaseSensitiveMap = new WeakMap<FileSystem, boolean>();
|
||||
|
||||
export function isFileSystemCaseSensitive(fs: FileSystem, tmp?: TempFile) {
|
||||
if (!tmp) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!_underTest && _fsCaseSensitivity !== undefined) {
|
||||
return _fsCaseSensitivity;
|
||||
}
|
||||
|
||||
if (!isFileSystemCaseSensitiveMap.has(fs)) {
|
||||
_fsCaseSensitivity = isFileSystemCaseSensitiveInternal(fs, tmp);
|
||||
isFileSystemCaseSensitiveMap.set(fs, _fsCaseSensitivity);
|
||||
}
|
||||
return !!isFileSystemCaseSensitiveMap.get(fs);
|
||||
}
|
||||
|
||||
export function isFileSystemCaseSensitiveInternal(fs: FileSystem, tmp: TempFile) {
|
||||
let filePath: string | undefined = undefined;
|
||||
try {
|
||||
// Make unique file name.
|
||||
let name: string;
|
||||
let mangledFilePath: string;
|
||||
do {
|
||||
name = `${randomBytesHex(21)}-a`;
|
||||
filePath = path.join(tmp.tmpdir(), name);
|
||||
mangledFilePath = path.join(tmp.tmpdir(), name.toUpperCase());
|
||||
} while (fs.existsSync(filePath) || fs.existsSync(mangledFilePath));
|
||||
|
||||
fs.writeFileSync(filePath, '', 'utf8');
|
||||
|
||||
// If file exists, then it is insensitive.
|
||||
return !fs.existsSync(mangledFilePath);
|
||||
} catch (e: any) {
|
||||
return false;
|
||||
} finally {
|
||||
if (filePath) {
|
||||
// remove temp file created
|
||||
try {
|
||||
fs.unlinkSync(filePath);
|
||||
} catch (e: any) {
|
||||
/* ignored */
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export function getLibraryPathWithoutExtension(libraryFilePath: string) {
|
||||
let filePathWithoutExtension = stripFileExtension(libraryFilePath);
|
||||
|
||||
// Strip off the '/__init__' if it's present.
|
||||
if (filePathWithoutExtension.endsWith('__init__')) {
|
||||
filePathWithoutExtension = filePathWithoutExtension.substr(0, filePathWithoutExtension.length - 9);
|
||||
}
|
||||
|
||||
return filePathWithoutExtension;
|
||||
}
|
||||
|
||||
export function getDirectoryChangeKind(
|
||||
fs: ReadOnlyFileSystem,
|
||||
oldDirectory: string,
|
||||
newDirectory: string
|
||||
): 'Same' | 'Renamed' | 'Moved' {
|
||||
if (fs.realCasePath(oldDirectory) === fs.realCasePath(newDirectory)) {
|
||||
return 'Same';
|
||||
}
|
||||
|
||||
const relativePaths = getRelativePathComponentsFromDirectory(oldDirectory, newDirectory, (f) => fs.realCasePath(f));
|
||||
|
||||
// 3 means only last folder name has changed.
|
||||
if (relativePaths.length === 3 && relativePaths[1] === '..' && relativePaths[2] !== '..') {
|
||||
return 'Renamed';
|
||||
}
|
||||
|
||||
return 'Moved';
|
||||
}
|
||||
|
||||
export function deduplicateFolders(listOfFolders: string[][]): string[] {
|
||||
const foldersToWatch = new Set<string>();
|
||||
|
||||
listOfFolders.forEach((folders) => {
|
||||
folders.forEach((p) => {
|
||||
if (foldersToWatch.has(p)) {
|
||||
// Bail out on exact match.
|
||||
return;
|
||||
}
|
||||
|
||||
for (const existing of foldersToWatch) {
|
||||
// ex) p: "/user/test" existing: "/user"
|
||||
if (p.startsWith(existing)) {
|
||||
// We already have the parent folder in the watch list
|
||||
return;
|
||||
}
|
||||
|
||||
// ex) p: "/user" folderToWatch: "/user/test"
|
||||
if (existing.startsWith(p)) {
|
||||
// We found better one to watch. replace.
|
||||
foldersToWatch.delete(existing);
|
||||
foldersToWatch.add(p);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
foldersToWatch.add(p);
|
||||
});
|
||||
});
|
||||
|
||||
return [...foldersToWatch];
|
||||
}
|
||||
|
@ -8,7 +8,6 @@ import { FakeFS, NativePath, PortablePath, PosixFS, ppath, VirtualFS, ZipFS, Zip
|
||||
import { getLibzipSync } from '@yarnpkg/libzip';
|
||||
import * as fs from 'fs';
|
||||
import * as tmp from 'tmp';
|
||||
import { URI } from 'vscode-uri';
|
||||
import { isMainThread } from 'worker_threads';
|
||||
|
||||
import { ConsoleInterface, NullConsole } from './console';
|
||||
@ -21,7 +20,9 @@ import {
|
||||
FileWatcherProvider,
|
||||
nullFileWatcherProvider,
|
||||
} from './fileWatcher';
|
||||
import { combinePaths, getRootLength, isUri } from './pathUtils';
|
||||
import { getRootLength } from './pathUtils';
|
||||
import { Uri } from './uri/uri';
|
||||
import { getRootUri, isFileSystemCaseSensitive } from './uri/uriUtils';
|
||||
|
||||
// Automatically remove files created by tmp at process exit.
|
||||
tmp.setGracefulCleanup();
|
||||
@ -208,9 +209,19 @@ class YarnFS extends PosixFS {
|
||||
const yarnFS = new YarnFS();
|
||||
|
||||
class RealFileSystem implements FileSystem {
|
||||
constructor(private _fileWatcherProvider: FileWatcherProvider, private _console: ConsoleInterface) {}
|
||||
private _isCaseSensitive = true;
|
||||
constructor(private _fileWatcherProvider: FileWatcherProvider, private _console: ConsoleInterface) {
|
||||
this._isCaseSensitive = isFileSystemCaseSensitive(this, new RealTempFile(/* isCaseSensitive */ true));
|
||||
}
|
||||
|
||||
existsSync(path: string) {
|
||||
get isCaseSensitive(): boolean {
|
||||
return this._isCaseSensitive;
|
||||
}
|
||||
existsSync(uri: Uri) {
|
||||
if (uri.isEmpty()) {
|
||||
return false;
|
||||
}
|
||||
const path = uri.getFilePath();
|
||||
try {
|
||||
// Catch zip open errors. existsSync is assumed to never throw by callers.
|
||||
return yarnFS.existsSync(path);
|
||||
@ -219,11 +230,13 @@ class RealFileSystem implements FileSystem {
|
||||
}
|
||||
}
|
||||
|
||||
mkdirSync(path: string, options?: MkDirOptions) {
|
||||
mkdirSync(uri: Uri, options?: MkDirOptions) {
|
||||
const path = uri.getFilePath();
|
||||
yarnFS.mkdirSync(path, options);
|
||||
}
|
||||
|
||||
chdir(path: string) {
|
||||
chdir(uri: Uri) {
|
||||
const path = uri.getFilePath();
|
||||
// If this file system happens to be running in a worker thread,
|
||||
// then we can't call 'chdir'.
|
||||
if (isMainThread) {
|
||||
@ -231,11 +244,13 @@ class RealFileSystem implements FileSystem {
|
||||
}
|
||||
}
|
||||
|
||||
readdirSync(path: string): string[] {
|
||||
readdirSync(uri: Uri): string[] {
|
||||
const path = uri.getFilePath();
|
||||
return yarnFS.readdirSync(path);
|
||||
}
|
||||
|
||||
readdirEntriesSync(path: string): fs.Dirent[] {
|
||||
readdirEntriesSync(uri: Uri): fs.Dirent[] {
|
||||
const path = uri.getFilePath();
|
||||
return yarnFS.readdirSync(path, { withFileTypes: true }).map((entry): fs.Dirent => {
|
||||
// Treat zip/egg files as directories.
|
||||
// See: https://github.com/yarnpkg/berry/blob/master/packages/vscode-zipfs/sources/ZipFSProvider.ts
|
||||
@ -257,21 +272,24 @@ class RealFileSystem implements FileSystem {
|
||||
});
|
||||
}
|
||||
|
||||
readFileSync(path: string, encoding?: null): Buffer;
|
||||
readFileSync(path: string, encoding: BufferEncoding): string;
|
||||
readFileSync(path: string, encoding?: BufferEncoding | null): Buffer | string;
|
||||
readFileSync(path: string, encoding: BufferEncoding | null = null) {
|
||||
readFileSync(uri: Uri, encoding?: null): Buffer;
|
||||
readFileSync(uri: Uri, encoding: BufferEncoding): string;
|
||||
readFileSync(uri: Uri, encoding?: BufferEncoding | null): Buffer | string;
|
||||
readFileSync(uri: Uri, encoding: BufferEncoding | null = null) {
|
||||
const path = uri.getFilePath();
|
||||
if (encoding === 'utf8' || encoding === 'utf-8') {
|
||||
return yarnFS.readFileSync(path, 'utf8');
|
||||
}
|
||||
return yarnFS.readFileSync(path);
|
||||
}
|
||||
|
||||
writeFileSync(path: string, data: string | Buffer, encoding: BufferEncoding | null) {
|
||||
writeFileSync(uri: Uri, data: string | Buffer, encoding: BufferEncoding | null) {
|
||||
const path = uri.getFilePath();
|
||||
yarnFS.writeFileSync(path, data, encoding || undefined);
|
||||
}
|
||||
|
||||
statSync(path: string) {
|
||||
statSync(uri: Uri) {
|
||||
const path = uri.getFilePath();
|
||||
const stat = yarnFS.statSync(path);
|
||||
// Treat zip/egg files as directories.
|
||||
// See: https://github.com/yarnpkg/berry/blob/master/packages/vscode-zipfs/sources/ZipFSProvider.ts
|
||||
@ -286,53 +304,62 @@ class RealFileSystem implements FileSystem {
|
||||
return stat;
|
||||
}
|
||||
|
||||
rmdirSync(path: string): void {
|
||||
rmdirSync(uri: Uri): void {
|
||||
const path = uri.getFilePath();
|
||||
yarnFS.rmdirSync(path);
|
||||
}
|
||||
|
||||
unlinkSync(path: string) {
|
||||
unlinkSync(uri: Uri) {
|
||||
const path = uri.getFilePath();
|
||||
yarnFS.unlinkSync(path);
|
||||
}
|
||||
|
||||
realpathSync(path: string) {
|
||||
realpathSync(uri: Uri) {
|
||||
try {
|
||||
return yarnFS.realpathSync(path);
|
||||
const path = uri.getFilePath();
|
||||
return Uri.file(yarnFS.realpathSync(path), this._isCaseSensitive);
|
||||
} catch (e: any) {
|
||||
return path;
|
||||
return uri;
|
||||
}
|
||||
}
|
||||
|
||||
getModulePath(): string {
|
||||
getModulePath(): Uri {
|
||||
// The entry point to the tool should have set the __rootDirectory
|
||||
// global variable to point to the directory that contains the
|
||||
// typeshed-fallback directory.
|
||||
return (global as any).__rootDirectory;
|
||||
return getRootUri(this._isCaseSensitive) || Uri.empty();
|
||||
}
|
||||
|
||||
createFileSystemWatcher(paths: string[], listener: FileWatcherEventHandler): FileWatcher {
|
||||
createFileSystemWatcher(paths: Uri[], listener: FileWatcherEventHandler): FileWatcher {
|
||||
return this._fileWatcherProvider.createFileWatcher(
|
||||
paths.map((p) => this.realCasePath(p)),
|
||||
paths.map((p) => p.getFilePath()),
|
||||
listener
|
||||
);
|
||||
}
|
||||
|
||||
createReadStream(path: string): fs.ReadStream {
|
||||
createReadStream(uri: Uri): fs.ReadStream {
|
||||
const path = uri.getFilePath();
|
||||
return yarnFS.createReadStream(path);
|
||||
}
|
||||
|
||||
createWriteStream(path: string): fs.WriteStream {
|
||||
createWriteStream(uri: Uri): fs.WriteStream {
|
||||
const path = uri.getFilePath();
|
||||
return yarnFS.createWriteStream(path);
|
||||
}
|
||||
|
||||
copyFileSync(src: string, dst: string): void {
|
||||
yarnFS.copyFileSync(src, dst);
|
||||
copyFileSync(src: Uri, dst: Uri): void {
|
||||
const srcPath = src.getFilePath();
|
||||
const destPath = dst.getFilePath();
|
||||
yarnFS.copyFileSync(srcPath, destPath);
|
||||
}
|
||||
|
||||
readFile(path: string): Promise<Buffer> {
|
||||
readFile(uri: Uri): Promise<Buffer> {
|
||||
const path = uri.getFilePath();
|
||||
return yarnFS.readFilePromise(path);
|
||||
}
|
||||
|
||||
async readFileText(path: string, encoding: BufferEncoding): Promise<string> {
|
||||
async readFileText(uri: Uri, encoding: BufferEncoding): Promise<string> {
|
||||
const path = uri.getFilePath();
|
||||
if (encoding === 'utf8' || encoding === 'utf-8') {
|
||||
return yarnFS.readFilePromise(path, 'utf8');
|
||||
}
|
||||
@ -340,17 +367,18 @@ class RealFileSystem implements FileSystem {
|
||||
return buffer.toString(encoding);
|
||||
}
|
||||
|
||||
realCasePath(path: string): string {
|
||||
realCasePath(uri: Uri): Uri {
|
||||
try {
|
||||
// If it doesn't exist in the real FS, then just use this path.
|
||||
if (!this.existsSync(path)) {
|
||||
return this._getNormalizedPath(path);
|
||||
if (!this.existsSync(uri)) {
|
||||
return uri;
|
||||
}
|
||||
|
||||
// If it does exist, skip this for symlinks.
|
||||
const path = uri.getFilePath();
|
||||
const stat = fs.lstatSync(path);
|
||||
if (stat.isSymbolicLink()) {
|
||||
return this._getNormalizedPath(path);
|
||||
return uri;
|
||||
}
|
||||
|
||||
// realpathSync.native will return casing as in OS rather than
|
||||
@ -359,53 +387,34 @@ class RealFileSystem implements FileSystem {
|
||||
|
||||
// On UNC mapped drives we want to keep the original drive letter.
|
||||
if (getRootLength(realCase) !== getRootLength(path)) {
|
||||
return path;
|
||||
return uri;
|
||||
}
|
||||
|
||||
return realCase;
|
||||
return Uri.file(realCase, this._isCaseSensitive);
|
||||
} catch (e: any) {
|
||||
// Return as it is, if anything failed.
|
||||
this._console.log(`Failed to get real file system casing for ${path}: ${e}`);
|
||||
this._console.log(`Failed to get real file system casing for ${uri}: ${e}`);
|
||||
|
||||
return path;
|
||||
return uri;
|
||||
}
|
||||
}
|
||||
|
||||
isMappedFilePath(filepath: string): boolean {
|
||||
isMappedUri(uri: Uri): boolean {
|
||||
return false;
|
||||
}
|
||||
|
||||
getOriginalFilePath(mappedFilePath: string) {
|
||||
return mappedFilePath;
|
||||
getOriginalUri(mappedUri: Uri) {
|
||||
return mappedUri;
|
||||
}
|
||||
|
||||
getMappedFilePath(originalFilepath: string) {
|
||||
return originalFilepath;
|
||||
getMappedUri(originalUri: Uri) {
|
||||
return originalUri;
|
||||
}
|
||||
|
||||
getUri(path: string): string {
|
||||
// If this is not a file path, just return the original path.
|
||||
if (isUri(path)) {
|
||||
return path;
|
||||
}
|
||||
return URI.file(path).toString();
|
||||
}
|
||||
|
||||
isInZip(path: string): boolean {
|
||||
isInZip(uri: Uri): boolean {
|
||||
const path = uri.getFilePath();
|
||||
return /[^\\/]\.(?:egg|zip|jar)[\\/]/.test(path) && yarnFS.isZip(path);
|
||||
}
|
||||
|
||||
private _getNormalizedPath(path: string) {
|
||||
const driveLength = getRootLength(path);
|
||||
|
||||
if (driveLength === 0) {
|
||||
return path;
|
||||
}
|
||||
|
||||
// `vscode` sometimes uses different casing for drive letter.
|
||||
// Make sure we normalize at least drive letter.
|
||||
return combinePaths(fs.realpathSync.native(path.substring(0, driveLength)), path.substring(driveLength));
|
||||
}
|
||||
}
|
||||
|
||||
interface WorkspaceFileWatcher extends FileWatcher {
|
||||
@ -436,15 +445,15 @@ export class WorkspaceFileWatcherProvider implements FileWatcherProvider, FileWa
|
||||
return fileWatcher;
|
||||
}
|
||||
|
||||
onFileChange(eventType: FileWatcherEventType, filePath: string): void {
|
||||
onFileChange(eventType: FileWatcherEventType, fileUri: Uri): void {
|
||||
// Since file watcher is a server wide service, we don't know which watcher is
|
||||
// for which workspace (for multi workspace case), also, we don't know which watcher
|
||||
// is for source or library. so we need to solely rely on paths that can cause us
|
||||
// to raise events both for source and library if .venv is inside of workspace root
|
||||
// for a file change. It is event handler's job to filter those out.
|
||||
this._fileWatchers.forEach((watcher) => {
|
||||
if (watcher.workspacePaths.some((dirPath) => filePath.startsWith(dirPath))) {
|
||||
watcher.eventHandler(eventType, filePath);
|
||||
if (watcher.workspacePaths.some((dirPath) => fileUri.pathStartsWith(dirPath))) {
|
||||
watcher.eventHandler(eventType, fileUri);
|
||||
}
|
||||
});
|
||||
}
|
||||
@ -453,17 +462,15 @@ export class WorkspaceFileWatcherProvider implements FileWatcherProvider, FileWa
|
||||
export class RealTempFile implements TempFile {
|
||||
private _tmpdir?: tmp.DirResult;
|
||||
|
||||
tmpdir() {
|
||||
if (!this._tmpdir) {
|
||||
this._tmpdir = tmp.dirSync({ prefix: 'pyright' });
|
||||
constructor(private readonly _isCaseSensitive: boolean) {}
|
||||
|
||||
tmpdir(): Uri {
|
||||
return Uri.file(this._getTmpDir().name, this._isCaseSensitive);
|
||||
}
|
||||
|
||||
return this._tmpdir.name;
|
||||
}
|
||||
|
||||
tmpfile(options?: TmpfileOptions): string {
|
||||
const f = tmp.fileSync({ dir: this.tmpdir(), discardDescriptor: true, ...options });
|
||||
return f.name;
|
||||
tmpfile(options?: TmpfileOptions): Uri {
|
||||
const f = tmp.fileSync({ dir: this._getTmpDir().name, discardDescriptor: true, ...options });
|
||||
return Uri.file(f.name, this._isCaseSensitive);
|
||||
}
|
||||
|
||||
dispose(): void {
|
||||
@ -474,4 +481,12 @@ export class RealTempFile implements TempFile {
|
||||
// ignore
|
||||
}
|
||||
}
|
||||
|
||||
private _getTmpDir(): tmp.DirResult {
|
||||
if (!this._tmpdir) {
|
||||
this._tmpdir = tmp.dirSync({ prefix: 'pyright' });
|
||||
}
|
||||
|
||||
return this._tmpdir;
|
||||
}
|
||||
}
|
||||
|
@ -11,20 +11,22 @@ import { IPythonMode, SourceFile, SourceFileEditMode } from '../analyzer/sourceF
|
||||
import { SupportPartialStubs } from '../pyrightFileSystem';
|
||||
import { ConsoleInterface } from './console';
|
||||
import {
|
||||
StatusMutationListener,
|
||||
DebugInfoInspector,
|
||||
ServiceProvider as ReadOnlyServiceProvider,
|
||||
StatusMutationListener,
|
||||
SymbolDefinitionProvider,
|
||||
SymbolUsageProviderFactory,
|
||||
DebugInfoInspector,
|
||||
} from './extensibility';
|
||||
import { FileSystem, TempFile } from './fileSystem';
|
||||
import { LogTracker } from './logTracker';
|
||||
import { GroupServiceKey, ServiceKey, ServiceProvider } from './serviceProvider';
|
||||
import { Uri } from './uri/uri';
|
||||
|
||||
declare module './serviceProvider' {
|
||||
interface ServiceProvider {
|
||||
fs(): FileSystem;
|
||||
console(): ConsoleInterface;
|
||||
tmp(): TempFile | undefined;
|
||||
sourceFileFactory(): ISourceFileFactory;
|
||||
partialStubs(): SupportPartialStubs;
|
||||
}
|
||||
@ -79,6 +81,9 @@ ServiceProvider.prototype.console = function () {
|
||||
ServiceProvider.prototype.partialStubs = function () {
|
||||
return this.get(ServiceKeys.partialStubs);
|
||||
};
|
||||
ServiceProvider.prototype.tmp = function () {
|
||||
return this.tryGet(ServiceKeys.tempFile);
|
||||
};
|
||||
ServiceProvider.prototype.sourceFileFactory = function () {
|
||||
const result = this.tryGet(ServiceKeys.sourceFileFactory);
|
||||
return result || DefaultSourceFileFactory;
|
||||
@ -87,26 +92,24 @@ ServiceProvider.prototype.sourceFileFactory = function () {
|
||||
const DefaultSourceFileFactory: ISourceFileFactory = {
|
||||
createSourceFile(
|
||||
serviceProvider: ReadOnlyServiceProvider,
|
||||
filePath: string,
|
||||
fileUri: Uri,
|
||||
moduleName: string,
|
||||
isThirdPartyImport: boolean,
|
||||
isThirdPartyPyTypedPresent: boolean,
|
||||
editMode: SourceFileEditMode,
|
||||
console?: ConsoleInterface,
|
||||
logTracker?: LogTracker,
|
||||
realFilePath?: string,
|
||||
ipythonMode?: IPythonMode
|
||||
) {
|
||||
return new SourceFile(
|
||||
serviceProvider,
|
||||
filePath,
|
||||
fileUri,
|
||||
moduleName,
|
||||
isThirdPartyImport,
|
||||
isThirdPartyPyTypedPresent,
|
||||
editMode,
|
||||
console,
|
||||
logTracker,
|
||||
realFilePath,
|
||||
ipythonMode
|
||||
);
|
||||
},
|
||||
|
@ -32,11 +32,11 @@ import {
|
||||
} from '../parser/parseNodes';
|
||||
import { ParseResults } from '../parser/parser';
|
||||
import { appendArray, getOrAdd, removeArrayElements } from './collectionUtils';
|
||||
import { isString } from './core';
|
||||
import * as debug from './debug';
|
||||
import { FileEditAction } from './editAction';
|
||||
import { convertOffsetToPosition, convertTextRangeToRange } from './positionUtils';
|
||||
import { doesRangeContain, doRangesIntersect, extendRange, Range, TextRange } from './textRange';
|
||||
import { Uri } from './uri/uri';
|
||||
|
||||
export class TextEditTracker {
|
||||
private readonly _nodesRemoved: Map<ParseNode, ParseResults> = new Map<ParseNode, ParseResults>();
|
||||
@ -49,11 +49,11 @@ export class TextEditTracker {
|
||||
}
|
||||
|
||||
addEdits(...edits: FileEditAction[]) {
|
||||
edits.forEach((e) => this.addEdit(e.filePath, e.range, e.replacementText));
|
||||
edits.forEach((e) => this.addEdit(e.fileUri, e.range, e.replacementText));
|
||||
}
|
||||
|
||||
addEdit(filePath: string, range: Range, replacementText: string) {
|
||||
const edits = getOrAdd(this._results, filePath, () => []);
|
||||
addEdit(fileUri: Uri, range: Range, replacementText: string) {
|
||||
const edits = getOrAdd(this._results, fileUri.key, () => []);
|
||||
|
||||
// If there is any overlapping edit, see whether we can merge edits.
|
||||
// We can merge edits, if one of them is 'deletion' or 2 edits has the same
|
||||
@ -70,11 +70,11 @@ export class TextEditTracker {
|
||||
);
|
||||
}
|
||||
|
||||
edits.push({ filePath, range, replacementText });
|
||||
edits.push({ fileUri: fileUri, range, replacementText });
|
||||
}
|
||||
|
||||
addEditWithTextRange(parseResults: ParseResults, range: TextRange, replacementText: string) {
|
||||
const filePath = getFileInfo(parseResults.parseTree).filePath;
|
||||
const filePath = getFileInfo(parseResults.parseTree).fileUri;
|
||||
|
||||
const existing = parseResults.text.substr(range.start, range.length);
|
||||
if (existing === replacementText) {
|
||||
@ -93,7 +93,7 @@ export class TextEditTracker {
|
||||
? (importToDelete.parent as ImportNode).list
|
||||
: (importToDelete.parent as ImportFromNode).imports;
|
||||
|
||||
const filePath = getFileInfo(parseResults.parseTree).filePath;
|
||||
const filePath = getFileInfo(parseResults.parseTree).fileUri;
|
||||
const ranges = getTextRangeForImportNameDeletion(
|
||||
parseResults,
|
||||
imports,
|
||||
@ -183,7 +183,7 @@ export class TextEditTracker {
|
||||
importGroup: ImportGroup,
|
||||
importNameInfo?: ImportNameInfo[]
|
||||
) {
|
||||
const filePath = getFileInfo(parseResults.parseTree).filePath;
|
||||
const fileUri = getFileInfo(parseResults.parseTree).fileUri;
|
||||
|
||||
this.addEdits(
|
||||
...getTextEditsForAutoImportInsertion(
|
||||
@ -193,7 +193,7 @@ export class TextEditTracker {
|
||||
importGroup,
|
||||
parseResults,
|
||||
convertOffsetToPosition(parseResults.parseTree.length, parseResults.tokenizerOutput.lines)
|
||||
).map((e) => ({ filePath, range: e.range, replacementText: e.replacementText }))
|
||||
).map((e) => ({ fileUri, range: e.range, replacementText: e.replacementText }))
|
||||
);
|
||||
}
|
||||
|
||||
@ -220,7 +220,7 @@ export class TextEditTracker {
|
||||
return false;
|
||||
}
|
||||
|
||||
const filePath = getFileInfo(parseResults.parseTree).filePath;
|
||||
const fileUri = getFileInfo(parseResults.parseTree).fileUri;
|
||||
|
||||
const edits = getTextEditsForAutoImportSymbolAddition(importNameInfo, imported, parseResults);
|
||||
if (imported.node !== updateOptions.currentFromImport) {
|
||||
@ -228,7 +228,7 @@ export class TextEditTracker {
|
||||
// node we are working on.
|
||||
// ex) from xxx import yyy <= we are working on here.
|
||||
// from xxx import zzz <= but we found this.
|
||||
this.addEdits(...edits.map((e) => ({ filePath, range: e.range, replacementText: e.replacementText })));
|
||||
this.addEdits(...edits.map((e) => ({ fileUri, range: e.range, replacementText: e.replacementText })));
|
||||
return true;
|
||||
}
|
||||
|
||||
@ -247,9 +247,9 @@ export class TextEditTracker {
|
||||
return false;
|
||||
}
|
||||
|
||||
const deletions = this._getDeletionsForSpan(filePath, edits[0].range);
|
||||
const deletions = this._getDeletionsForSpan(fileUri, edits[0].range);
|
||||
if (deletions.length === 0) {
|
||||
this.addEdit(filePath, edits[0].range, edits[0].replacementText);
|
||||
this.addEdit(fileUri, edits[0].range, edits[0].replacementText);
|
||||
return true;
|
||||
}
|
||||
|
||||
@ -265,13 +265,13 @@ export class TextEditTracker {
|
||||
return false;
|
||||
}
|
||||
|
||||
this._removeEdits(filePath, deletions);
|
||||
this._removeEdits(fileUri, deletions);
|
||||
if (importName.alias) {
|
||||
this._nodesRemoved.delete(importName.alias);
|
||||
}
|
||||
|
||||
this.addEdit(
|
||||
filePath,
|
||||
fileUri,
|
||||
convertTextRangeToRange(importName.name, parseResults.tokenizerOutput.lines),
|
||||
newLastModuleName
|
||||
);
|
||||
@ -279,17 +279,17 @@ export class TextEditTracker {
|
||||
return true;
|
||||
}
|
||||
|
||||
private _getDeletionsForSpan(filePathOrEdit: string | FileEditAction[], range: Range) {
|
||||
const edits = this._getOverlappingForSpan(filePathOrEdit, range);
|
||||
private _getDeletionsForSpan(fileUriOrEdit: Uri | FileEditAction[], range: Range) {
|
||||
const edits = this._getOverlappingForSpan(fileUriOrEdit, range);
|
||||
return edits.filter((e) => e.replacementText === '');
|
||||
}
|
||||
|
||||
private _removeEdits(filePathOrEdit: string | FileEditAction[], edits: FileEditAction[]) {
|
||||
if (isString(filePathOrEdit)) {
|
||||
filePathOrEdit = this._results.get(filePathOrEdit) ?? [];
|
||||
private _removeEdits(fileUriOrEdit: Uri | FileEditAction[], edits: FileEditAction[]) {
|
||||
if (Uri.isUri(fileUriOrEdit)) {
|
||||
fileUriOrEdit = this._results.get(fileUriOrEdit.key) ?? [];
|
||||
}
|
||||
|
||||
removeArrayElements(filePathOrEdit, (f) => edits.some((e) => FileEditAction.areEqual(f, e)));
|
||||
removeArrayElements(fileUriOrEdit, (f) => edits.some((e) => FileEditAction.areEqual(f, e)));
|
||||
}
|
||||
|
||||
private _getEditsToMerge(edits: FileEditAction[], range: Range, replacementText: string) {
|
||||
@ -319,12 +319,12 @@ export class TextEditTracker {
|
||||
);
|
||||
}
|
||||
|
||||
private _getOverlappingForSpan(filePathOrEdit: string | FileEditAction[], range: Range) {
|
||||
if (isString(filePathOrEdit)) {
|
||||
filePathOrEdit = this._results.get(filePathOrEdit) ?? [];
|
||||
private _getOverlappingForSpan(fileUriOrEdit: Uri | FileEditAction[], range: Range) {
|
||||
if (Uri.isUri(fileUriOrEdit)) {
|
||||
fileUriOrEdit = this._results.get(fileUriOrEdit.key) ?? [];
|
||||
}
|
||||
|
||||
return filePathOrEdit.filter((e) => doRangesIntersect(e.range, range));
|
||||
return fileUriOrEdit.filter((e) => doRangesIntersect(e.range, range));
|
||||
}
|
||||
|
||||
private _processNodeRemoved(token: CancellationToken) {
|
||||
@ -343,7 +343,7 @@ export class TextEditTracker {
|
||||
this._pendingNodeToRemove.pop();
|
||||
|
||||
const info = getFileInfo(peekNodeToRemove.parseResults.parseTree);
|
||||
this.addEdit(info.filePath, convertTextRangeToRange(peekNodeToRemove.node, info.lines), '');
|
||||
this.addEdit(info.fileUri, convertTextRangeToRange(peekNodeToRemove.node, info.lines), '');
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -370,11 +370,7 @@ export class TextEditTracker {
|
||||
);
|
||||
|
||||
if (nameNodes.length === nodesRemoved.length) {
|
||||
this.addEdit(
|
||||
info.filePath,
|
||||
ParseTreeUtils.getFullStatementRange(importNode, nodeToRemove.parseResults),
|
||||
''
|
||||
);
|
||||
this.addEdit(info.fileUri, ParseTreeUtils.getFullStatementRange(importNode, nodeToRemove.parseResults), '');
|
||||
|
||||
// Remove nodes that are handled from queue.
|
||||
this._removeNodesHandled(nodesRemoved);
|
||||
@ -397,7 +393,7 @@ export class TextEditTracker {
|
||||
}
|
||||
|
||||
const editSpans = getTextRangeForImportNameDeletion(nodeToRemove.parseResults, nameNodes, ...indices);
|
||||
editSpans.forEach((e) => this.addEdit(info.filePath, convertTextRangeToRange(e, info.lines), ''));
|
||||
editSpans.forEach((e) => this.addEdit(info.fileUri, convertTextRangeToRange(e, info.lines), ''));
|
||||
|
||||
this._removeNodesHandled(nodesRemoved);
|
||||
return true;
|
||||
|
@ -7,6 +7,8 @@
|
||||
* Specifies the range of text within a larger string.
|
||||
*/
|
||||
|
||||
import { Uri } from './uri/uri';
|
||||
|
||||
export interface TextRange {
|
||||
readonly start: number;
|
||||
readonly length: number;
|
||||
@ -131,7 +133,7 @@ export namespace Range {
|
||||
|
||||
// Represents a range within a particular document.
|
||||
export interface DocumentRange {
|
||||
path: string;
|
||||
uri: Uri;
|
||||
range: Range;
|
||||
}
|
||||
|
||||
|
269
packages/pyright-internal/src/common/uri/baseUri.ts
Normal file
269
packages/pyright-internal/src/common/uri/baseUri.ts
Normal 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[];
|
||||
}
|
123
packages/pyright-internal/src/common/uri/emptyUri.ts
Normal file
123
packages/pyright-internal/src/common/uri/emptyUri.ts
Normal 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 '';
|
||||
}
|
||||
}
|
203
packages/pyright-internal/src/common/uri/fileUri.ts
Normal file
203
packages/pyright-internal/src/common/uri/fileUri.ts
Normal 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 : ''}`;
|
||||
}
|
||||
}
|
70
packages/pyright-internal/src/common/uri/memoization.ts
Normal file
70
packages/pyright-internal/src/common/uri/memoization.ts
Normal 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;
|
||||
};
|
||||
}
|
215
packages/pyright-internal/src/common/uri/uri.ts
Normal file
215
packages/pyright-internal/src/common/uri/uri.ts
Normal 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';
|
||||
}
|
||||
}
|
406
packages/pyright-internal/src/common/uri/uriUtils.ts
Normal file
406
packages/pyright-internal/src/common/uri/uriUtils.ts
Normal 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;
|
||||
}
|
207
packages/pyright-internal/src/common/uri/webUri.ts
Normal file
207
packages/pyright-internal/src/common/uri/webUri.ts
Normal 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 : ''}`;
|
||||
}
|
||||
}
|
@ -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';
|
||||
}
|
||||
}
|
@ -19,15 +19,14 @@ import {
|
||||
import { TextDocument } from 'vscode-languageserver-textdocument';
|
||||
import { AnalyzerService } from '../analyzer/service';
|
||||
import { FileEditAction, FileEditActions, TextEditAction } from '../common/editAction';
|
||||
import { convertPathToUri, convertUriToPath } from '../common/pathUtils';
|
||||
import { createMapFromItems } from './collectionUtils';
|
||||
import { isArray } from './core';
|
||||
import { assertNever } from './debug';
|
||||
import { EditableProgram, SourceFileInfo } from './extensibility';
|
||||
import { ReadOnlyFileSystem } from './fileSystem';
|
||||
import { convertRangeToTextRange, convertTextRangeToRange } from './positionUtils';
|
||||
import { TextRange } from './textRange';
|
||||
import { TextRangeCollection } from './textRangeCollection';
|
||||
import { Uri } from './uri/uri';
|
||||
|
||||
export function convertToTextEdits(editActions: TextEditAction[]): TextEdit[] {
|
||||
return editActions.map((editAction) => ({
|
||||
@ -36,14 +35,13 @@ export function convertToTextEdits(editActions: TextEditAction[]): TextEdit[] {
|
||||
}));
|
||||
}
|
||||
|
||||
export function convertToFileTextEdits(filePath: string, editActions: TextEditAction[]): FileEditAction[] {
|
||||
return editActions.map((a) => ({ filePath, ...a }));
|
||||
export function convertToFileTextEdits(fileUri: Uri, editActions: TextEditAction[]): FileEditAction[] {
|
||||
return editActions.map((a) => ({ fileUri, ...a }));
|
||||
}
|
||||
|
||||
export function convertToWorkspaceEdit(fs: ReadOnlyFileSystem, edits: FileEditAction[]): WorkspaceEdit;
|
||||
export function convertToWorkspaceEdit(fs: ReadOnlyFileSystem, edits: FileEditActions): WorkspaceEdit;
|
||||
export function convertToWorkspaceEdit(edits: FileEditAction[]): WorkspaceEdit;
|
||||
export function convertToWorkspaceEdit(edits: FileEditActions): WorkspaceEdit;
|
||||
export function convertToWorkspaceEdit(
|
||||
fs: ReadOnlyFileSystem,
|
||||
edits: FileEditActions,
|
||||
changeAnnotations: {
|
||||
[id: string]: ChangeAnnotation;
|
||||
@ -51,7 +49,6 @@ export function convertToWorkspaceEdit(
|
||||
defaultAnnotationId: string
|
||||
): WorkspaceEdit;
|
||||
export function convertToWorkspaceEdit(
|
||||
fs: ReadOnlyFileSystem,
|
||||
edits: FileEditActions | FileEditAction[],
|
||||
changeAnnotations?: {
|
||||
[id: string]: ChangeAnnotation;
|
||||
@ -59,17 +56,17 @@ export function convertToWorkspaceEdit(
|
||||
defaultAnnotationId = 'default'
|
||||
): WorkspaceEdit {
|
||||
if (isArray(edits)) {
|
||||
return _convertToWorkspaceEditWithChanges(fs, edits);
|
||||
return _convertToWorkspaceEditWithChanges(edits);
|
||||
}
|
||||
|
||||
return _convertToWorkspaceEditWithDocumentChanges(fs, edits, changeAnnotations, defaultAnnotationId);
|
||||
return _convertToWorkspaceEditWithDocumentChanges(edits, changeAnnotations, defaultAnnotationId);
|
||||
}
|
||||
|
||||
export function appendToWorkspaceEdit(fs: ReadOnlyFileSystem, edits: FileEditAction[], workspaceEdit: WorkspaceEdit) {
|
||||
export function appendToWorkspaceEdit(edits: FileEditAction[], workspaceEdit: WorkspaceEdit) {
|
||||
edits.forEach((edit) => {
|
||||
const uri = convertPathToUri(fs, edit.filePath);
|
||||
workspaceEdit.changes![uri] = workspaceEdit.changes![uri] || [];
|
||||
workspaceEdit.changes![uri].push({ range: edit.range, newText: edit.replacementText });
|
||||
const uri = edit.fileUri;
|
||||
workspaceEdit.changes![uri.toString()] = workspaceEdit.changes![uri.toString()] || [];
|
||||
workspaceEdit.changes![uri.toString()].push({ range: edit.range, newText: edit.replacementText });
|
||||
});
|
||||
}
|
||||
|
||||
@ -101,18 +98,18 @@ export function applyTextEditsToString(
|
||||
return current;
|
||||
}
|
||||
|
||||
export function applyWorkspaceEdit(program: EditableProgram, edits: WorkspaceEdit, filesChanged: Set<string>) {
|
||||
export function applyWorkspaceEdit(program: EditableProgram, edits: WorkspaceEdit, filesChanged: Map<string, Uri>) {
|
||||
if (edits.changes) {
|
||||
for (const kv of Object.entries(edits.changes)) {
|
||||
const filePath = convertUriToPath(program.fileSystem, kv[0]);
|
||||
const fileInfo = program.getSourceFileInfo(filePath);
|
||||
const fileUri = Uri.parse(kv[0], program.configOptions.projectRoot.isCaseSensitive);
|
||||
const fileInfo = program.getSourceFileInfo(fileUri);
|
||||
if (!fileInfo || !fileInfo.isTracked) {
|
||||
// We don't allow non user file being modified.
|
||||
continue;
|
||||
}
|
||||
|
||||
applyDocumentChanges(program, fileInfo, kv[1]);
|
||||
filesChanged.add(filePath);
|
||||
filesChanged.set(fileUri.key, fileUri);
|
||||
}
|
||||
}
|
||||
|
||||
@ -120,15 +117,15 @@ export function applyWorkspaceEdit(program: EditableProgram, edits: WorkspaceEdi
|
||||
if (edits.documentChanges) {
|
||||
for (const change of edits.documentChanges) {
|
||||
if (TextDocumentEdit.is(change)) {
|
||||
const filePath = convertUriToPath(program.fileSystem, change.textDocument.uri);
|
||||
const fileInfo = program.getSourceFileInfo(filePath);
|
||||
const fileUri = Uri.parse(change.textDocument.uri, program.configOptions.projectRoot.isCaseSensitive);
|
||||
const fileInfo = program.getSourceFileInfo(fileUri);
|
||||
if (!fileInfo || !fileInfo.isTracked) {
|
||||
// We don't allow non user file being modified.
|
||||
continue;
|
||||
}
|
||||
|
||||
applyDocumentChanges(program, fileInfo, change.edits);
|
||||
filesChanged.add(filePath);
|
||||
filesChanged.set(fileUri.key, fileUri);
|
||||
}
|
||||
|
||||
// For now, we don't support other kinds of text changes.
|
||||
@ -140,30 +137,29 @@ export function applyWorkspaceEdit(program: EditableProgram, edits: WorkspaceEdi
|
||||
export function applyDocumentChanges(program: EditableProgram, fileInfo: SourceFileInfo, edits: TextEdit[]) {
|
||||
if (!fileInfo.isOpenByClient) {
|
||||
const fileContent = fileInfo.sourceFile.getFileContent();
|
||||
program.setFileOpened(fileInfo.sourceFile.getFilePath(), 0, fileContent ?? '', {
|
||||
program.setFileOpened(fileInfo.sourceFile.getUri(), 0, fileContent ?? '', {
|
||||
isTracked: fileInfo.isTracked,
|
||||
ipythonMode: fileInfo.sourceFile.getIPythonMode(),
|
||||
chainedFilePath: fileInfo.chainedSourceFile?.sourceFile.getFilePath(),
|
||||
realFilePath: fileInfo.sourceFile.getRealFilePath(),
|
||||
chainedFileUri: fileInfo.chainedSourceFile?.sourceFile.getUri(),
|
||||
});
|
||||
}
|
||||
|
||||
const version = fileInfo.sourceFile.getClientVersion() ?? 0;
|
||||
const filePath = fileInfo.sourceFile.getFilePath();
|
||||
const fileUri = fileInfo.sourceFile.getUri();
|
||||
const filePath = fileUri.getFilePath();
|
||||
const sourceDoc = TextDocument.create(filePath, 'python', version, fileInfo.sourceFile.getOpenFileContents() ?? '');
|
||||
|
||||
program.setFileOpened(filePath, version + 1, TextDocument.applyEdits(sourceDoc, edits), {
|
||||
program.setFileOpened(fileUri, version + 1, TextDocument.applyEdits(sourceDoc, edits), {
|
||||
isTracked: fileInfo.isTracked,
|
||||
ipythonMode: fileInfo.sourceFile.getIPythonMode(),
|
||||
chainedFilePath: fileInfo.chainedSourceFile?.sourceFile.getFilePath(),
|
||||
realFilePath: fileInfo.sourceFile.getRealFilePath(),
|
||||
chainedFileUri: fileInfo.chainedSourceFile?.sourceFile.getUri(),
|
||||
});
|
||||
}
|
||||
|
||||
export function generateWorkspaceEdit(
|
||||
originalService: AnalyzerService,
|
||||
clonedService: AnalyzerService,
|
||||
filesChanged: Set<string>
|
||||
filesChanged: Map<string, Uri>
|
||||
) {
|
||||
// For now, we won't do text diff to find out minimal text changes. instead, we will
|
||||
// consider whole text of the files are changed. In future, we could consider
|
||||
@ -171,9 +167,9 @@ export function generateWorkspaceEdit(
|
||||
// to support annotation.
|
||||
const edits: WorkspaceEdit = { changes: {} };
|
||||
|
||||
for (const filePath of filesChanged) {
|
||||
const original = originalService.backgroundAnalysisProgram.program.getBoundSourceFile(filePath);
|
||||
const final = clonedService.backgroundAnalysisProgram.program.getBoundSourceFile(filePath);
|
||||
for (const uri of filesChanged.values()) {
|
||||
const original = originalService.backgroundAnalysisProgram.program.getBoundSourceFile(uri);
|
||||
const final = clonedService.backgroundAnalysisProgram.program.getBoundSourceFile(uri);
|
||||
if (!original || !final) {
|
||||
// Both must exist.
|
||||
continue;
|
||||
@ -184,7 +180,7 @@ export function generateWorkspaceEdit(
|
||||
continue;
|
||||
}
|
||||
|
||||
edits.changes![convertPathToUri(originalService.fs, filePath)] = [
|
||||
edits.changes![uri.toString()] = [
|
||||
{
|
||||
range: convertTextRangeToRange(parseResults.parseTree, parseResults.tokenizerOutput.lines),
|
||||
newText: final.getFileContent() ?? '',
|
||||
@ -195,17 +191,16 @@ export function generateWorkspaceEdit(
|
||||
return edits;
|
||||
}
|
||||
|
||||
function _convertToWorkspaceEditWithChanges(fs: ReadOnlyFileSystem, edits: FileEditAction[]) {
|
||||
function _convertToWorkspaceEditWithChanges(edits: FileEditAction[]) {
|
||||
const workspaceEdit: WorkspaceEdit = {
|
||||
changes: {},
|
||||
};
|
||||
|
||||
appendToWorkspaceEdit(fs, edits, workspaceEdit);
|
||||
appendToWorkspaceEdit(edits, workspaceEdit);
|
||||
return workspaceEdit;
|
||||
}
|
||||
|
||||
function _convertToWorkspaceEditWithDocumentChanges(
|
||||
fs: ReadOnlyFileSystem,
|
||||
editActions: FileEditActions,
|
||||
changeAnnotations?: {
|
||||
[id: string]: ChangeAnnotation;
|
||||
@ -223,11 +218,7 @@ function _convertToWorkspaceEditWithDocumentChanges(
|
||||
switch (operation.kind) {
|
||||
case 'create':
|
||||
workspaceEdit.documentChanges!.push(
|
||||
CreateFile.create(
|
||||
convertPathToUri(fs, operation.filePath),
|
||||
/* options */ undefined,
|
||||
defaultAnnotationId
|
||||
)
|
||||
CreateFile.create(operation.fileUri.toString(), /* options */ undefined, defaultAnnotationId)
|
||||
);
|
||||
break;
|
||||
case 'rename':
|
||||
@ -239,11 +230,11 @@ function _convertToWorkspaceEditWithDocumentChanges(
|
||||
}
|
||||
|
||||
// Text edit's file path must refer to original file paths unless it is a new file just created.
|
||||
const mapPerFile = createMapFromItems(editActions.edits, (e) => e.filePath);
|
||||
const mapPerFile = createMapFromItems(editActions.edits, (e) => e.fileUri.key);
|
||||
for (const [key, value] of mapPerFile) {
|
||||
workspaceEdit.documentChanges!.push(
|
||||
TextDocumentEdit.create(
|
||||
{ uri: convertPathToUri(fs, key), version: null },
|
||||
{ uri: key, version: null },
|
||||
Array.from(
|
||||
value.map((v) => ({
|
||||
range: v.range,
|
||||
@ -262,8 +253,8 @@ function _convertToWorkspaceEditWithDocumentChanges(
|
||||
case 'rename':
|
||||
workspaceEdit.documentChanges!.push(
|
||||
RenameFile.create(
|
||||
convertPathToUri(fs, operation.oldFilePath),
|
||||
convertPathToUri(fs, operation.newFilePath),
|
||||
operation.oldFileUri.toString(),
|
||||
operation.newFileUri.toString(),
|
||||
/* options */ undefined,
|
||||
defaultAnnotationId
|
||||
)
|
||||
@ -271,11 +262,7 @@ function _convertToWorkspaceEditWithDocumentChanges(
|
||||
break;
|
||||
case 'delete':
|
||||
workspaceEdit.documentChanges!.push(
|
||||
DeleteFile.create(
|
||||
convertPathToUri(fs, operation.filePath),
|
||||
/* options */ undefined,
|
||||
defaultAnnotationId
|
||||
)
|
||||
DeleteFile.create(operation.fileUri.toString(), /* options */ undefined, defaultAnnotationId)
|
||||
);
|
||||
break;
|
||||
default:
|
||||
|
@ -107,18 +107,11 @@ import { FileSystem, ReadOnlyFileSystem } from './common/fileSystem';
|
||||
import { FileWatcherEventType, FileWatcherHandler } from './common/fileWatcher';
|
||||
import { Host } from './common/host';
|
||||
import { fromLSPAny } from './common/lspUtils';
|
||||
import {
|
||||
convertPathToUri,
|
||||
convertUriToPath,
|
||||
deduplicateFolders,
|
||||
getDirectoryPath,
|
||||
getFileName,
|
||||
isFile,
|
||||
} from './common/pathUtils';
|
||||
import { ProgressReportTracker, ProgressReporter } from './common/progressReporter';
|
||||
import { ServiceProvider } from './common/serviceProvider';
|
||||
import { DocumentRange, Position, Range } from './common/textRange';
|
||||
import { UriParser } from './common/uriParser';
|
||||
import { Uri } from './common/uri/uri';
|
||||
import { deduplicateFolders, isFile } from './common/uri/uriUtils';
|
||||
import { AnalyzerServiceExecutor } from './languageService/analyzerServiceExecutor';
|
||||
import { CallHierarchyProvider } from './languageService/callHierarchyProvider';
|
||||
import { CompletionItemData, CompletionProvider } from './languageService/completionProvider';
|
||||
@ -137,17 +130,17 @@ import { ParseResults } from './parser/parser';
|
||||
import { InitStatus, WellKnownWorkspaceKinds, Workspace, WorkspaceFactory } from './workspaceFactory';
|
||||
|
||||
export interface ServerSettings {
|
||||
venvPath?: string | undefined;
|
||||
pythonPath?: string | undefined;
|
||||
typeshedPath?: string | undefined;
|
||||
stubPath?: string | undefined;
|
||||
venvPath?: Uri | undefined;
|
||||
pythonPath?: Uri | undefined;
|
||||
typeshedPath?: Uri | undefined;
|
||||
stubPath?: Uri | undefined;
|
||||
openFilesOnly?: boolean | undefined;
|
||||
typeCheckingMode?: string | undefined;
|
||||
useLibraryCodeForTypes?: boolean | undefined;
|
||||
disableLanguageServices?: boolean | undefined;
|
||||
disableOrganizeImports?: boolean | undefined;
|
||||
autoSearchPaths?: boolean | undefined;
|
||||
extraPaths?: string[] | undefined;
|
||||
extraPaths?: Uri[] | undefined;
|
||||
watchForSourceChanges?: boolean | undefined;
|
||||
watchForLibraryChanges?: boolean | undefined;
|
||||
watchForConfigChanges?: boolean | undefined;
|
||||
@ -181,23 +174,22 @@ export interface WindowInterface {
|
||||
}
|
||||
|
||||
export interface LanguageServerInterface {
|
||||
readonly rootPath: string;
|
||||
readonly rootUri: Uri;
|
||||
readonly console: ConsoleInterface;
|
||||
readonly window: WindowInterface;
|
||||
readonly supportAdvancedEdits: boolean;
|
||||
|
||||
getWorkspaces(): Promise<Workspace[]>;
|
||||
getWorkspaceForFile(filePath: string): Promise<Workspace>;
|
||||
getWorkspaceForFile(fileUri: Uri): Promise<Workspace>;
|
||||
getSettings(workspace: Workspace): Promise<ServerSettings>;
|
||||
createBackgroundAnalysis(serviceId: string): BackgroundAnalysisBase | undefined;
|
||||
reanalyze(): void;
|
||||
restart(): void;
|
||||
decodeTextDocumentUri(uriString: string): string;
|
||||
}
|
||||
|
||||
export interface ServerOptions {
|
||||
productName: string;
|
||||
rootDirectory: string;
|
||||
rootDirectory: Uri;
|
||||
version: string;
|
||||
cancellationProvider: CancellationProvider;
|
||||
serviceProvider: ServiceProvider;
|
||||
@ -323,7 +315,7 @@ export abstract class LanguageServerBase implements LanguageServerInterface, Dis
|
||||
private _workspaceFoldersChangedDisposable: Disposable | undefined;
|
||||
|
||||
// Global root path - the basis for all global settings.
|
||||
rootPath = '';
|
||||
rootUri = Uri.empty();
|
||||
|
||||
protected client: ClientCapabilities = {
|
||||
hasConfigurationCapability: false,
|
||||
@ -358,16 +350,10 @@ export abstract class LanguageServerBase implements LanguageServerInterface, Dis
|
||||
// The URIs for which diagnostics are reported
|
||||
protected readonly documentsWithDiagnostics = new Set<string>();
|
||||
|
||||
readonly uriParser: UriParser;
|
||||
|
||||
constructor(
|
||||
protected serverOptions: ServerOptions,
|
||||
protected connection: Connection,
|
||||
uriParserFactory = (fs: FileSystem) => new UriParser(fs)
|
||||
) {
|
||||
constructor(protected serverOptions: ServerOptions, protected connection: Connection) {
|
||||
// Stash the base directory into a global variable.
|
||||
// This must happen before fs.getModulePath().
|
||||
(global as any).__rootDirectory = serverOptions.rootDirectory;
|
||||
(global as any).__rootDirectory = serverOptions.rootDirectory.getFilePath();
|
||||
|
||||
this.console.info(
|
||||
`${serverOptions.productName} language server ${
|
||||
@ -379,16 +365,14 @@ export abstract class LanguageServerBase implements LanguageServerInterface, Dis
|
||||
|
||||
this.fs = this.serverOptions.serviceProvider.fs();
|
||||
|
||||
this.uriParser = uriParserFactory(this.fs);
|
||||
|
||||
this.workspaceFactory = new WorkspaceFactory(
|
||||
this.console,
|
||||
this.uriParser,
|
||||
/* isWeb */ false,
|
||||
this.createAnalyzerServiceForWorkspace.bind(this),
|
||||
this.isPythonPathImmutable.bind(this),
|
||||
this.onWorkspaceCreated.bind(this),
|
||||
this.onWorkspaceRemoved.bind(this)
|
||||
this.onWorkspaceRemoved.bind(this),
|
||||
this.fs.isCaseSensitive
|
||||
);
|
||||
|
||||
// Set the working directory to a known location within
|
||||
@ -427,11 +411,6 @@ export abstract class LanguageServerBase implements LanguageServerInterface, Dis
|
||||
this._workspaceFoldersChangedDisposable?.dispose();
|
||||
}
|
||||
|
||||
// Convert uri to path
|
||||
decodeTextDocumentUri(uriString: string): string {
|
||||
return this.uriParser.decodeTextDocumentUri(uriString);
|
||||
}
|
||||
|
||||
abstract createBackgroundAnalysis(serviceId: string): BackgroundAnalysisBase | undefined;
|
||||
|
||||
abstract getSettings(workspace: Workspace): Promise<ServerSettings>;
|
||||
@ -472,12 +451,12 @@ export abstract class LanguageServerBase implements LanguageServerInterface, Dis
|
||||
return workspaces;
|
||||
}
|
||||
|
||||
async getWorkspaceForFile(filePath: string, pythonPath?: string): Promise<Workspace> {
|
||||
return this.workspaceFactory.getWorkspaceForFile(filePath, pythonPath);
|
||||
async getWorkspaceForFile(fileUri: Uri, pythonPath?: Uri): Promise<Workspace> {
|
||||
return this.workspaceFactory.getWorkspaceForFile(fileUri, pythonPath);
|
||||
}
|
||||
|
||||
async getContainingWorkspacesForFile(filePath: string): Promise<Workspace[]> {
|
||||
return this.workspaceFactory.getContainingWorkspacesForFile(filePath);
|
||||
async getContainingWorkspacesForFile(fileUri: Uri): Promise<Workspace[]> {
|
||||
return this.workspaceFactory.getContainingWorkspacesForFile(fileUri);
|
||||
}
|
||||
|
||||
reanalyze() {
|
||||
@ -521,7 +500,10 @@ export abstract class LanguageServerBase implements LanguageServerInterface, Dis
|
||||
(this.console as ConsoleWithLogLevel).level = serverSettings.logLevel ?? LogLevel.Info;
|
||||
|
||||
// Apply the new path to the workspace (before restarting the service).
|
||||
serverSettings.pythonPath = this.workspaceFactory.applyPythonPath(workspace, serverSettings.pythonPath);
|
||||
serverSettings.pythonPath = this.workspaceFactory.applyPythonPath(
|
||||
workspace,
|
||||
serverSettings.pythonPath ? serverSettings.pythonPath : undefined
|
||||
);
|
||||
|
||||
// Then use the updated settings to restart the service.
|
||||
this.updateOptionsAndRestartService(workspace, serverSettings);
|
||||
@ -540,8 +522,8 @@ export abstract class LanguageServerBase implements LanguageServerInterface, Dis
|
||||
serverSettings: ServerSettings,
|
||||
typeStubTargetImportName?: string
|
||||
) {
|
||||
AnalyzerServiceExecutor.runWithOptions(this.rootPath, workspace, serverSettings, typeStubTargetImportName);
|
||||
workspace.searchPathsToWatch = workspace.service.librarySearchPathsToWatch ?? [];
|
||||
AnalyzerServiceExecutor.runWithOptions(this.rootUri, workspace, serverSettings, typeStubTargetImportName);
|
||||
workspace.searchPathsToWatch = workspace.service.librarySearchUrisToWatch ?? [];
|
||||
}
|
||||
|
||||
protected abstract executeCommand(params: ExecuteCommandParams, token: CancellationToken): Promise<any>;
|
||||
@ -553,18 +535,18 @@ export abstract class LanguageServerBase implements LanguageServerInterface, Dis
|
||||
token: CancellationToken
|
||||
): Promise<(Command | CodeAction)[] | undefined | null>;
|
||||
|
||||
protected isPythonPathImmutable(filePath: string): boolean {
|
||||
protected isPythonPathImmutable(fileUri: Uri): boolean {
|
||||
// This function is called to determine if the file is using
|
||||
// a special pythonPath separate from a workspace or not.
|
||||
// The default is no.
|
||||
return false;
|
||||
}
|
||||
|
||||
protected async getConfiguration(scopeUri: string | undefined, section: string) {
|
||||
protected async getConfiguration(scopeUri: Uri | undefined, section: string) {
|
||||
if (this.client.hasConfigurationCapability) {
|
||||
const item: ConfigurationItem = {};
|
||||
if (scopeUri !== undefined) {
|
||||
item.scopeUri = scopeUri;
|
||||
item.scopeUri = scopeUri.toString();
|
||||
}
|
||||
if (section !== undefined) {
|
||||
item.section = section;
|
||||
@ -691,7 +673,7 @@ export abstract class LanguageServerBase implements LanguageServerInterface, Dis
|
||||
setLocaleOverride(params.locale);
|
||||
}
|
||||
|
||||
this.rootPath = params.rootPath || '';
|
||||
this.rootUri = params.rootUri ? Uri.parse(params.rootUri, this.fs.isCaseSensitive) : Uri.empty();
|
||||
|
||||
const capabilities = params.capabilities;
|
||||
this.client.hasConfigurationCapability = !!capabilities.workspace?.configuration;
|
||||
@ -862,7 +844,7 @@ export abstract class LanguageServerBase implements LanguageServerInterface, Dis
|
||||
filter: DefinitionFilter,
|
||||
getDefinitionsFunc: (
|
||||
workspace: Workspace,
|
||||
filePath: string,
|
||||
fileUri: Uri,
|
||||
position: Position,
|
||||
filter: DefinitionFilter,
|
||||
token: CancellationToken
|
||||
@ -870,20 +852,20 @@ export abstract class LanguageServerBase implements LanguageServerInterface, Dis
|
||||
) {
|
||||
this.recordUserInteractionTime();
|
||||
|
||||
const { filePath, position } = this.uriParser.decodeTextDocumentPosition(params.textDocument, params.position);
|
||||
const uri = Uri.parse(params.textDocument.uri, this.fs.isCaseSensitive);
|
||||
|
||||
const workspace = await this.getWorkspaceForFile(filePath);
|
||||
const workspace = await this.getWorkspaceForFile(uri);
|
||||
if (workspace.disableLanguageServices) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const locations = getDefinitionsFunc(workspace, filePath, position, filter, token);
|
||||
const locations = getDefinitionsFunc(workspace, uri, params.position, filter, token);
|
||||
if (!locations) {
|
||||
return undefined;
|
||||
}
|
||||
return locations
|
||||
.filter((loc) => this.canNavigateToFile(loc.path, workspace.service.fs))
|
||||
.map((loc) => Location.create(convertPathToUri(workspace.service.fs, loc.path), loc.range));
|
||||
.filter((loc) => this.canNavigateToFile(loc.uri, workspace.service.fs))
|
||||
.map((loc) => Location.create(loc.uri.toString(), loc.range));
|
||||
}
|
||||
|
||||
protected async onReferences(
|
||||
@ -891,7 +873,7 @@ export abstract class LanguageServerBase implements LanguageServerInterface, Dis
|
||||
token: CancellationToken,
|
||||
workDoneReporter: WorkDoneProgressReporter,
|
||||
resultReporter: ResultProgressReporter<Location[]> | undefined,
|
||||
createDocumentRange?: (filePath: string, result: CollectionResult, parseResults: ParseResults) => DocumentRange,
|
||||
createDocumentRange?: (uri: Uri, result: CollectionResult, parseResults: ParseResults) => DocumentRange,
|
||||
convertToLocation?: (fs: ReadOnlyFileSystem, ranges: DocumentRange) => Location | undefined
|
||||
): Promise<Location[] | null | undefined> {
|
||||
if (this._pendingFindAllRefsCancellationSource) {
|
||||
@ -912,12 +894,9 @@ export abstract class LanguageServerBase implements LanguageServerInterface, Dis
|
||||
this._pendingFindAllRefsCancellationSource = source;
|
||||
|
||||
try {
|
||||
const { filePath, position } = this.uriParser.decodeTextDocumentPosition(
|
||||
params.textDocument,
|
||||
params.position
|
||||
);
|
||||
const uri = Uri.parse(params.textDocument.uri, this.fs.isCaseSensitive);
|
||||
|
||||
const workspace = await this.getWorkspaceForFile(filePath);
|
||||
const workspace = await this.getWorkspaceForFile(uri);
|
||||
if (workspace.disableLanguageServices) {
|
||||
return;
|
||||
}
|
||||
@ -928,7 +907,7 @@ export abstract class LanguageServerBase implements LanguageServerInterface, Dis
|
||||
source.token,
|
||||
createDocumentRange,
|
||||
convertToLocation
|
||||
).reportReferences(filePath, position, params.context.includeDeclaration, resultReporter);
|
||||
).reportReferences(uri, params.position, params.context.includeDeclaration, resultReporter);
|
||||
}, token);
|
||||
} finally {
|
||||
progress.reporter.done();
|
||||
@ -942,8 +921,8 @@ export abstract class LanguageServerBase implements LanguageServerInterface, Dis
|
||||
): Promise<DocumentSymbol[] | SymbolInformation[] | null | undefined> {
|
||||
this.recordUserInteractionTime();
|
||||
|
||||
const filePath = this.uriParser.decodeTextDocumentUri(params.textDocument.uri);
|
||||
const workspace = await this.getWorkspaceForFile(filePath);
|
||||
const uri = Uri.parse(params.textDocument.uri, this.fs.isCaseSensitive);
|
||||
const workspace = await this.getWorkspaceForFile(uri);
|
||||
if (workspace.disableLanguageServices) {
|
||||
return undefined;
|
||||
}
|
||||
@ -951,7 +930,7 @@ export abstract class LanguageServerBase implements LanguageServerInterface, Dis
|
||||
return workspace.service.run((program) => {
|
||||
return new DocumentSymbolProvider(
|
||||
program,
|
||||
filePath,
|
||||
uri,
|
||||
this.client.hasHierarchicalDocumentSymbolCapability,
|
||||
token
|
||||
).getSymbols();
|
||||
@ -974,11 +953,11 @@ export abstract class LanguageServerBase implements LanguageServerInterface, Dis
|
||||
}
|
||||
|
||||
protected async onHover(params: HoverParams, token: CancellationToken) {
|
||||
const { filePath, position } = this.uriParser.decodeTextDocumentPosition(params.textDocument, params.position);
|
||||
const workspace = await this.getWorkspaceForFile(filePath);
|
||||
const uri = Uri.parse(params.textDocument.uri, this.fs.isCaseSensitive);
|
||||
const workspace = await this.getWorkspaceForFile(uri);
|
||||
|
||||
return workspace.service.run((program) => {
|
||||
return new HoverProvider(program, filePath, position, this.client.hoverContentFormat, token).getHover();
|
||||
return new HoverProvider(program, uri, params.position, this.client.hoverContentFormat, token).getHover();
|
||||
}, token);
|
||||
}
|
||||
|
||||
@ -986,11 +965,11 @@ export abstract class LanguageServerBase implements LanguageServerInterface, Dis
|
||||
params: DocumentHighlightParams,
|
||||
token: CancellationToken
|
||||
): Promise<DocumentHighlight[] | null | undefined> {
|
||||
const { filePath, position } = this.uriParser.decodeTextDocumentPosition(params.textDocument, params.position);
|
||||
const workspace = await this.getWorkspaceForFile(filePath);
|
||||
const uri = Uri.parse(params.textDocument.uri, this.fs.isCaseSensitive);
|
||||
const workspace = await this.getWorkspaceForFile(uri);
|
||||
|
||||
return workspace.service.run((program) => {
|
||||
return new DocumentHighlightProvider(program, filePath, position, token).getDocumentHighlight();
|
||||
return new DocumentHighlightProvider(program, uri, params.position, token).getDocumentHighlight();
|
||||
}, token);
|
||||
}
|
||||
|
||||
@ -998,9 +977,9 @@ export abstract class LanguageServerBase implements LanguageServerInterface, Dis
|
||||
params: SignatureHelpParams,
|
||||
token: CancellationToken
|
||||
): Promise<SignatureHelp | undefined | null> {
|
||||
const { filePath, position } = this.uriParser.decodeTextDocumentPosition(params.textDocument, params.position);
|
||||
const uri = Uri.parse(params.textDocument.uri, this.fs.isCaseSensitive);
|
||||
|
||||
const workspace = await this.getWorkspaceForFile(filePath);
|
||||
const workspace = await this.getWorkspaceForFile(uri);
|
||||
if (workspace.disableLanguageServices) {
|
||||
return;
|
||||
}
|
||||
@ -1008,8 +987,8 @@ export abstract class LanguageServerBase implements LanguageServerInterface, Dis
|
||||
return workspace.service.run((program) => {
|
||||
return new SignatureHelpProvider(
|
||||
program,
|
||||
filePath,
|
||||
position,
|
||||
uri,
|
||||
params.position,
|
||||
this.client.signatureDocFormat,
|
||||
this.client.hasSignatureLabelOffsetCapability,
|
||||
this.client.hasActiveParameterCapability,
|
||||
@ -1040,8 +1019,8 @@ export abstract class LanguageServerBase implements LanguageServerInterface, Dis
|
||||
}
|
||||
|
||||
protected async onCompletion(params: CompletionParams, token: CancellationToken): Promise<CompletionList | null> {
|
||||
const { filePath, position } = this.uriParser.decodeTextDocumentPosition(params.textDocument, params.position);
|
||||
const workspace = await this.getWorkspaceForFile(filePath);
|
||||
const uri = Uri.parse(params.textDocument.uri, this.fs.isCaseSensitive);
|
||||
const workspace = await this.getWorkspaceForFile(uri);
|
||||
if (workspace.disableLanguageServices) {
|
||||
return null;
|
||||
}
|
||||
@ -1049,9 +1028,9 @@ export abstract class LanguageServerBase implements LanguageServerInterface, Dis
|
||||
return workspace.service.run((program) => {
|
||||
const completions = new CompletionProvider(
|
||||
program,
|
||||
workspace.rootPath,
|
||||
filePath,
|
||||
position,
|
||||
workspace.rootUri,
|
||||
uri,
|
||||
params.position,
|
||||
{
|
||||
format: this.client.completionDocFormat,
|
||||
snippet: this.client.completionSupportsSnippet,
|
||||
@ -1074,13 +1053,14 @@ export abstract class LanguageServerBase implements LanguageServerInterface, Dis
|
||||
// cache that result and never call us back.
|
||||
protected async onCompletionResolve(params: CompletionItem, token: CancellationToken): Promise<CompletionItem> {
|
||||
const completionItemData = fromLSPAny<CompletionItemData>(params.data);
|
||||
if (completionItemData && completionItemData.filePath) {
|
||||
const workspace = await this.getWorkspaceForFile(completionItemData.filePath);
|
||||
if (completionItemData && completionItemData.uri) {
|
||||
const uri = Uri.parse(completionItemData.uri, this.fs.isCaseSensitive);
|
||||
const workspace = await this.getWorkspaceForFile(uri);
|
||||
workspace.service.run((program) => {
|
||||
return new CompletionProvider(
|
||||
program,
|
||||
workspace.rootPath,
|
||||
completionItemData.filePath,
|
||||
workspace.rootUri,
|
||||
uri,
|
||||
completionItemData.position,
|
||||
{
|
||||
format: this.client.completionDocFormat,
|
||||
@ -1098,16 +1078,16 @@ export abstract class LanguageServerBase implements LanguageServerInterface, Dis
|
||||
params: PrepareRenameParams,
|
||||
token: CancellationToken
|
||||
): Promise<Range | { range: Range; placeholder: string } | null> {
|
||||
const { filePath, position } = this.uriParser.decodeTextDocumentPosition(params.textDocument, params.position);
|
||||
const isUntitled = this.uriParser.isUntitled(params.textDocument.uri);
|
||||
const uri = Uri.parse(params.textDocument.uri, this.fs.isCaseSensitive);
|
||||
const isUntitled = uri.isUntitled();
|
||||
|
||||
const workspace = await this.getWorkspaceForFile(filePath);
|
||||
const workspace = await this.getWorkspaceForFile(uri);
|
||||
if (workspace.disableLanguageServices) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return workspace.service.run((program) => {
|
||||
return new RenameProvider(program, filePath, position, token).canRenameSymbol(
|
||||
return new RenameProvider(program, uri, params.position, token).canRenameSymbol(
|
||||
workspace.kinds.includes(WellKnownWorkspaceKinds.Default),
|
||||
isUntitled
|
||||
);
|
||||
@ -1118,16 +1098,16 @@ export abstract class LanguageServerBase implements LanguageServerInterface, Dis
|
||||
params: RenameParams,
|
||||
token: CancellationToken
|
||||
): Promise<WorkspaceEdit | null | undefined> {
|
||||
const { filePath, position } = this.uriParser.decodeTextDocumentPosition(params.textDocument, params.position);
|
||||
const isUntitled = this.uriParser.isUntitled(params.textDocument.uri);
|
||||
const uri = Uri.parse(params.textDocument.uri, this.fs.isCaseSensitive);
|
||||
const isUntitled = uri.isUntitled();
|
||||
|
||||
const workspace = await this.getWorkspaceForFile(filePath);
|
||||
const workspace = await this.getWorkspaceForFile(uri);
|
||||
if (workspace.disableLanguageServices) {
|
||||
return;
|
||||
}
|
||||
|
||||
return workspace.service.run((program) => {
|
||||
return new RenameProvider(program, filePath, position, token).renameSymbol(
|
||||
return new RenameProvider(program, uri, params.position, token).renameSymbol(
|
||||
params.newName,
|
||||
workspace.kinds.includes(WellKnownWorkspaceKinds.Default),
|
||||
isUntitled
|
||||
@ -1139,28 +1119,28 @@ export abstract class LanguageServerBase implements LanguageServerInterface, Dis
|
||||
params: CallHierarchyPrepareParams,
|
||||
token: CancellationToken
|
||||
): Promise<CallHierarchyItem[] | null> {
|
||||
const { filePath, position } = this.uriParser.decodeTextDocumentPosition(params.textDocument, params.position);
|
||||
const uri = Uri.parse(params.textDocument.uri, this.fs.isCaseSensitive);
|
||||
|
||||
const workspace = await this.getWorkspaceForFile(filePath);
|
||||
const workspace = await this.getWorkspaceForFile(uri);
|
||||
if (workspace.disableLanguageServices) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return workspace.service.run((program) => {
|
||||
return new CallHierarchyProvider(program, filePath, position, token).onPrepare();
|
||||
return new CallHierarchyProvider(program, uri, params.position, token).onPrepare();
|
||||
}, token);
|
||||
}
|
||||
|
||||
protected async onCallHierarchyIncomingCalls(params: CallHierarchyIncomingCallsParams, token: CancellationToken) {
|
||||
const { filePath, position } = this.uriParser.decodeTextDocumentPosition(params.item, params.item.range.start);
|
||||
const uri = Uri.parse(params.item.uri, this.fs.isCaseSensitive);
|
||||
|
||||
const workspace = await this.getWorkspaceForFile(filePath);
|
||||
const workspace = await this.getWorkspaceForFile(uri);
|
||||
if (workspace.disableLanguageServices) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return workspace.service.run((program) => {
|
||||
return new CallHierarchyProvider(program, filePath, position, token).getIncomingCalls();
|
||||
return new CallHierarchyProvider(program, uri, params.item.range.start, token).getIncomingCalls();
|
||||
}, token);
|
||||
}
|
||||
|
||||
@ -1168,46 +1148,51 @@ export abstract class LanguageServerBase implements LanguageServerInterface, Dis
|
||||
params: CallHierarchyOutgoingCallsParams,
|
||||
token: CancellationToken
|
||||
): Promise<CallHierarchyOutgoingCall[] | null> {
|
||||
const { filePath, position } = this.uriParser.decodeTextDocumentPosition(params.item, params.item.range.start);
|
||||
const uri = Uri.parse(params.item.uri, this.fs.isCaseSensitive);
|
||||
|
||||
const workspace = await this.getWorkspaceForFile(filePath);
|
||||
const workspace = await this.getWorkspaceForFile(uri);
|
||||
if (workspace.disableLanguageServices) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return workspace.service.run((program) => {
|
||||
return new CallHierarchyProvider(program, filePath, position, token).getOutgoingCalls();
|
||||
return new CallHierarchyProvider(program, uri, params.item.range.start, token).getOutgoingCalls();
|
||||
}, token);
|
||||
}
|
||||
|
||||
protected async onDidOpenTextDocument(params: DidOpenTextDocumentParams, ipythonMode = IPythonMode.None) {
|
||||
const filePath = this.uriParser.decodeTextDocumentUri(params.textDocument.uri);
|
||||
const uri = Uri.parse(params.textDocument.uri, this.fs.isCaseSensitive);
|
||||
|
||||
let doc = this.openFileMap.get(filePath);
|
||||
let doc = this.openFileMap.get(uri.key);
|
||||
if (doc) {
|
||||
// We shouldn't get an open text document request for an already-opened doc.
|
||||
this.console.error(`Received redundant open text document command for ${filePath}`);
|
||||
this.console.error(`Received redundant open text document command for ${uri}`);
|
||||
TextDocument.update(doc, [{ text: params.textDocument.text }], params.textDocument.version);
|
||||
} else {
|
||||
doc = TextDocument.create(filePath, 'python', params.textDocument.version, params.textDocument.text);
|
||||
doc = TextDocument.create(
|
||||
params.textDocument.uri,
|
||||
'python',
|
||||
params.textDocument.version,
|
||||
params.textDocument.text
|
||||
);
|
||||
}
|
||||
this.openFileMap.set(filePath, doc);
|
||||
this.openFileMap.set(uri.key, doc);
|
||||
|
||||
// Send this open to all the workspaces that might contain this file.
|
||||
const workspaces = await this.getContainingWorkspacesForFile(filePath);
|
||||
const workspaces = await this.getContainingWorkspacesForFile(uri);
|
||||
workspaces.forEach((w) => {
|
||||
w.service.setFileOpened(filePath, params.textDocument.version, params.textDocument.text, ipythonMode);
|
||||
w.service.setFileOpened(uri, params.textDocument.version, params.textDocument.text, ipythonMode);
|
||||
});
|
||||
}
|
||||
|
||||
protected async onDidChangeTextDocument(params: DidChangeTextDocumentParams, ipythonMode = IPythonMode.None) {
|
||||
this.recordUserInteractionTime();
|
||||
|
||||
const filePath = this.uriParser.decodeTextDocumentUri(params.textDocument.uri);
|
||||
const doc = this.openFileMap.get(filePath);
|
||||
const uri = Uri.parse(params.textDocument.uri, this.fs.isCaseSensitive);
|
||||
const doc = this.openFileMap.get(uri.key);
|
||||
if (!doc) {
|
||||
// We shouldn't get a change text request for a closed doc.
|
||||
this.console.error(`Received change text document command for closed file ${filePath}`);
|
||||
this.console.error(`Received change text document command for closed file ${uri}`);
|
||||
return;
|
||||
}
|
||||
|
||||
@ -1215,27 +1200,27 @@ export abstract class LanguageServerBase implements LanguageServerInterface, Dis
|
||||
const newContents = doc.getText();
|
||||
|
||||
// Send this change to all the workspaces that might contain this file.
|
||||
const workspaces = await this.getContainingWorkspacesForFile(filePath);
|
||||
const workspaces = await this.getContainingWorkspacesForFile(uri);
|
||||
workspaces.forEach((w) => {
|
||||
w.service.updateOpenFileContents(filePath, params.textDocument.version, newContents, ipythonMode);
|
||||
w.service.updateOpenFileContents(uri, params.textDocument.version, newContents, ipythonMode);
|
||||
});
|
||||
}
|
||||
|
||||
protected async onDidCloseTextDocument(params: DidCloseTextDocumentParams) {
|
||||
const filePath = this.uriParser.decodeTextDocumentUri(params.textDocument.uri);
|
||||
const uri = Uri.parse(params.textDocument.uri, this.fs.isCaseSensitive);
|
||||
|
||||
// Send this close to all the workspaces that might contain this file.
|
||||
const workspaces = await this.getContainingWorkspacesForFile(filePath);
|
||||
const workspaces = await this.getContainingWorkspacesForFile(uri);
|
||||
workspaces.forEach((w) => {
|
||||
w.service.setFileClosed(filePath);
|
||||
w.service.setFileClosed(uri);
|
||||
});
|
||||
|
||||
this.openFileMap.delete(filePath);
|
||||
this.openFileMap.delete(uri.key);
|
||||
}
|
||||
|
||||
protected onDidChangeWatchedFiles(params: DidChangeWatchedFilesParams) {
|
||||
params.changes.forEach((change) => {
|
||||
const filePath = this.fs.realCasePath(this.uriParser.decodeTextDocumentUri(change.uri));
|
||||
const filePath = this.fs.realCasePath(Uri.parse(change.uri, this.fs.isCaseSensitive));
|
||||
const eventType: FileWatcherEventType = change.type === 1 ? 'add' : 'change';
|
||||
this.serverOptions.fileWatcherHandler.onFileChange(eventType, filePath);
|
||||
});
|
||||
@ -1302,7 +1287,7 @@ export abstract class LanguageServerBase implements LanguageServerInterface, Dis
|
||||
protected convertDiagnostics(fs: FileSystem, fileDiagnostics: FileDiagnostics): PublishDiagnosticsParams[] {
|
||||
return [
|
||||
{
|
||||
uri: convertPathToUri(fs, fileDiagnostics.filePath),
|
||||
uri: fileDiagnostics.fileUri.toString(),
|
||||
version: fileDiagnostics.version,
|
||||
diagnostics: this._convertDiagnostics(fs, fileDiagnostics.diagnostics),
|
||||
},
|
||||
@ -1316,7 +1301,7 @@ export abstract class LanguageServerBase implements LanguageServerInterface, Dis
|
||||
protected onAnalysisCompletedHandler(fs: FileSystem, results: AnalysisResults): void {
|
||||
// Send the computed diagnostics to the client.
|
||||
results.diagnostics.forEach((fileDiag) => {
|
||||
if (!this.canNavigateToFile(fileDiag.filePath, fs)) {
|
||||
if (!this.canNavigateToFile(fileDiag.fileUri, fs)) {
|
||||
return;
|
||||
}
|
||||
|
||||
@ -1361,11 +1346,11 @@ export abstract class LanguageServerBase implements LanguageServerInterface, Dis
|
||||
const otherWorkspaces = this.workspaceFactory.items().filter((w) => w !== workspace);
|
||||
|
||||
for (const uri of documentsWithDiagnosticsList) {
|
||||
const filePath = convertUriToPath(workspace.service.fs, uri);
|
||||
const fileUri = Uri.parse(uri, this.fs.isCaseSensitive);
|
||||
|
||||
if (workspace.service.isTracked(filePath)) {
|
||||
if (workspace.service.isTracked(fileUri)) {
|
||||
// Do not clean up diagnostics for files tracked by multiple workspaces
|
||||
if (otherWorkspaces.some((w) => w.service.isTracked(filePath))) {
|
||||
if (otherWorkspaces.some((w) => w.service.isTracked(fileUri))) {
|
||||
continue;
|
||||
}
|
||||
this.sendDiagnostics([
|
||||
@ -1380,8 +1365,7 @@ export abstract class LanguageServerBase implements LanguageServerInterface, Dis
|
||||
|
||||
protected createAnalyzerServiceForWorkspace(
|
||||
name: string,
|
||||
_rootPath: string,
|
||||
_uri: string,
|
||||
uri: Uri,
|
||||
kinds: string[],
|
||||
services?: WorkspaceServices
|
||||
): AnalyzerService {
|
||||
@ -1422,7 +1406,7 @@ export abstract class LanguageServerBase implements LanguageServerInterface, Dis
|
||||
|
||||
protected abstract createProgressReporter(): ProgressReporter;
|
||||
|
||||
protected canNavigateToFile(path: string, fs: FileSystem): boolean {
|
||||
protected canNavigateToFile(path: Uri, fs: FileSystem): boolean {
|
||||
return canNavigateToFile(fs, path);
|
||||
}
|
||||
|
||||
@ -1481,13 +1465,13 @@ export abstract class LanguageServerBase implements LanguageServerInterface, Dis
|
||||
const foldersToWatch = deduplicateFolders(
|
||||
this.workspaceFactory
|
||||
.getNonDefaultWorkspaces()
|
||||
.map((w) => w.searchPathsToWatch.filter((p) => !p.startsWith(w.rootPath)))
|
||||
.map((w) => w.searchPathsToWatch.filter((p) => !p.startsWith(w.rootUri)))
|
||||
);
|
||||
|
||||
foldersToWatch.forEach((p) => {
|
||||
const globPattern = isFile(this.fs, p, /* treatZipDirectoryAsFile */ true)
|
||||
? { baseUri: convertPathToUri(this.fs, getDirectoryPath(p)), pattern: getFileName(p) }
|
||||
: { baseUri: convertPathToUri(this.fs, p), pattern: '**' };
|
||||
? { baseUri: p.getDirectory().toString(), pattern: p.fileName }
|
||||
: { baseUri: p.toString(), pattern: '**' };
|
||||
|
||||
watchers.push({ globPattern, kind: watchKind });
|
||||
});
|
||||
@ -1582,10 +1566,10 @@ export abstract class LanguageServerBase implements LanguageServerInterface, Dis
|
||||
const relatedInfo = diag.getRelatedInfo();
|
||||
if (relatedInfo.length > 0) {
|
||||
vsDiag.relatedInformation = relatedInfo
|
||||
.filter((info) => this.canNavigateToFile(info.filePath, fs))
|
||||
.filter((info) => this.canNavigateToFile(info.uri, fs))
|
||||
.map((info) =>
|
||||
DiagnosticRelatedInformation.create(
|
||||
Location.create(convertPathToUri(fs, info.filePath), info.range),
|
||||
Location.create(info.uri.toString(), info.range),
|
||||
info.message
|
||||
)
|
||||
);
|
||||
|
@ -13,7 +13,7 @@ import { AnalyzerService, getNextServiceId } from '../analyzer/service';
|
||||
import { CommandLineOptions } from '../common/commandLineOptions';
|
||||
import { LogLevel } from '../common/console';
|
||||
import { FileSystem } from '../common/fileSystem';
|
||||
import { combinePaths } from '../common/pathUtils';
|
||||
import { Uri } from '../common/uri/uri';
|
||||
import { LanguageServerInterface, ServerSettings } from '../languageServerBase';
|
||||
import { WellKnownWorkspaceKinds, Workspace, createInitStatus } from '../workspaceFactory';
|
||||
|
||||
@ -25,15 +25,15 @@ export interface CloneOptions {
|
||||
|
||||
export class AnalyzerServiceExecutor {
|
||||
static runWithOptions(
|
||||
languageServiceRootPath: string,
|
||||
languageServiceRootUri: Uri,
|
||||
workspace: Workspace,
|
||||
serverSettings: ServerSettings,
|
||||
typeStubTargetImportName?: string,
|
||||
trackFiles = true
|
||||
): void {
|
||||
const commandLineOptions = getEffectiveCommandLineOptions(
|
||||
languageServiceRootPath,
|
||||
workspace.rootPath,
|
||||
languageServiceRootUri.getFilePath(),
|
||||
workspace.rootUri.getFilePath(),
|
||||
serverSettings,
|
||||
trackFiles,
|
||||
typeStubTargetImportName,
|
||||
@ -58,8 +58,7 @@ export class AnalyzerServiceExecutor {
|
||||
const tempWorkspace: Workspace = {
|
||||
...workspace,
|
||||
workspaceName: `temp workspace for cloned service`,
|
||||
rootPath: workspace.rootPath,
|
||||
uri: workspace.uri,
|
||||
rootUri: workspace.rootUri,
|
||||
pythonPath: workspace.pythonPath,
|
||||
pythonPathKind: workspace.pythonPathKind,
|
||||
kinds: [...workspace.kinds, WellKnownWorkspaceKinds.Cloned],
|
||||
@ -78,7 +77,7 @@ export class AnalyzerServiceExecutor {
|
||||
|
||||
const serverSettings = await ls.getSettings(workspace);
|
||||
AnalyzerServiceExecutor.runWithOptions(
|
||||
ls.rootPath,
|
||||
ls.rootUri,
|
||||
tempWorkspace,
|
||||
serverSettings,
|
||||
options.typeStubTargetImportName,
|
||||
@ -120,21 +119,15 @@ function getEffectiveCommandLineOptions(
|
||||
}
|
||||
|
||||
if (serverSettings.venvPath) {
|
||||
commandLineOptions.venvPath = combinePaths(
|
||||
workspaceRootPath || languageServiceRootPath,
|
||||
serverSettings.venvPath
|
||||
);
|
||||
commandLineOptions.venvPath = serverSettings.venvPath.getFilePath();
|
||||
}
|
||||
|
||||
if (serverSettings.pythonPath) {
|
||||
// The Python VS Code extension treats the value "python" specially. This means
|
||||
// the local python interpreter should be used rather than interpreting the
|
||||
// setting value as a path to the interpreter. We'll simply ignore it in this case.
|
||||
if (!isPythonBinary(serverSettings.pythonPath)) {
|
||||
commandLineOptions.pythonPath = combinePaths(
|
||||
workspaceRootPath || languageServiceRootPath,
|
||||
serverSettings.pythonPath
|
||||
);
|
||||
if (!isPythonBinary(serverSettings.pythonPath.getFilePath())) {
|
||||
commandLineOptions.pythonPath = serverSettings.pythonPath.getFilePath();
|
||||
}
|
||||
}
|
||||
|
||||
@ -142,11 +135,11 @@ function getEffectiveCommandLineOptions(
|
||||
// Pyright supports only one typeshed path currently, whereas the
|
||||
// official VS Code Python extension supports multiple typeshed paths.
|
||||
// We'll use the first one specified and ignore the rest.
|
||||
commandLineOptions.typeshedPath = serverSettings.typeshedPath;
|
||||
commandLineOptions.typeshedPath = serverSettings.typeshedPath.getFilePath();
|
||||
}
|
||||
|
||||
if (serverSettings.stubPath) {
|
||||
commandLineOptions.stubPath = serverSettings.stubPath;
|
||||
commandLineOptions.stubPath = serverSettings.stubPath.getFilePath();
|
||||
}
|
||||
|
||||
if (serverSettings.logLevel === LogLevel.Log) {
|
||||
@ -160,7 +153,7 @@ function getEffectiveCommandLineOptions(
|
||||
}
|
||||
|
||||
commandLineOptions.autoSearchPaths = serverSettings.autoSearchPaths;
|
||||
commandLineOptions.extraPaths = serverSettings.extraPaths;
|
||||
commandLineOptions.extraPaths = serverSettings.extraPaths?.map((e) => e.getFilePath()) ?? [];
|
||||
commandLineOptions.diagnosticSeverityOverrides = serverSettings.diagnosticSeverityOverrides;
|
||||
|
||||
commandLineOptions.includeFileSpecs = serverSettings.includeFileSpecs ?? [];
|
||||
|
@ -12,15 +12,15 @@ import { DeclarationType } from '../analyzer/declaration';
|
||||
import { ImportResolver, ModuleNameAndType } from '../analyzer/importResolver';
|
||||
import { ImportType } from '../analyzer/importResult';
|
||||
import {
|
||||
ImportGroup,
|
||||
ImportNameInfo,
|
||||
ImportStatements,
|
||||
ModuleNameInfo,
|
||||
getImportGroup,
|
||||
getImportGroupFromModuleNameAndType,
|
||||
getTextEditsForAutoImportInsertion,
|
||||
getTextEditsForAutoImportSymbolAddition,
|
||||
getTopLevelImports,
|
||||
ImportGroup,
|
||||
ImportNameInfo,
|
||||
ImportStatements,
|
||||
ModuleNameInfo,
|
||||
} from '../analyzer/importStatementUtils';
|
||||
import { isUserCode } from '../analyzer/sourceFileInfoUtils';
|
||||
import { Symbol } from '../analyzer/symbol';
|
||||
@ -30,13 +30,14 @@ import { throwIfCancellationRequested } from '../common/cancellationUtils';
|
||||
import { appendArray } from '../common/collectionUtils';
|
||||
import { ExecutionEnvironment } from '../common/configOptions';
|
||||
import { TextEditAction } from '../common/editAction';
|
||||
import { combinePaths, getDirectoryPath, getFileName, stripFileExtension } from '../common/pathUtils';
|
||||
import { SourceFileInfo } from '../common/extensibility';
|
||||
import { stripFileExtension } from '../common/pathUtils';
|
||||
import * as StringUtils from '../common/stringUtils';
|
||||
import { Position } from '../common/textRange';
|
||||
import { Uri } from '../common/uri/uri';
|
||||
import { ParseNodeType } from '../parser/parseNodes';
|
||||
import { ParseResults } from '../parser/parser';
|
||||
import { CompletionMap } from './completionProvider';
|
||||
import { SourceFileInfo } from '../common/extensibility';
|
||||
import { IndexAliasData } from './symbolIndexer';
|
||||
|
||||
export interface AutoImportSymbol {
|
||||
@ -47,6 +48,7 @@ export interface AutoImportSymbol {
|
||||
}
|
||||
|
||||
export interface ModuleSymbolTable {
|
||||
uri: Uri;
|
||||
forEach(callbackfn: (symbol: AutoImportSymbol, name: string, library: boolean) => void): void;
|
||||
}
|
||||
|
||||
@ -54,8 +56,8 @@ export type ModuleSymbolMap = Map<string, ModuleSymbolTable>;
|
||||
|
||||
export interface AutoImportResult {
|
||||
readonly name: string;
|
||||
readonly declPath: string;
|
||||
readonly originalDeclPath: string;
|
||||
readonly declUri: Uri;
|
||||
readonly originalDeclUri: Uri;
|
||||
readonly insertionText: string;
|
||||
readonly symbol?: Symbol;
|
||||
readonly source?: string;
|
||||
@ -74,7 +76,7 @@ export interface ImportParts {
|
||||
readonly importName: string;
|
||||
readonly symbolName?: string;
|
||||
readonly importFrom?: string;
|
||||
readonly filePath: string;
|
||||
readonly fileUri: Uri;
|
||||
readonly dotCount: number;
|
||||
readonly moduleNameAndType: ModuleNameAndType;
|
||||
}
|
||||
@ -85,7 +87,7 @@ export interface ImportAliasData {
|
||||
readonly symbol?: Symbol;
|
||||
readonly kind?: SymbolKind;
|
||||
readonly itemKind?: CompletionItemKind;
|
||||
readonly filePath: string;
|
||||
readonly fileUri: Uri;
|
||||
}
|
||||
|
||||
export type AutoImportResultMap = Map<string, AutoImportResult[]>;
|
||||
@ -106,13 +108,13 @@ export function addModuleSymbolsMap(files: readonly SourceFileInfo[], moduleSymb
|
||||
return;
|
||||
}
|
||||
|
||||
const filePath = file.sourceFile.getFilePath();
|
||||
const uri = file.sourceFile.getUri();
|
||||
const symbolTable = file.sourceFile.getModuleSymbolTable();
|
||||
if (!symbolTable) {
|
||||
return;
|
||||
}
|
||||
|
||||
const fileName = stripFileExtension(getFileName(filePath));
|
||||
const fileName = stripFileExtension(uri.fileName);
|
||||
|
||||
// Don't offer imports from files that are named with private
|
||||
// naming semantics like "_ast.py".
|
||||
@ -120,7 +122,8 @@ export function addModuleSymbolsMap(files: readonly SourceFileInfo[], moduleSymb
|
||||
return;
|
||||
}
|
||||
|
||||
moduleSymbolMap.set(filePath, {
|
||||
moduleSymbolMap.set(uri.key, {
|
||||
uri,
|
||||
forEach(callbackfn: (value: AutoImportSymbol, key: string, library: boolean) => void): void {
|
||||
symbolTable.forEach((symbol, name) => {
|
||||
if (!isVisibleExternally(symbol)) {
|
||||
@ -206,12 +209,12 @@ export class AutoImporter {
|
||||
results: AutoImportResultMap,
|
||||
token: CancellationToken
|
||||
) {
|
||||
this.moduleSymbolMap.forEach((topLevelSymbols, filePath) => {
|
||||
this.moduleSymbolMap.forEach((topLevelSymbols, key) => {
|
||||
// See if this file should be offered as an implicit import.
|
||||
const isStubFileOrHasInit = this.isStubFileOrHasInit(this.moduleSymbolMap!, filePath);
|
||||
const isStubFileOrHasInit = this.isStubFileOrHasInit(this.moduleSymbolMap!, topLevelSymbols.uri);
|
||||
this.processModuleSymbolTable(
|
||||
topLevelSymbols,
|
||||
filePath,
|
||||
topLevelSymbols.uri,
|
||||
word,
|
||||
similarityLimit,
|
||||
isStubFileOrHasInit,
|
||||
@ -244,7 +247,7 @@ export class AutoImporter {
|
||||
|
||||
// If import statement for the module already exist, then bail out.
|
||||
// ex) import module[.submodule] or from module[.submodule] import symbol
|
||||
if (this._importStatements.mapByFilePath.has(importAliasData.importParts.filePath)) {
|
||||
if (this._importStatements.mapByFilePath.has(importAliasData.importParts.fileUri.key)) {
|
||||
return;
|
||||
}
|
||||
|
||||
@ -281,7 +284,7 @@ export class AutoImporter {
|
||||
},
|
||||
importAliasData.importParts.importName,
|
||||
importAliasData.importGroup,
|
||||
importAliasData.importParts.filePath
|
||||
importAliasData.importParts.fileUri
|
||||
);
|
||||
|
||||
this._addResult(results, {
|
||||
@ -292,8 +295,8 @@ export class AutoImporter {
|
||||
source: importAliasData.importParts.importFrom,
|
||||
insertionText: autoImportTextEdits.insertionText,
|
||||
edits: autoImportTextEdits.edits,
|
||||
declPath: importAliasData.importParts.filePath,
|
||||
originalDeclPath: importAliasData.filePath,
|
||||
declUri: importAliasData.importParts.fileUri,
|
||||
originalDeclUri: importAliasData.fileUri,
|
||||
});
|
||||
});
|
||||
});
|
||||
@ -301,7 +304,7 @@ export class AutoImporter {
|
||||
|
||||
protected processModuleSymbolTable(
|
||||
topLevelSymbols: ModuleSymbolTable,
|
||||
moduleFilePath: string,
|
||||
moduleUri: Uri,
|
||||
word: string,
|
||||
similarityLimit: number,
|
||||
isStubOrHasInit: { isStub: boolean; hasInit: boolean },
|
||||
@ -312,7 +315,7 @@ export class AutoImporter {
|
||||
) {
|
||||
throwIfCancellationRequested(token);
|
||||
|
||||
const [importSource, importGroup, moduleNameAndType] = this._getImportPartsForSymbols(moduleFilePath);
|
||||
const [importSource, importGroup, moduleNameAndType] = this._getImportPartsForSymbols(moduleUri);
|
||||
if (!importSource) {
|
||||
return;
|
||||
}
|
||||
@ -345,7 +348,7 @@ export class AutoImporter {
|
||||
symbolName: name,
|
||||
importName: name,
|
||||
importFrom: importSource,
|
||||
filePath: moduleFilePath,
|
||||
fileUri: moduleUri,
|
||||
dotCount,
|
||||
moduleNameAndType,
|
||||
},
|
||||
@ -353,20 +356,20 @@ export class AutoImporter {
|
||||
symbol: autoImportSymbol.symbol,
|
||||
kind: autoImportSymbol.importAlias.kind,
|
||||
itemKind: autoImportSymbol.importAlias.itemKind,
|
||||
filePath: autoImportSymbol.importAlias.modulePath,
|
||||
fileUri: autoImportSymbol.importAlias.moduleUri,
|
||||
},
|
||||
importAliasMap
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
const nameForImportFrom = this.getNameForImportFrom(library, moduleFilePath);
|
||||
const nameForImportFrom = this.getNameForImportFrom(library, moduleUri);
|
||||
const autoImportTextEdits = this._getTextEditsForAutoImportByFilePath(
|
||||
{ name, alias: abbrFromUsers },
|
||||
{ name: importSource, nameForImportFrom },
|
||||
name,
|
||||
importGroup,
|
||||
moduleFilePath
|
||||
moduleUri
|
||||
);
|
||||
|
||||
this._addResult(results, {
|
||||
@ -377,8 +380,8 @@ export class AutoImporter {
|
||||
kind: autoImportSymbol.itemKind ?? convertSymbolKindToCompletionItemKind(autoImportSymbol.kind),
|
||||
insertionText: autoImportTextEdits.insertionText,
|
||||
edits: autoImportTextEdits.edits,
|
||||
declPath: moduleFilePath,
|
||||
originalDeclPath: moduleFilePath,
|
||||
declUri: moduleUri,
|
||||
originalDeclUri: moduleUri,
|
||||
});
|
||||
});
|
||||
|
||||
@ -389,7 +392,7 @@ export class AutoImporter {
|
||||
return;
|
||||
}
|
||||
|
||||
const importParts = this._getImportParts(moduleFilePath);
|
||||
const importParts = this._getImportParts(moduleUri);
|
||||
if (!importParts) {
|
||||
return;
|
||||
}
|
||||
@ -406,7 +409,7 @@ export class AutoImporter {
|
||||
|
||||
this._addToImportAliasMap(
|
||||
{
|
||||
modulePath: moduleFilePath,
|
||||
moduleUri,
|
||||
originalName: importParts.importName,
|
||||
kind: SymbolKind.Module,
|
||||
itemKind: CompletionItemKind.Module,
|
||||
@ -416,22 +419,22 @@ export class AutoImporter {
|
||||
importGroup,
|
||||
kind: SymbolKind.Module,
|
||||
itemKind: CompletionItemKind.Module,
|
||||
filePath: moduleFilePath,
|
||||
fileUri: moduleUri,
|
||||
},
|
||||
importAliasMap
|
||||
);
|
||||
}
|
||||
|
||||
protected getNameForImportFrom(library: boolean, moduleFilePath: string): string | undefined {
|
||||
protected getNameForImportFrom(library: boolean, moduleUri: Uri): string | undefined {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
protected isStubFileOrHasInit<T>(map: Map<string, T>, filePath: string) {
|
||||
const fileDir = getDirectoryPath(filePath);
|
||||
const initPathPy = combinePaths(fileDir, '__init__.py');
|
||||
const initPathPyi = initPathPy + 'i';
|
||||
const isStub = filePath.endsWith('.pyi');
|
||||
const hasInit = map.has(initPathPy) || map.has(initPathPyi);
|
||||
protected isStubFileOrHasInit<T>(map: Map<string, T>, uri: Uri) {
|
||||
const fileDir = uri.getDirectory();
|
||||
const initPathPy = fileDir.combinePaths('__init__.py');
|
||||
const initPathPyi = initPathPy.addPath('i');
|
||||
const isStub = uri.pathEndsWith('.pyi');
|
||||
const hasInit = map.has(initPathPy.key) || map.has(initPathPyi.key);
|
||||
return { isStub, hasInit };
|
||||
}
|
||||
|
||||
@ -462,14 +465,14 @@ export class AutoImporter {
|
||||
// Since we don't resolve alias declaration using type evaluator, there is still a chance
|
||||
// where we show multiple aliases for same symbols. but this should still reduce number of
|
||||
// such cases.
|
||||
if (!importAliasMap.has(alias.modulePath)) {
|
||||
if (!importAliasMap.has(alias.moduleUri.key)) {
|
||||
const map = new Map<string, ImportAliasData>();
|
||||
map.set(alias.originalName, data);
|
||||
importAliasMap.set(alias.modulePath, map);
|
||||
importAliasMap.set(alias.moduleUri.key, map);
|
||||
return;
|
||||
}
|
||||
|
||||
const map = importAliasMap.get(alias.modulePath)!;
|
||||
const map = importAliasMap.get(alias.moduleUri.key)!;
|
||||
if (!map.has(alias.originalName)) {
|
||||
map.set(alias.originalName, data);
|
||||
return;
|
||||
@ -508,8 +511,8 @@ export class AutoImporter {
|
||||
return StringUtils.getStringComparer()(left.importParts.importName, right.importParts.importName);
|
||||
}
|
||||
|
||||
private _getImportPartsForSymbols(filePath: string): [string | undefined, ImportGroup, ModuleNameAndType] {
|
||||
const localImport = this._importStatements.mapByFilePath.get(filePath);
|
||||
private _getImportPartsForSymbols(uri: Uri): [string | undefined, ImportGroup, ModuleNameAndType] {
|
||||
const localImport = this._importStatements.mapByFilePath.get(uri.key);
|
||||
if (localImport) {
|
||||
return [
|
||||
localImport.moduleName,
|
||||
@ -521,7 +524,7 @@ export class AutoImporter {
|
||||
},
|
||||
];
|
||||
} else {
|
||||
const moduleNameAndType = this._getModuleNameAndTypeFromFilePath(filePath);
|
||||
const moduleNameAndType = this._getModuleNameAndTypeFromFilePath(uri);
|
||||
return [
|
||||
moduleNameAndType.moduleName,
|
||||
getImportGroupFromModuleNameAndType(moduleNameAndType),
|
||||
@ -530,15 +533,15 @@ export class AutoImporter {
|
||||
}
|
||||
}
|
||||
|
||||
private _getImportParts(filePath: string) {
|
||||
const name = stripFileExtension(getFileName(filePath));
|
||||
private _getImportParts(uri: Uri) {
|
||||
const name = stripFileExtension(uri.fileName);
|
||||
|
||||
// See if we can import module as "import xxx"
|
||||
if (name === '__init__') {
|
||||
return createImportParts(this._getModuleNameAndTypeFromFilePath(getDirectoryPath(filePath)));
|
||||
return createImportParts(this._getModuleNameAndTypeFromFilePath(uri.getDirectory()));
|
||||
}
|
||||
|
||||
return createImportParts(this._getModuleNameAndTypeFromFilePath(filePath));
|
||||
return createImportParts(this._getModuleNameAndTypeFromFilePath(uri));
|
||||
|
||||
function createImportParts(module: ModuleNameAndType): ImportParts | undefined {
|
||||
const moduleName = module.moduleName;
|
||||
@ -553,7 +556,7 @@ export class AutoImporter {
|
||||
symbolName: importNamePart,
|
||||
importName: importNamePart ?? moduleName,
|
||||
importFrom,
|
||||
filePath,
|
||||
fileUri: uri,
|
||||
dotCount: StringUtils.getCharacterCount(moduleName, '.'),
|
||||
moduleNameAndType: module,
|
||||
};
|
||||
@ -601,8 +604,8 @@ export class AutoImporter {
|
||||
// Given the file path of a module that we want to import,
|
||||
// convert to a module name that can be used in an
|
||||
// 'import from' statement.
|
||||
private _getModuleNameAndTypeFromFilePath(filePath: string): ModuleNameAndType {
|
||||
return this.importResolver.getModuleNameForImport(filePath, this.execEnvironment);
|
||||
private _getModuleNameAndTypeFromFilePath(uri: Uri): ModuleNameAndType {
|
||||
return this.importResolver.getModuleNameForImport(uri, this.execEnvironment);
|
||||
}
|
||||
|
||||
private _getTextEditsForAutoImportByFilePath(
|
||||
@ -610,10 +613,10 @@ export class AutoImporter {
|
||||
moduleNameInfo: ModuleNameInfo,
|
||||
insertionText: string,
|
||||
importGroup: ImportGroup,
|
||||
filePath: string
|
||||
fileUri: Uri
|
||||
): { insertionText: string; edits?: TextEditAction[] | undefined } {
|
||||
// If there is no symbolName, there can't be existing import statement.
|
||||
const importStatement = this._importStatements.mapByFilePath.get(filePath);
|
||||
const importStatement = this._importStatements.mapByFilePath.get(fileUri.key);
|
||||
if (importStatement) {
|
||||
// Found import for given module. See whether we can use the module as it is.
|
||||
if (importStatement.node.nodeType === ParseNodeType.Import) {
|
||||
@ -699,7 +702,7 @@ export class AutoImporter {
|
||||
}
|
||||
|
||||
// Check whether it is one of implicit imports
|
||||
const importFrom = this._importStatements.implicitImports?.get(filePath);
|
||||
const importFrom = this._importStatements.implicitImports?.get(fileUri.key);
|
||||
if (importFrom) {
|
||||
// For now, we don't check whether alias or moduleName got overwritten at
|
||||
// given position
|
||||
|
@ -29,10 +29,10 @@ import { appendArray } from '../common/collectionUtils';
|
||||
import { isDefined } from '../common/core';
|
||||
import { ProgramView, ReferenceUseCase, SymbolUsageProvider } from '../common/extensibility';
|
||||
import { getSymbolKind } from '../common/lspUtils';
|
||||
import { convertPathToUri, getFileName } from '../common/pathUtils';
|
||||
import { convertOffsetsToRange } from '../common/positionUtils';
|
||||
import { ServiceKeys } from '../common/serviceProviderExtensions';
|
||||
import { Position, rangesAreEqual } from '../common/textRange';
|
||||
import { Uri } from '../common/uri/uri';
|
||||
import { ReferencesProvider, ReferencesResult } from '../languageService/referencesProvider';
|
||||
import { CallNode, MemberAccessNode, NameNode, ParseNode, ParseNodeType } from '../parser/parseNodes';
|
||||
import { ParseResults } from '../parser/parser';
|
||||
@ -44,11 +44,11 @@ export class CallHierarchyProvider {
|
||||
|
||||
constructor(
|
||||
private _program: ProgramView,
|
||||
private _filePath: string,
|
||||
private _fileUri: Uri,
|
||||
private _position: Position,
|
||||
private _token: CancellationToken
|
||||
) {
|
||||
this._parseResults = this._program.getParseResults(this._filePath);
|
||||
this._parseResults = this._program.getParseResults(this._fileUri);
|
||||
}
|
||||
|
||||
onPrepare(): CallHierarchyItem[] | null {
|
||||
@ -86,18 +86,15 @@ export class CallHierarchyProvider {
|
||||
const callItem: CallHierarchyItem = {
|
||||
name: symbolName,
|
||||
kind: getSymbolKind(targetDecl, this._evaluator, symbolName) ?? SymbolKind.Module,
|
||||
uri: callItemUri,
|
||||
uri: callItemUri.toString(),
|
||||
range: targetDecl.range,
|
||||
selectionRange: targetDecl.range,
|
||||
};
|
||||
|
||||
if (!canNavigateToFile(this._program.fileSystem, callItem.uri)) {
|
||||
if (!canNavigateToFile(this._program.fileSystem, Uri.parse(callItem.uri, this._fileUri.isCaseSensitive))) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// Convert the file path in the item to proper URI.
|
||||
callItem.uri = convertPathToUri(this._program.fileSystem, callItem.uri);
|
||||
|
||||
return [callItem];
|
||||
}
|
||||
|
||||
@ -117,11 +114,11 @@ export class CallHierarchyProvider {
|
||||
const items: CallHierarchyIncomingCall[] = [];
|
||||
const sourceFiles =
|
||||
targetDecl.type === DeclarationType.Alias
|
||||
? [this._program.getSourceFileInfo(this._filePath)!]
|
||||
? [this._program.getSourceFileInfo(this._fileUri)!]
|
||||
: this._program.getSourceFileInfoList();
|
||||
for (const curSourceFileInfo of sourceFiles) {
|
||||
if (isUserCode(curSourceFileInfo) || curSourceFileInfo.isOpenByClient) {
|
||||
const filePath = curSourceFileInfo.sourceFile.getFilePath();
|
||||
const filePath = curSourceFileInfo.sourceFile.getUri();
|
||||
const itemsToAdd = this._getIncomingCallsForDeclaration(filePath, symbolName, targetDecl);
|
||||
|
||||
if (itemsToAdd) {
|
||||
@ -138,14 +135,9 @@ export class CallHierarchyProvider {
|
||||
return null;
|
||||
}
|
||||
|
||||
const callItems = items.filter((item) => canNavigateToFile(this._program.fileSystem, item.from.uri));
|
||||
|
||||
// Convert the file paths in the items to proper URIs.
|
||||
callItems.forEach((item) => {
|
||||
item.from.uri = convertPathToUri(this._program.fileSystem, item.from.uri);
|
||||
});
|
||||
|
||||
return callItems;
|
||||
return items.filter((item) =>
|
||||
canNavigateToFile(this._program.fileSystem, Uri.parse(item.from.uri, this._fileUri.isCaseSensitive))
|
||||
);
|
||||
}
|
||||
|
||||
getOutgoingCalls(): CallHierarchyOutgoingCall[] | null {
|
||||
@ -209,14 +201,9 @@ export class CallHierarchyProvider {
|
||||
return null;
|
||||
}
|
||||
|
||||
const callItems = outgoingCalls.filter((item) => canNavigateToFile(this._program.fileSystem, item.to.uri));
|
||||
|
||||
// Convert the file paths in the items to proper URIs.
|
||||
callItems.forEach((item) => {
|
||||
item.to.uri = convertPathToUri(this._program.fileSystem, item.to.uri);
|
||||
});
|
||||
|
||||
return callItems;
|
||||
return outgoingCalls.filter((item) =>
|
||||
canNavigateToFile(this._program.fileSystem, Uri.parse(item.to.uri, this._fileUri.isCaseSensitive))
|
||||
);
|
||||
}
|
||||
|
||||
private get _evaluator(): TypeEvaluator {
|
||||
@ -225,7 +212,7 @@ export class CallHierarchyProvider {
|
||||
|
||||
private _getTargetDeclaration(referencesResult: ReferencesResult): {
|
||||
targetDecl: Declaration;
|
||||
callItemUri: string;
|
||||
callItemUri: Uri;
|
||||
symbolName: string;
|
||||
} {
|
||||
// If there's more than one declaration, pick the target one.
|
||||
@ -253,32 +240,26 @@ export class CallHierarchyProvider {
|
||||
// Although the LSP specification requires a URI, we are using a file path
|
||||
// here because it is converted to the proper URI by the caller.
|
||||
// This simplifies our code and ensures compatibility with the LSP specification.
|
||||
let callItemUri;
|
||||
let callItemUri: Uri;
|
||||
if (targetDecl.type === DeclarationType.Alias) {
|
||||
symbolName = (referencesResult.nodeAtOffset as NameNode).value;
|
||||
callItemUri = this._filePath;
|
||||
callItemUri = this._fileUri;
|
||||
} else {
|
||||
symbolName = DeclarationUtils.getNameFromDeclaration(targetDecl) || referencesResult.symbolNames[0];
|
||||
callItemUri = targetDecl.path;
|
||||
callItemUri = targetDecl.uri;
|
||||
}
|
||||
|
||||
return { targetDecl, callItemUri, symbolName };
|
||||
}
|
||||
|
||||
private _getIncomingCallsForDeclaration(
|
||||
filePath: string,
|
||||
fileUri: Uri,
|
||||
symbolName: string,
|
||||
declaration: Declaration
|
||||
): CallHierarchyIncomingCall[] | undefined {
|
||||
throwIfCancellationRequested(this._token);
|
||||
|
||||
const callFinder = new FindIncomingCallTreeWalker(
|
||||
this._program,
|
||||
filePath,
|
||||
symbolName,
|
||||
declaration,
|
||||
this._token
|
||||
);
|
||||
const callFinder = new FindIncomingCallTreeWalker(this._program, fileUri, symbolName, declaration, this._token);
|
||||
|
||||
const incomingCalls = callFinder.findCalls();
|
||||
return incomingCalls.length > 0 ? incomingCalls : undefined;
|
||||
@ -287,7 +268,7 @@ export class CallHierarchyProvider {
|
||||
private _getDeclaration(): ReferencesResult | undefined {
|
||||
return ReferencesProvider.getDeclarationForPosition(
|
||||
this._program,
|
||||
this._filePath,
|
||||
this._fileUri,
|
||||
this._position,
|
||||
/* reporter */ undefined,
|
||||
ReferenceUseCase.References,
|
||||
@ -394,7 +375,7 @@ class FindOutgoingCallTreeWalker extends ParseTreeWalker {
|
||||
const callDest: CallHierarchyItem = {
|
||||
name: nameNode.value,
|
||||
kind: getSymbolKind(resolvedDecl, this._evaluator, nameNode.value) ?? SymbolKind.Module,
|
||||
uri: resolvedDecl.path,
|
||||
uri: resolvedDecl.uri.toString(),
|
||||
range: resolvedDecl.range,
|
||||
selectionRange: resolvedDecl.range,
|
||||
};
|
||||
@ -437,14 +418,14 @@ class FindIncomingCallTreeWalker extends ParseTreeWalker {
|
||||
|
||||
constructor(
|
||||
private readonly _program: ProgramView,
|
||||
private readonly _filePath: string,
|
||||
private readonly _fileUri: Uri,
|
||||
private readonly _symbolName: string,
|
||||
private readonly _targetDeclaration: Declaration,
|
||||
private readonly _cancellationToken: CancellationToken
|
||||
) {
|
||||
super();
|
||||
|
||||
this._parseResults = this._program.getParseResults(this._filePath)!;
|
||||
this._parseResults = this._program.getParseResults(this._fileUri)!;
|
||||
this._usageProviders = (this._program.serviceProvider.tryGet(ServiceKeys.symbolUsageProviderFactory) ?? [])
|
||||
.map((f) =>
|
||||
f.tryCreateProvider(ReferenceUseCase.References, [this._targetDeclaration], this._cancellationToken)
|
||||
@ -570,12 +551,12 @@ class FindIncomingCallTreeWalker extends ParseTreeWalker {
|
||||
let callSource: CallHierarchyItem;
|
||||
if (executionNode.nodeType === ParseNodeType.Module) {
|
||||
const moduleRange = convertOffsetsToRange(0, 0, this._parseResults.tokenizerOutput.lines);
|
||||
const fileName = getFileName(this._filePath);
|
||||
const fileName = this._fileUri.fileName;
|
||||
|
||||
callSource = {
|
||||
name: `(module) ${fileName}`,
|
||||
kind: SymbolKind.Module,
|
||||
uri: this._filePath,
|
||||
uri: this._fileUri.toString(),
|
||||
range: moduleRange,
|
||||
selectionRange: moduleRange,
|
||||
};
|
||||
@ -589,7 +570,7 @@ class FindIncomingCallTreeWalker extends ParseTreeWalker {
|
||||
callSource = {
|
||||
name: '(lambda)',
|
||||
kind: SymbolKind.Function,
|
||||
uri: this._filePath,
|
||||
uri: this._fileUri.toString(),
|
||||
range: lambdaRange,
|
||||
selectionRange: lambdaRange,
|
||||
};
|
||||
@ -603,7 +584,7 @@ class FindIncomingCallTreeWalker extends ParseTreeWalker {
|
||||
callSource = {
|
||||
name: executionNode.name.value,
|
||||
kind: SymbolKind.Function,
|
||||
uri: this._filePath,
|
||||
uri: this._fileUri.toString(),
|
||||
range: functionRange,
|
||||
selectionRange: functionRange,
|
||||
};
|
||||
|
@ -12,8 +12,8 @@ import { Commands } from '../commands/commands';
|
||||
import { throwIfCancellationRequested } from '../common/cancellationUtils';
|
||||
import { ActionKind, CreateTypeStubFileAction, RenameShadowedFileAction } from '../common/diagnostic';
|
||||
import { FileEditActions } from '../common/editAction';
|
||||
import { getShortenedFileName } from '../common/pathUtils';
|
||||
import { Range } from '../common/textRange';
|
||||
import { Uri } from '../common/uri/uri';
|
||||
import { convertToWorkspaceEdit } from '../common/workspaceEditUtils';
|
||||
import { Localizer } from '../localization/localize';
|
||||
import { Workspace } from '../workspaceFactory';
|
||||
@ -29,7 +29,7 @@ export class CodeActionProvider {
|
||||
}
|
||||
static async getCodeActionsForPosition(
|
||||
workspace: Workspace,
|
||||
filePath: string,
|
||||
fileUri: Uri,
|
||||
range: Range,
|
||||
kinds: CodeActionKind[] | undefined,
|
||||
token: CancellationToken
|
||||
@ -44,7 +44,7 @@ export class CodeActionProvider {
|
||||
}
|
||||
|
||||
if (!workspace.disableLanguageServices) {
|
||||
const diags = await workspace.service.getDiagnosticsForRange(filePath, range, token);
|
||||
const diags = await workspace.service.getDiagnosticsForRange(fileUri, range, token);
|
||||
const typeStubDiag = diags.find((d) => {
|
||||
const actions = d.getActions();
|
||||
return actions && actions.find((a) => a.action === Commands.createTypeStub);
|
||||
@ -60,9 +60,9 @@ export class CodeActionProvider {
|
||||
Command.create(
|
||||
Localizer.CodeAction.createTypeStub(),
|
||||
Commands.createTypeStub,
|
||||
workspace.rootPath,
|
||||
workspace.rootUri.toString(),
|
||||
action.moduleName,
|
||||
filePath
|
||||
fileUri.toString()
|
||||
),
|
||||
CodeActionKind.QuickFix
|
||||
);
|
||||
@ -80,21 +80,20 @@ export class CodeActionProvider {
|
||||
.find((a) => a.action === ActionKind.RenameShadowedFileAction) as RenameShadowedFileAction;
|
||||
if (action) {
|
||||
const title = Localizer.CodeAction.renameShadowedFile().format({
|
||||
oldFile: getShortenedFileName(action.oldFile),
|
||||
newFile: getShortenedFileName(action.newFile),
|
||||
oldFile: action.oldUri.getShortenedFileName(),
|
||||
newFile: action.newUri.getShortenedFileName(),
|
||||
});
|
||||
const fs = workspace.service.getImportResolver().fileSystem;
|
||||
const editActions: FileEditActions = {
|
||||
edits: [],
|
||||
fileOperations: [
|
||||
{
|
||||
kind: 'rename',
|
||||
oldFilePath: action.oldFile,
|
||||
newFilePath: action.newFile,
|
||||
oldFileUri: action.oldUri,
|
||||
newFileUri: action.newUri,
|
||||
},
|
||||
],
|
||||
};
|
||||
const workspaceEdit = convertToWorkspaceEdit(fs, editActions);
|
||||
const workspaceEdit = convertToWorkspaceEdit(editActions);
|
||||
const renameAction = CodeAction.create(title, workspaceEdit, CodeActionKind.QuickFix);
|
||||
codeActions.push(renameAction);
|
||||
}
|
||||
|
@ -41,7 +41,7 @@ import { Symbol, SymbolTable } from '../analyzer/symbol';
|
||||
import * as SymbolNameUtils from '../analyzer/symbolNameUtils';
|
||||
import { getLastTypedDeclaredForSymbol, isVisibleExternally } from '../analyzer/symbolUtils';
|
||||
import { getTypedDictMembersForClass } from '../analyzer/typedDicts';
|
||||
import { getModuleDocStringFromPaths } from '../analyzer/typeDocStringUtils';
|
||||
import { getModuleDocStringFromUris } from '../analyzer/typeDocStringUtils';
|
||||
import { CallSignatureInfo, TypeEvaluator } from '../analyzer/typeEvaluatorTypes';
|
||||
import { printLiteralValue } from '../analyzer/typePrinter';
|
||||
import {
|
||||
@ -83,6 +83,7 @@ import { PythonVersion } from '../common/pythonVersion';
|
||||
import * as StringUtils from '../common/stringUtils';
|
||||
import { comparePositions, Position, TextRange } from '../common/textRange';
|
||||
import { TextRangeCollection } from '../common/textRangeCollection';
|
||||
import { Uri } from '../common/uri/uri';
|
||||
import { convertToTextEdits } from '../common/workspaceEditUtils';
|
||||
import { Localizer } from '../localization/localize';
|
||||
import {
|
||||
@ -231,13 +232,13 @@ enum SortCategory {
|
||||
// This data allows the resolve handling to disambiguate
|
||||
// which item was selected.
|
||||
export interface CompletionItemData {
|
||||
filePath: string;
|
||||
workspacePath: string;
|
||||
uri: string; // Have to be strings because this data is passed across the LSP boundary.
|
||||
workspaceUri: string;
|
||||
position: Position;
|
||||
autoImportText?: string;
|
||||
symbolLabel?: string;
|
||||
funcParensDisabled?: boolean;
|
||||
modulePath?: string;
|
||||
moduleUri?: string;
|
||||
}
|
||||
|
||||
export interface CompletionOptions {
|
||||
@ -287,20 +288,20 @@ export class CompletionProvider {
|
||||
|
||||
constructor(
|
||||
protected readonly program: ProgramView,
|
||||
private readonly _workspacePath: string,
|
||||
protected readonly filePath: string,
|
||||
private readonly _workspaceRootUri: Uri,
|
||||
protected readonly fileUri: Uri,
|
||||
protected readonly position: Position,
|
||||
protected readonly options: CompletionOptions,
|
||||
protected readonly cancellationToken: CancellationToken
|
||||
) {
|
||||
this.execEnv = this.configOptions.findExecEnvironment(this.filePath);
|
||||
this.execEnv = this.configOptions.findExecEnvironment(this.fileUri);
|
||||
|
||||
this.parseResults = this.program.getParseResults(this.filePath)!;
|
||||
this.sourceMapper = this.program.getSourceMapper(this.filePath, this.cancellationToken, /* mapCompiled */ true);
|
||||
this.parseResults = this.program.getParseResults(this.fileUri)!;
|
||||
this.sourceMapper = this.program.getSourceMapper(this.fileUri, this.cancellationToken, /* mapCompiled */ true);
|
||||
}
|
||||
|
||||
getCompletions(): CompletionList | null {
|
||||
if (!this.program.getSourceFileInfo(this.filePath)) {
|
||||
if (!this.program.getSourceFileInfo(this.fileUri)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@ -347,10 +348,15 @@ export class CompletionProvider {
|
||||
}
|
||||
|
||||
if (
|
||||
completionItemData.modulePath &&
|
||||
ImportResolver.isSupportedImportSourceFile(completionItemData.modulePath)
|
||||
completionItemData.moduleUri &&
|
||||
ImportResolver.isSupportedImportSourceFile(
|
||||
Uri.parse(completionItemData.moduleUri, this.importResolver.fileSystem.isCaseSensitive)
|
||||
)
|
||||
) {
|
||||
const documentation = getModuleDocStringFromPaths([completionItemData.modulePath], this.sourceMapper);
|
||||
const documentation = getModuleDocStringFromUris(
|
||||
[Uri.parse(completionItemData.moduleUri, this.importResolver.fileSystem.isCaseSensitive)],
|
||||
this.sourceMapper
|
||||
);
|
||||
if (!documentation) {
|
||||
return;
|
||||
}
|
||||
@ -499,7 +505,7 @@ export class CompletionProvider {
|
||||
const methodSignature = this._printMethodSignature(classResults.classType, decl);
|
||||
|
||||
let text: string;
|
||||
if (isStubFile(this.filePath)) {
|
||||
if (isStubFile(this.fileUri)) {
|
||||
text = `${methodSignature}: ...`;
|
||||
} else {
|
||||
const methodBody = this.printOverriddenMethodBody(
|
||||
@ -826,7 +832,7 @@ export class CompletionProvider {
|
||||
return;
|
||||
}
|
||||
|
||||
const currentFile = this.program.getSourceFileInfo(this.filePath);
|
||||
const currentFile = this.program.getSourceFileInfo(this.fileUri);
|
||||
const moduleSymbolMap = buildModuleSymbolsMap(
|
||||
this.program.getSourceFileInfoList().filter((s) => s !== currentFile)
|
||||
);
|
||||
@ -923,8 +929,8 @@ export class CompletionProvider {
|
||||
}
|
||||
|
||||
const completionItemData: CompletionItemData = {
|
||||
workspacePath: this._workspacePath,
|
||||
filePath: this.filePath,
|
||||
workspaceUri: this._workspaceRootUri.toString(),
|
||||
uri: this.fileUri.toString(),
|
||||
position: this.position,
|
||||
};
|
||||
|
||||
@ -932,8 +938,8 @@ export class CompletionProvider {
|
||||
completionItemData.funcParensDisabled = true;
|
||||
}
|
||||
|
||||
if (detail?.modulePath) {
|
||||
completionItemData.modulePath = detail.modulePath;
|
||||
if (detail?.moduleUri) {
|
||||
completionItemData.moduleUri = detail.moduleUri.toString();
|
||||
}
|
||||
|
||||
completionItem.data = toLSPAny(completionItemData);
|
||||
@ -1689,7 +1695,7 @@ export class CompletionProvider {
|
||||
return;
|
||||
}
|
||||
|
||||
const printFlags = isStubFile(this.filePath)
|
||||
const printFlags = isStubFile(this.fileUri)
|
||||
? ParseTreeUtils.PrintExpressionFlags.ForwardDeclarations |
|
||||
ParseTreeUtils.PrintExpressionFlags.DoNotLimitStringLength
|
||||
: ParseTreeUtils.PrintExpressionFlags.DoNotLimitStringLength;
|
||||
@ -1822,7 +1828,7 @@ export class CompletionProvider {
|
||||
const node = decl.node;
|
||||
|
||||
let ellipsisForDefault: boolean | undefined;
|
||||
if (isStubFile(this.filePath)) {
|
||||
if (isStubFile(this.fileUri)) {
|
||||
// In stubs, always use "...".
|
||||
ellipsisForDefault = true;
|
||||
} else if (classType.details.moduleName === decl.moduleName) {
|
||||
@ -1830,7 +1836,7 @@ export class CompletionProvider {
|
||||
ellipsisForDefault = false;
|
||||
}
|
||||
|
||||
const printFlags = isStubFile(this.filePath)
|
||||
const printFlags = isStubFile(this.fileUri)
|
||||
? ParseTreeUtils.PrintExpressionFlags.ForwardDeclarations |
|
||||
ParseTreeUtils.PrintExpressionFlags.DoNotLimitStringLength
|
||||
: ParseTreeUtils.PrintExpressionFlags.DoNotLimitStringLength;
|
||||
@ -2190,7 +2196,7 @@ export class CompletionProvider {
|
||||
return [];
|
||||
}
|
||||
|
||||
if (declaration.path !== this.filePath) {
|
||||
if (!declaration.uri.equals(this.fileUri)) {
|
||||
return [];
|
||||
}
|
||||
|
||||
@ -2200,8 +2206,8 @@ export class CompletionProvider {
|
||||
|
||||
// Find the lowest tree to search the symbol.
|
||||
if (
|
||||
ParseTreeUtils.getFileInfoFromNode(startingNode)?.filePath ===
|
||||
ParseTreeUtils.getFileInfoFromNode(scopeRoot)?.filePath
|
||||
ParseTreeUtils.getFileInfoFromNode(startingNode)?.fileUri ===
|
||||
ParseTreeUtils.getFileInfoFromNode(scopeRoot)?.fileUri
|
||||
) {
|
||||
startingNode = scopeRoot;
|
||||
}
|
||||
@ -2731,7 +2737,9 @@ export class CompletionProvider {
|
||||
|
||||
const completionMap = new CompletionMap();
|
||||
const resolvedPath =
|
||||
importInfo.resolvedPaths.length > 0 ? importInfo.resolvedPaths[importInfo.resolvedPaths.length - 1] : '';
|
||||
importInfo.resolvedUris.length > 0
|
||||
? importInfo.resolvedUris[importInfo.resolvedUris.length - 1]
|
||||
: Uri.empty();
|
||||
|
||||
const parseResults = this.program.getParseResults(resolvedPath);
|
||||
if (!parseResults) {
|
||||
@ -2775,7 +2783,7 @@ export class CompletionProvider {
|
||||
importInfo.implicitImports.forEach((implImport) => {
|
||||
if (!importFromNode.imports.find((imp) => imp.name.value === implImport.name)) {
|
||||
this.addNameToCompletions(implImport.name, CompletionItemKind.Module, priorWord, completionMap, {
|
||||
modulePath: implImport.path,
|
||||
moduleUri: implImport.uri,
|
||||
});
|
||||
}
|
||||
});
|
||||
@ -2817,8 +2825,8 @@ export class CompletionProvider {
|
||||
completionItem.kind = CompletionItemKind.Variable;
|
||||
|
||||
const completionItemData: CompletionItemData = {
|
||||
workspacePath: this._workspacePath,
|
||||
filePath: this.filePath,
|
||||
workspaceUri: this._workspaceRootUri.toString(),
|
||||
uri: this.fileUri.toString(),
|
||||
position: this.position,
|
||||
};
|
||||
completionItem.data = toLSPAny(completionItemData);
|
||||
@ -2914,8 +2922,7 @@ export class CompletionProvider {
|
||||
// exported from this scope, don't include it in the
|
||||
// suggestion list unless we are in the same file.
|
||||
const hidden =
|
||||
!isVisibleExternally(symbol) &&
|
||||
!symbol.getDeclarations().some((d) => isDefinedInFile(d, this.filePath));
|
||||
!isVisibleExternally(symbol) && !symbol.getDeclarations().some((d) => isDefinedInFile(d, this.fileUri));
|
||||
if (!hidden && includeSymbolCallback(symbol, name)) {
|
||||
// Don't add a symbol more than once. It may have already been
|
||||
// added from an inner scope's symbol table.
|
||||
@ -3098,7 +3105,7 @@ export class CompletionProvider {
|
||||
importedSymbols: new Set<string>(),
|
||||
};
|
||||
|
||||
const completions = this.importResolver.getCompletionSuggestions(this.filePath, this.execEnv, moduleDescriptor);
|
||||
const completions = this.importResolver.getCompletionSuggestions(this.fileUri, this.execEnv, moduleDescriptor);
|
||||
|
||||
const completionMap = new CompletionMap();
|
||||
|
||||
@ -3120,7 +3127,7 @@ export class CompletionProvider {
|
||||
completions.forEach((modulePath, completionName) => {
|
||||
this.addNameToCompletions(completionName, CompletionItemKind.Module, '', completionMap, {
|
||||
sortText: this._makeSortText(SortCategory.ImportModuleName, completionName),
|
||||
modulePath,
|
||||
moduleUri: modulePath,
|
||||
});
|
||||
});
|
||||
|
||||
|
@ -11,21 +11,22 @@ import { InsertTextFormat, MarkupContent, MarkupKind, TextEdit } from 'vscode-la
|
||||
import { Declaration, DeclarationType } from '../analyzer/declaration';
|
||||
import { convertDocStringToMarkdown, convertDocStringToPlainText } from '../analyzer/docStringConversion';
|
||||
import { TypeEvaluator } from '../analyzer/typeEvaluatorTypes';
|
||||
import { isProperty } from '../analyzer/typeUtils';
|
||||
import {
|
||||
ClassType,
|
||||
Type,
|
||||
TypeBase,
|
||||
TypeCategory,
|
||||
UnknownType,
|
||||
getTypeAliasInfo,
|
||||
isClassInstance,
|
||||
isFunction,
|
||||
isModule,
|
||||
isOverloadedFunction,
|
||||
Type,
|
||||
TypeBase,
|
||||
TypeCategory,
|
||||
UnknownType,
|
||||
} from '../analyzer/types';
|
||||
import { isProperty } from '../analyzer/typeUtils';
|
||||
import { SignatureDisplayType } from '../common/configOptions';
|
||||
import { TextEditAction } from '../common/editAction';
|
||||
import { Uri } from '../common/uri/uri';
|
||||
import { getToolTipForType } from './tooltipUtils';
|
||||
|
||||
export interface Edits {
|
||||
@ -55,7 +56,7 @@ export interface CompletionDetail extends CommonDetail {
|
||||
};
|
||||
sortText?: string;
|
||||
itemDetail?: string;
|
||||
modulePath?: string;
|
||||
moduleUri?: Uri;
|
||||
}
|
||||
|
||||
export function getTypeDetail(
|
||||
|
@ -24,10 +24,11 @@ import { appendArray } from '../common/collectionUtils';
|
||||
import { isDefined } from '../common/core';
|
||||
import { ProgramView, ServiceProvider } from '../common/extensibility';
|
||||
import { convertPositionToOffset } from '../common/positionUtils';
|
||||
import { ServiceKeys } from '../common/serviceProviderExtensions';
|
||||
import { DocumentRange, Position, rangesAreEqual } from '../common/textRange';
|
||||
import { Uri } from '../common/uri/uri';
|
||||
import { ParseNode, ParseNodeType } from '../parser/parseNodes';
|
||||
import { ParseResults } from '../parser/parser';
|
||||
import { ServiceKeys } from '../common/serviceProviderExtensions';
|
||||
|
||||
export enum DefinitionFilter {
|
||||
All = 'all',
|
||||
@ -50,7 +51,7 @@ export function addDeclarationsToDefinitions(
|
||||
allowExternallyHiddenAccess: true,
|
||||
});
|
||||
|
||||
if (!resolvedDecl || !resolvedDecl.path) {
|
||||
if (!resolvedDecl || resolvedDecl.uri.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
@ -66,13 +67,13 @@ export function addDeclarationsToDefinitions(
|
||||
resolvedDecl.type === DeclarationType.Alias &&
|
||||
resolvedDecl.symbolName &&
|
||||
resolvedDecl.submoduleFallback &&
|
||||
resolvedDecl.submoduleFallback.path
|
||||
!resolvedDecl.submoduleFallback.uri.isEmpty()
|
||||
) {
|
||||
resolvedDecl = resolvedDecl.submoduleFallback;
|
||||
}
|
||||
|
||||
_addIfUnique(definitions, {
|
||||
path: resolvedDecl.path,
|
||||
uri: resolvedDecl.uri,
|
||||
range: resolvedDecl.range,
|
||||
});
|
||||
|
||||
@ -82,22 +83,22 @@ export function addDeclarationsToDefinitions(
|
||||
if (functionType && isOverloadedFunction(functionType)) {
|
||||
for (const overloadDecl of functionType.overloads.map((o) => o.details.declaration).filter(isDefined)) {
|
||||
_addIfUnique(definitions, {
|
||||
path: overloadDecl.path,
|
||||
uri: overloadDecl.uri,
|
||||
range: overloadDecl.range,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!isStubFile(resolvedDecl.path)) {
|
||||
if (!isStubFile(resolvedDecl.uri)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (resolvedDecl.type === DeclarationType.Alias) {
|
||||
// Add matching source module
|
||||
sourceMapper
|
||||
.findModules(resolvedDecl.path)
|
||||
.map((m) => getFileInfo(m)?.filePath)
|
||||
.findModules(resolvedDecl.uri)
|
||||
.map((m) => getFileInfo(m)?.fileUri)
|
||||
.filter(isDefined)
|
||||
.forEach((f) => _addIfUnique(definitions, _createModuleEntry(f)));
|
||||
return;
|
||||
@ -105,9 +106,9 @@ export function addDeclarationsToDefinitions(
|
||||
|
||||
const implDecls = sourceMapper.findDeclarations(resolvedDecl);
|
||||
for (const implDecl of implDecls) {
|
||||
if (implDecl && implDecl.path) {
|
||||
if (implDecl && !implDecl.uri.isEmpty()) {
|
||||
_addIfUnique(definitions, {
|
||||
path: implDecl.path,
|
||||
uri: implDecl.uri,
|
||||
range: implDecl.range,
|
||||
});
|
||||
}
|
||||
@ -123,7 +124,7 @@ export function filterDefinitions(filter: DefinitionFilter, definitions: Documen
|
||||
// If go-to-declaration is supported, attempt to only show only pyi files in go-to-declaration
|
||||
// and none in go-to-definition, unless filtering would produce an empty list.
|
||||
const preferStubs = filter === DefinitionFilter.PreferStubs;
|
||||
const wantedFile = (v: DocumentRange) => preferStubs === isStubFile(v.path);
|
||||
const wantedFile = (v: DocumentRange) => preferStubs === isStubFile(v.uri);
|
||||
if (definitions.find(wantedFile)) {
|
||||
return definitions.filter(wantedFile);
|
||||
}
|
||||
@ -181,13 +182,13 @@ class DefinitionProviderBase {
|
||||
export class DefinitionProvider extends DefinitionProviderBase {
|
||||
constructor(
|
||||
program: ProgramView,
|
||||
filePath: string,
|
||||
fileUri: Uri,
|
||||
position: Position,
|
||||
filter: DefinitionFilter,
|
||||
token: CancellationToken
|
||||
) {
|
||||
const sourceMapper = program.getSourceMapper(filePath, token);
|
||||
const parseResults = program.getParseResults(filePath);
|
||||
const sourceMapper = program.getSourceMapper(fileUri, token);
|
||||
const parseResults = program.getParseResults(fileUri);
|
||||
const { node, offset } = _tryGetNode(parseResults, position);
|
||||
|
||||
super(sourceMapper, program.evaluator!, program.serviceProvider, node, offset, filter, token);
|
||||
@ -222,15 +223,15 @@ export class DefinitionProvider extends DefinitionProviderBase {
|
||||
}
|
||||
|
||||
export class TypeDefinitionProvider extends DefinitionProviderBase {
|
||||
private readonly _filePath: string;
|
||||
private readonly _fileUri: Uri;
|
||||
|
||||
constructor(program: ProgramView, filePath: string, position: Position, token: CancellationToken) {
|
||||
const sourceMapper = program.getSourceMapper(filePath, token, /*mapCompiled*/ false, /*preferStubs*/ true);
|
||||
const parseResults = program.getParseResults(filePath);
|
||||
constructor(program: ProgramView, fileUri: Uri, position: Position, token: CancellationToken) {
|
||||
const sourceMapper = program.getSourceMapper(fileUri, token, /*mapCompiled*/ false, /*preferStubs*/ true);
|
||||
const parseResults = program.getParseResults(fileUri);
|
||||
const { node, offset } = _tryGetNode(parseResults, position);
|
||||
|
||||
super(sourceMapper, program.evaluator!, program.serviceProvider, node, offset, DefinitionFilter.All, token);
|
||||
this._filePath = filePath;
|
||||
this._fileUri = fileUri;
|
||||
}
|
||||
|
||||
getDefinitions(): DocumentRange[] | undefined {
|
||||
@ -251,7 +252,7 @@ export class TypeDefinitionProvider extends DefinitionProviderBase {
|
||||
if (subtype?.category === TypeCategory.Class) {
|
||||
appendArray(
|
||||
declarations,
|
||||
this.sourceMapper.findClassDeclarationsByType(this._filePath, subtype)
|
||||
this.sourceMapper.findClassDeclarationsByType(this._fileUri, subtype)
|
||||
);
|
||||
}
|
||||
});
|
||||
@ -290,9 +291,9 @@ function _tryGetNode(parseResults: ParseResults | undefined, position: Position)
|
||||
return { node: ParseTreeUtils.findNodeByOffset(parseResults.parseTree, offset), offset };
|
||||
}
|
||||
|
||||
function _createModuleEntry(filePath: string): DocumentRange {
|
||||
function _createModuleEntry(uri: Uri): DocumentRange {
|
||||
return {
|
||||
path: filePath,
|
||||
uri,
|
||||
range: {
|
||||
start: { line: 0, character: 0 },
|
||||
end: { line: 0, character: 0 },
|
||||
@ -302,7 +303,7 @@ function _createModuleEntry(filePath: string): DocumentRange {
|
||||
|
||||
function _addIfUnique(definitions: DocumentRange[], itemToAdd: DocumentRange) {
|
||||
for (const def of definitions) {
|
||||
if (def.path === itemToAdd.path && rangesAreEqual(def.range, itemToAdd.range)) {
|
||||
if (def.uri.equals(itemToAdd.uri) && rangesAreEqual(def.range, itemToAdd.range)) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
@ -15,6 +15,7 @@ import { throwIfCancellationRequested } from '../common/cancellationUtils';
|
||||
import { ProgramView, ReferenceUseCase } from '../common/extensibility';
|
||||
import { convertOffsetsToRange, convertPositionToOffset } from '../common/positionUtils';
|
||||
import { Position, TextRange } from '../common/textRange';
|
||||
import { Uri } from '../common/uri/uri';
|
||||
import { ParseNodeType } from '../parser/parseNodes';
|
||||
import { ParseResults } from '../parser/parser';
|
||||
import { DocumentSymbolCollector } from './documentSymbolCollector';
|
||||
@ -24,11 +25,11 @@ export class DocumentHighlightProvider {
|
||||
|
||||
constructor(
|
||||
private _program: ProgramView,
|
||||
private _filePath: string,
|
||||
private _fileUri: Uri,
|
||||
private _position: Position,
|
||||
private _token: CancellationToken
|
||||
) {
|
||||
this._parseResults = this._program.getParseResults(this._filePath);
|
||||
this._parseResults = this._program.getParseResults(this._fileUri);
|
||||
}
|
||||
|
||||
getDocumentHighlight(): DocumentHighlight[] | undefined {
|
||||
|
@ -29,12 +29,12 @@ import { TypeEvaluator } from '../analyzer/typeEvaluatorTypes';
|
||||
import { TypeCategory } from '../analyzer/types';
|
||||
import { throwIfCancellationRequested } from '../common/cancellationUtils';
|
||||
import { appendArray } from '../common/collectionUtils';
|
||||
import { isDefined } from '../common/core';
|
||||
import { assert } from '../common/debug';
|
||||
import { ProgramView, ReferenceUseCase, SymbolUsageProvider } from '../common/extensibility';
|
||||
import { ServiceKeys } from '../common/serviceProviderExtensions';
|
||||
import { TextRange } from '../common/textRange';
|
||||
import { ImportAsNode, NameNode, ParseNode, ParseNodeType, StringListNode, StringNode } from '../parser/parseNodes';
|
||||
import { ServiceKeys } from '../common/serviceProviderExtensions';
|
||||
import { isDefined } from '../common/core';
|
||||
|
||||
export type CollectionResult = {
|
||||
node: NameNode | StringNode;
|
||||
@ -184,17 +184,18 @@ export class DocumentSymbolCollector extends ParseTreeWalker {
|
||||
|
||||
const declarations = getDeclarationsForNameNode(evaluator, node, /* skipUnreachableCode */ false);
|
||||
const fileInfo = AnalyzerNodeInfo.getFileInfo(node);
|
||||
const fileUri = fileInfo.fileUri;
|
||||
|
||||
const resolvedDeclarations: Declaration[] = [];
|
||||
const sourceMapper = program.getSourceMapper(fileInfo.filePath, token);
|
||||
const sourceMapper = program.getSourceMapper(fileUri, token);
|
||||
declarations.forEach((decl) => {
|
||||
const resolvedDecl = evaluator.resolveAliasDeclaration(decl, resolveLocalName);
|
||||
if (resolvedDecl) {
|
||||
addDeclarationIfUnique(resolvedDeclarations, resolvedDecl);
|
||||
if (sourceMapper && isStubFile(resolvedDecl.path)) {
|
||||
if (sourceMapper && isStubFile(resolvedDecl.uri)) {
|
||||
const implDecls = sourceMapper.findDeclarations(resolvedDecl);
|
||||
for (const implDecl of implDecls) {
|
||||
if (implDecl && implDecl.path) {
|
||||
if (implDecl && !implDecl.uri.isEmpty()) {
|
||||
addDeclarationIfUnique(resolvedDeclarations, implDecl);
|
||||
}
|
||||
}
|
||||
@ -202,7 +203,7 @@ export class DocumentSymbolCollector extends ParseTreeWalker {
|
||||
}
|
||||
});
|
||||
|
||||
const sourceFileInfo = program.getSourceFileInfo(fileInfo.filePath);
|
||||
const sourceFileInfo = program.getSourceFileInfo(fileUri);
|
||||
if (sourceFileInfo && sourceFileInfo.sourceFile.getIPythonMode() === IPythonMode.CellDocs) {
|
||||
// Add declarations from chained source files
|
||||
let builtinsScope = fileInfo.builtinsScope;
|
||||
@ -215,7 +216,7 @@ export class DocumentSymbolCollector extends ParseTreeWalker {
|
||||
// Add declarations from files that implicitly import the target file.
|
||||
const implicitlyImportedBy = collectImportedByCells(program, sourceFileInfo);
|
||||
implicitlyImportedBy.forEach((implicitImport) => {
|
||||
const parseTree = program.getParseResults(implicitImport.sourceFile.getFilePath())?.parseTree;
|
||||
const parseTree = program.getParseResults(implicitImport.sourceFile.getUri())?.parseTree;
|
||||
if (parseTree) {
|
||||
const scope = AnalyzerNodeInfo.getScope(parseTree);
|
||||
const symbol = scope?.lookUpSymbol(node.value);
|
||||
@ -449,7 +450,7 @@ function _getDeclarationsForNonModuleNameNode(
|
||||
const type = evaluator.getType(node);
|
||||
if (type?.category === TypeCategory.Module) {
|
||||
// Synthesize decl for the module.
|
||||
return [createSynthesizedAliasDeclaration(type.filePath)];
|
||||
return [createSynthesizedAliasDeclaration(type.fileUri)];
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -10,23 +10,22 @@
|
||||
|
||||
import { CancellationToken, DocumentSymbol, Location, SymbolInformation } from 'vscode-languageserver';
|
||||
|
||||
import { getFileInfo } from '../analyzer/analyzerNodeInfo';
|
||||
import { throwIfCancellationRequested } from '../common/cancellationUtils';
|
||||
import { ProgramView } from '../common/extensibility';
|
||||
import { Uri } from '../common/uri/uri';
|
||||
import { ParseResults } from '../parser/parser';
|
||||
import { IndexSymbolData, SymbolIndexer } from './symbolIndexer';
|
||||
import { ProgramView } from '../common/extensibility';
|
||||
import { getFileInfo } from '../analyzer/analyzerNodeInfo';
|
||||
import { convertPathToUri } from '../common/pathUtils';
|
||||
|
||||
export function convertToFlatSymbols(
|
||||
program: ProgramView,
|
||||
filePath: string,
|
||||
uri: Uri,
|
||||
symbolList: DocumentSymbol[]
|
||||
): SymbolInformation[] {
|
||||
const flatSymbols: SymbolInformation[] = [];
|
||||
const documentUri = convertPathToUri(program.fileSystem, filePath);
|
||||
|
||||
for (const symbol of symbolList) {
|
||||
_appendToFlatSymbolsRecursive(flatSymbols, documentUri, symbol);
|
||||
_appendToFlatSymbolsRecursive(flatSymbols, uri, symbol);
|
||||
}
|
||||
|
||||
return flatSymbols;
|
||||
@ -37,11 +36,11 @@ export class DocumentSymbolProvider {
|
||||
|
||||
constructor(
|
||||
protected readonly program: ProgramView,
|
||||
protected readonly filePath: string,
|
||||
protected readonly uri: Uri,
|
||||
private readonly _supportHierarchicalDocumentSymbol: boolean,
|
||||
private readonly _token: CancellationToken
|
||||
) {
|
||||
this._parseResults = this.program.getParseResults(this.filePath);
|
||||
this._parseResults = this.program.getParseResults(this.uri);
|
||||
}
|
||||
|
||||
getSymbols(): DocumentSymbol[] | SymbolInformation[] {
|
||||
@ -54,12 +53,12 @@ export class DocumentSymbolProvider {
|
||||
return symbolList;
|
||||
}
|
||||
|
||||
return convertToFlatSymbols(this.program, this.filePath, symbolList);
|
||||
return convertToFlatSymbols(this.program, this.uri, symbolList);
|
||||
}
|
||||
|
||||
protected getHierarchicalSymbols() {
|
||||
const symbolList: DocumentSymbol[] = [];
|
||||
const parseResults = this.program.getParseResults(this.filePath);
|
||||
const parseResults = this.program.getParseResults(this.uri);
|
||||
if (!parseResults) {
|
||||
return symbolList;
|
||||
}
|
||||
@ -115,14 +114,14 @@ export class DocumentSymbolProvider {
|
||||
|
||||
function _appendToFlatSymbolsRecursive(
|
||||
flatSymbols: SymbolInformation[],
|
||||
documentUri: string,
|
||||
documentUri: Uri,
|
||||
symbol: DocumentSymbol,
|
||||
parent?: DocumentSymbol
|
||||
) {
|
||||
const flatSymbol: SymbolInformation = {
|
||||
name: symbol.name,
|
||||
kind: symbol.kind,
|
||||
location: Location.create(documentUri, symbol.range),
|
||||
location: Location.create(documentUri.toString(), symbol.range),
|
||||
};
|
||||
|
||||
if (symbol.tags) {
|
||||
|
@ -30,10 +30,12 @@ import {
|
||||
isTypeVar,
|
||||
} from '../analyzer/types';
|
||||
import { throwIfCancellationRequested } from '../common/cancellationUtils';
|
||||
import { SignatureDisplayType } from '../common/configOptions';
|
||||
import { assertNever, fail } from '../common/debug';
|
||||
import { ProgramView } from '../common/extensibility';
|
||||
import { convertOffsetToPosition, convertPositionToOffset } from '../common/positionUtils';
|
||||
import { Position, Range, TextRange } from '../common/textRange';
|
||||
import { Uri } from '../common/uri/uri';
|
||||
import { ExpressionNode, NameNode, ParseNode, ParseNodeType, StringNode } from '../parser/parseNodes';
|
||||
import { ParseResults } from '../parser/parser';
|
||||
import {
|
||||
@ -43,7 +45,6 @@ import {
|
||||
getToolTipForType,
|
||||
getTypeForToolTip,
|
||||
} from './tooltipUtils';
|
||||
import { SignatureDisplayType } from '../common/configOptions';
|
||||
|
||||
export interface HoverTextPart {
|
||||
python?: boolean;
|
||||
@ -151,13 +152,13 @@ export class HoverProvider {
|
||||
|
||||
constructor(
|
||||
private readonly _program: ProgramView,
|
||||
private readonly _filePath: string,
|
||||
private readonly _fileUri: Uri,
|
||||
private readonly _position: Position,
|
||||
private readonly _format: MarkupKind,
|
||||
private readonly _token: CancellationToken
|
||||
) {
|
||||
this._parseResults = this._program.getParseResults(this._filePath);
|
||||
this._sourceMapper = this._program.getSourceMapper(this._filePath, this._token, /* mapCompiled */ true);
|
||||
this._parseResults = this._program.getParseResults(this._fileUri);
|
||||
this._sourceMapper = this._program.getSourceMapper(this._fileUri, this._token, /* mapCompiled */ true);
|
||||
}
|
||||
|
||||
getHover(): Hover | null {
|
||||
|
@ -7,10 +7,10 @@
|
||||
*/
|
||||
import { Location } from 'vscode-languageserver-types';
|
||||
import { ReadOnlyFileSystem } from '../common/fileSystem';
|
||||
import { convertPathToUri } from '../common/pathUtils';
|
||||
import { DocumentRange } from '../common/textRange';
|
||||
import { Uri } from '../common/uri/uri';
|
||||
|
||||
export function canNavigateToFile(fs: ReadOnlyFileSystem, path: string): boolean {
|
||||
export function canNavigateToFile(fs: ReadOnlyFileSystem, path: Uri): boolean {
|
||||
return !fs.isInZip(path);
|
||||
}
|
||||
|
||||
@ -23,9 +23,9 @@ export function convertDocumentRangesToLocation(
|
||||
}
|
||||
|
||||
export function convertDocumentRangeToLocation(fs: ReadOnlyFileSystem, range: DocumentRange): Location | undefined {
|
||||
if (!canNavigateToFile(fs, range.path)) {
|
||||
if (!canNavigateToFile(fs, range.uri)) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
return Location.create(convertPathToUri(fs, range.path), range.range);
|
||||
return Location.create(range.uri.toString(), range.range);
|
||||
}
|
||||
|
@ -11,16 +11,17 @@ import { CancellationToken } from 'vscode-languageserver';
|
||||
|
||||
import { Commands } from '../commands/commands';
|
||||
import { ProgramView } from '../common/extensibility';
|
||||
import { Uri } from '../common/uri/uri';
|
||||
import { ImportSorter } from './importSorter';
|
||||
|
||||
export function performQuickAction(
|
||||
programView: ProgramView,
|
||||
filePath: string,
|
||||
uri: Uri,
|
||||
command: string,
|
||||
args: any[],
|
||||
token: CancellationToken
|
||||
) {
|
||||
const sourceFileInfo = programView.getSourceFileInfo(filePath);
|
||||
const sourceFileInfo = programView.getSourceFileInfo(uri);
|
||||
|
||||
// This command should be called only for open files, in which
|
||||
// case we should have the file contents already loaded.
|
||||
@ -29,7 +30,7 @@ export function performQuickAction(
|
||||
}
|
||||
|
||||
// If we have no completed analysis job, there's nothing to do.
|
||||
const parseResults = programView.getParseResults(filePath);
|
||||
const parseResults = programView.getParseResults(uri);
|
||||
if (!parseResults) {
|
||||
return [];
|
||||
}
|
||||
|
@ -19,18 +19,19 @@ import { isVisibleExternally } from '../analyzer/symbolUtils';
|
||||
import { TypeEvaluator } from '../analyzer/typeEvaluatorTypes';
|
||||
import { maxTypeRecursionCount } from '../analyzer/types';
|
||||
import { throwIfCancellationRequested } from '../common/cancellationUtils';
|
||||
import { isDefined } from '../common/core';
|
||||
import { appendArray } from '../common/collectionUtils';
|
||||
import { isDefined } from '../common/core';
|
||||
import { assertNever } from '../common/debug';
|
||||
import { ProgramView, ReferenceUseCase, SymbolUsageProvider } from '../common/extensibility';
|
||||
import { ReadOnlyFileSystem } from '../common/fileSystem';
|
||||
import { convertOffsetToPosition, convertPositionToOffset } from '../common/positionUtils';
|
||||
import { ServiceKeys } from '../common/serviceProviderExtensions';
|
||||
import { DocumentRange, Position, TextRange, doesRangeContain } from '../common/textRange';
|
||||
import { Uri } from '../common/uri/uri';
|
||||
import { NameNode, ParseNode, ParseNodeType } from '../parser/parseNodes';
|
||||
import { ParseResults } from '../parser/parser';
|
||||
import { CollectionResult, DocumentSymbolCollector } from './documentSymbolCollector';
|
||||
import { ReadOnlyFileSystem } from '../common/fileSystem';
|
||||
import { convertDocumentRangesToLocation } from './navigationUtils';
|
||||
import { ServiceKeys } from '../common/serviceProviderExtensions';
|
||||
|
||||
export type ReferenceCallback = (locations: DocumentRange[]) => void;
|
||||
|
||||
@ -103,17 +104,17 @@ export class FindReferencesTreeWalker {
|
||||
|
||||
constructor(
|
||||
private _program: ProgramView,
|
||||
private _filePath: string,
|
||||
private _fileUri: Uri,
|
||||
private _referencesResult: ReferencesResult,
|
||||
private _includeDeclaration: boolean,
|
||||
private _cancellationToken: CancellationToken,
|
||||
private readonly _createDocumentRange: (
|
||||
filePath: string,
|
||||
fileUri: Uri,
|
||||
result: CollectionResult,
|
||||
parseResults: ParseResults
|
||||
) => DocumentRange = FindReferencesTreeWalker.createDocumentRange
|
||||
) {
|
||||
this._parseResults = this._program.getParseResults(this._filePath);
|
||||
this._parseResults = this._program.getParseResults(this._fileUri);
|
||||
}
|
||||
|
||||
findReferences(rootNode = this._parseResults?.parseTree) {
|
||||
@ -139,16 +140,16 @@ export class FindReferencesTreeWalker {
|
||||
for (const result of collector.collect()) {
|
||||
// Is it the same symbol?
|
||||
if (this._includeDeclaration || result.node !== this._referencesResult.nodeAtOffset) {
|
||||
results.push(this._createDocumentRange(this._filePath, result, this._parseResults));
|
||||
results.push(this._createDocumentRange(this._fileUri, result, this._parseResults));
|
||||
}
|
||||
}
|
||||
|
||||
return results;
|
||||
}
|
||||
|
||||
static createDocumentRange(filePath: string, result: CollectionResult, parseResults: ParseResults): DocumentRange {
|
||||
static createDocumentRange(fileUri: Uri, result: CollectionResult, parseResults: ParseResults): DocumentRange {
|
||||
return {
|
||||
path: filePath,
|
||||
uri: fileUri,
|
||||
range: {
|
||||
start: convertOffsetToPosition(result.range.start, parseResults.tokenizerOutput.lines),
|
||||
end: convertOffsetToPosition(TextRange.getEnd(result.range), parseResults.tokenizerOutput.lines),
|
||||
@ -162,7 +163,7 @@ export class ReferencesProvider {
|
||||
private _program: ProgramView,
|
||||
private _token: CancellationToken,
|
||||
private readonly _createDocumentRange?: (
|
||||
filePath: string,
|
||||
fileUri: Uri,
|
||||
result: CollectionResult,
|
||||
parseResults: ParseResults
|
||||
) => DocumentRange,
|
||||
@ -172,17 +173,17 @@ export class ReferencesProvider {
|
||||
}
|
||||
|
||||
reportReferences(
|
||||
filePath: string,
|
||||
fileUri: Uri,
|
||||
position: Position,
|
||||
includeDeclaration: boolean,
|
||||
resultReporter?: ResultProgressReporter<Location[]>
|
||||
) {
|
||||
const sourceFileInfo = this._program.getSourceFileInfo(filePath);
|
||||
const sourceFileInfo = this._program.getSourceFileInfo(fileUri);
|
||||
if (!sourceFileInfo) {
|
||||
return;
|
||||
}
|
||||
|
||||
const parseResults = this._program.getParseResults(filePath);
|
||||
const parseResults = this._program.getParseResults(fileUri);
|
||||
if (!parseResults) {
|
||||
return;
|
||||
}
|
||||
@ -202,7 +203,7 @@ export class ReferencesProvider {
|
||||
const invokedFromUserFile = isUserCode(sourceFileInfo);
|
||||
const referencesResult = ReferencesProvider.getDeclarationForPosition(
|
||||
this._program,
|
||||
filePath,
|
||||
fileUri,
|
||||
position,
|
||||
reporter,
|
||||
ReferenceUseCase.References,
|
||||
@ -214,7 +215,7 @@ export class ReferencesProvider {
|
||||
|
||||
// Do we need to do a global search as well?
|
||||
if (!referencesResult.requiresGlobalSearch) {
|
||||
this.addReferencesToResult(sourceFileInfo.sourceFile.getFilePath(), includeDeclaration, referencesResult);
|
||||
this.addReferencesToResult(sourceFileInfo.sourceFile.getUri(), includeDeclaration, referencesResult);
|
||||
}
|
||||
|
||||
for (const curSourceFileInfo of this._program.getSourceFileInfoList()) {
|
||||
@ -228,7 +229,7 @@ export class ReferencesProvider {
|
||||
const fileContents = curSourceFileInfo.sourceFile.getFileContent();
|
||||
if (!fileContents || referencesResult.symbolNames.some((s) => fileContents.search(s) >= 0)) {
|
||||
this.addReferencesToResult(
|
||||
curSourceFileInfo.sourceFile.getFilePath(),
|
||||
curSourceFileInfo.sourceFile.getUri(),
|
||||
includeDeclaration,
|
||||
referencesResult
|
||||
);
|
||||
@ -246,12 +247,12 @@ export class ReferencesProvider {
|
||||
for (const decl of referencesResult.declarations) {
|
||||
throwIfCancellationRequested(this._token);
|
||||
|
||||
if (referencesResult.locations.some((l) => l.path === decl.path)) {
|
||||
if (referencesResult.locations.some((l) => l.uri.equals(decl.uri))) {
|
||||
// Already included.
|
||||
continue;
|
||||
}
|
||||
|
||||
const declFileInfo = this._program.getSourceFileInfo(decl.path);
|
||||
const declFileInfo = this._program.getSourceFileInfo(decl.uri);
|
||||
if (!declFileInfo) {
|
||||
// The file the declaration belongs to doesn't belong to the program.
|
||||
continue;
|
||||
@ -266,10 +267,10 @@ export class ReferencesProvider {
|
||||
referencesResult.providers
|
||||
);
|
||||
|
||||
this.addReferencesToResult(declFileInfo.sourceFile.getFilePath(), includeDeclaration, tempResult);
|
||||
this.addReferencesToResult(declFileInfo.sourceFile.getUri(), includeDeclaration, tempResult);
|
||||
for (const loc of tempResult.locations) {
|
||||
// Include declarations only. And throw away any references
|
||||
if (loc.path === decl.path && doesRangeContain(decl.range, loc.range)) {
|
||||
if (loc.uri.equals(decl.uri) && doesRangeContain(decl.range, loc.range)) {
|
||||
referencesResult.addLocations(loc);
|
||||
}
|
||||
}
|
||||
@ -279,15 +280,15 @@ export class ReferencesProvider {
|
||||
return locations;
|
||||
}
|
||||
|
||||
addReferencesToResult(filePath: string, includeDeclaration: boolean, referencesResult: ReferencesResult): void {
|
||||
const parseResults = this._program.getParseResults(filePath);
|
||||
addReferencesToResult(fileUri: Uri, includeDeclaration: boolean, referencesResult: ReferencesResult): void {
|
||||
const parseResults = this._program.getParseResults(fileUri);
|
||||
if (!parseResults) {
|
||||
return;
|
||||
}
|
||||
|
||||
const refTreeWalker = new FindReferencesTreeWalker(
|
||||
this._program,
|
||||
filePath,
|
||||
fileUri,
|
||||
referencesResult,
|
||||
includeDeclaration,
|
||||
this._token,
|
||||
@ -299,7 +300,7 @@ export class ReferencesProvider {
|
||||
|
||||
static getDeclarationForNode(
|
||||
program: ProgramView,
|
||||
filePath: string,
|
||||
fileUri: Uri,
|
||||
node: NameNode,
|
||||
reporter: ReferenceCallback | undefined,
|
||||
useCase: ReferenceUseCase,
|
||||
@ -318,7 +319,7 @@ export class ReferencesProvider {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const requiresGlobalSearch = isVisibleOutside(program.evaluator!, filePath, node, declarations);
|
||||
const requiresGlobalSearch = isVisibleOutside(program.evaluator!, fileUri, node, declarations);
|
||||
const symbolNames = new Set<string>(declarations.map((d) => getNameFromDeclaration(d)!).filter((n) => !!n));
|
||||
symbolNames.add(node.value);
|
||||
|
||||
@ -345,14 +346,14 @@ export class ReferencesProvider {
|
||||
|
||||
static getDeclarationForPosition(
|
||||
program: ProgramView,
|
||||
filePath: string,
|
||||
fileUri: Uri,
|
||||
position: Position,
|
||||
reporter: ReferenceCallback | undefined,
|
||||
useCase: ReferenceUseCase,
|
||||
token: CancellationToken
|
||||
): ReferencesResult | undefined {
|
||||
throwIfCancellationRequested(token);
|
||||
const parseResults = program.getParseResults(filePath);
|
||||
const parseResults = program.getParseResults(fileUri);
|
||||
if (!parseResults) {
|
||||
return undefined;
|
||||
}
|
||||
@ -372,16 +373,11 @@ export class ReferencesProvider {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
return this.getDeclarationForNode(program, filePath, node, reporter, useCase, token);
|
||||
return this.getDeclarationForNode(program, fileUri, node, reporter, useCase, token);
|
||||
}
|
||||
}
|
||||
|
||||
function isVisibleOutside(
|
||||
evaluator: TypeEvaluator,
|
||||
currentFilePath: string,
|
||||
node: NameNode,
|
||||
declarations: Declaration[]
|
||||
) {
|
||||
function isVisibleOutside(evaluator: TypeEvaluator, currentUri: Uri, node: NameNode, declarations: Declaration[]) {
|
||||
const result = evaluator.lookUpSymbolRecursive(node, node.value, /* honorCodeFlow */ false);
|
||||
if (result && !isExternallyVisible(result.symbol)) {
|
||||
return false;
|
||||
@ -394,7 +390,7 @@ function isVisibleOutside(
|
||||
// that is within the current file and cannot be imported directly from other modules.
|
||||
return declarations.some((decl) => {
|
||||
// If the declaration is outside of this file, a global search is needed.
|
||||
if (decl.path !== currentFilePath) {
|
||||
if (!decl.uri.equals(currentUri)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
|
@ -9,27 +9,28 @@
|
||||
|
||||
import { CancellationToken, WorkspaceEdit } from 'vscode-languageserver';
|
||||
|
||||
import { isUserCode } from '../analyzer/sourceFileInfoUtils';
|
||||
import { throwIfCancellationRequested } from '../common/cancellationUtils';
|
||||
import { assertNever } from '../common/debug';
|
||||
import { FileEditAction } from '../common/editAction';
|
||||
import { ProgramView, ReferenceUseCase } from '../common/extensibility';
|
||||
import { convertTextRangeToRange } from '../common/positionUtils';
|
||||
import { Position, Range } from '../common/textRange';
|
||||
import { ReferencesProvider, ReferencesResult } from '../languageService/referencesProvider';
|
||||
import { isUserCode } from '../analyzer/sourceFileInfoUtils';
|
||||
import { throwIfCancellationRequested } from '../common/cancellationUtils';
|
||||
import { ParseResults } from '../parser/parser';
|
||||
import { Uri } from '../common/uri/uri';
|
||||
import { convertToWorkspaceEdit } from '../common/workspaceEditUtils';
|
||||
import { ReferencesProvider, ReferencesResult } from '../languageService/referencesProvider';
|
||||
import { ParseResults } from '../parser/parser';
|
||||
|
||||
export class RenameProvider {
|
||||
private readonly _parseResults: ParseResults | undefined;
|
||||
|
||||
constructor(
|
||||
private _program: ProgramView,
|
||||
private _filePath: string,
|
||||
private _fileUri: Uri,
|
||||
private _position: Position,
|
||||
private _token: CancellationToken
|
||||
) {
|
||||
this._parseResults = this._program.getParseResults(this._filePath);
|
||||
this._parseResults = this._program.getParseResults(this._fileUri);
|
||||
}
|
||||
|
||||
canRenameSymbol(isDefaultWorkspace: boolean, isUntitled: boolean): Range | null {
|
||||
@ -45,7 +46,7 @@ export class RenameProvider {
|
||||
|
||||
const renameMode = RenameProvider.getRenameSymbolMode(
|
||||
this._program,
|
||||
this._filePath,
|
||||
this._fileUri,
|
||||
referencesResult,
|
||||
isDefaultWorkspace,
|
||||
isUntitled
|
||||
@ -72,7 +73,7 @@ export class RenameProvider {
|
||||
const referenceProvider = new ReferencesProvider(this._program, this._token);
|
||||
const renameMode = RenameProvider.getRenameSymbolMode(
|
||||
this._program,
|
||||
this._filePath,
|
||||
this._fileUri,
|
||||
referencesResult,
|
||||
isDefaultWorkspace,
|
||||
isUntitled
|
||||
@ -80,11 +81,7 @@ export class RenameProvider {
|
||||
|
||||
switch (renameMode) {
|
||||
case 'singleFileMode':
|
||||
referenceProvider.addReferencesToResult(
|
||||
this._filePath,
|
||||
/* includeDeclaration */ true,
|
||||
referencesResult
|
||||
);
|
||||
referenceProvider.addReferencesToResult(this._fileUri, /* includeDeclaration */ true, referencesResult);
|
||||
break;
|
||||
|
||||
case 'multiFileMode': {
|
||||
@ -99,7 +96,7 @@ export class RenameProvider {
|
||||
}
|
||||
|
||||
referenceProvider.addReferencesToResult(
|
||||
curSourceFileInfo.sourceFile.getFilePath(),
|
||||
curSourceFileInfo.sourceFile.getUri(),
|
||||
/* includeDeclaration */ true,
|
||||
referencesResult
|
||||
);
|
||||
@ -124,23 +121,23 @@ export class RenameProvider {
|
||||
const edits: FileEditAction[] = [];
|
||||
referencesResult.locations.forEach((loc) => {
|
||||
edits.push({
|
||||
filePath: loc.path,
|
||||
fileUri: loc.uri,
|
||||
range: loc.range,
|
||||
replacementText: newName,
|
||||
});
|
||||
});
|
||||
|
||||
return convertToWorkspaceEdit(this._program.fileSystem, { edits, fileOperations: [] });
|
||||
return convertToWorkspaceEdit({ edits, fileOperations: [] });
|
||||
}
|
||||
|
||||
static getRenameSymbolMode(
|
||||
program: ProgramView,
|
||||
filePath: string,
|
||||
fileUri: Uri,
|
||||
referencesResult: ReferencesResult,
|
||||
isDefaultWorkspace: boolean,
|
||||
isUntitled: boolean
|
||||
) {
|
||||
const sourceFileInfo = program.getSourceFileInfo(filePath)!;
|
||||
const sourceFileInfo = program.getSourceFileInfo(fileUri)!;
|
||||
|
||||
// We have 2 different cases
|
||||
// Single file mode.
|
||||
@ -156,12 +153,12 @@ export class RenameProvider {
|
||||
(userFile && !referencesResult.requiresGlobalSearch) ||
|
||||
(!userFile &&
|
||||
sourceFileInfo.isOpenByClient &&
|
||||
referencesResult.declarations.every((d) => program.getSourceFileInfo(d.path) === sourceFileInfo))
|
||||
referencesResult.declarations.every((d) => program.getSourceFileInfo(d.uri) === sourceFileInfo))
|
||||
) {
|
||||
return 'singleFileMode';
|
||||
}
|
||||
|
||||
if (!isUntitled && referencesResult.declarations.every((d) => isUserCode(program.getSourceFileInfo(d.path)))) {
|
||||
if (!isUntitled && referencesResult.declarations.every((d) => isUserCode(program.getSourceFileInfo(d.uri)))) {
|
||||
return 'multiFileMode';
|
||||
}
|
||||
|
||||
@ -173,7 +170,7 @@ export class RenameProvider {
|
||||
private _getReferenceResult() {
|
||||
const referencesResult = ReferencesProvider.getDeclarationForPosition(
|
||||
this._program,
|
||||
this._filePath,
|
||||
this._fileUri,
|
||||
this._position,
|
||||
/* reporter */ undefined,
|
||||
ReferenceUseCase.Rename,
|
||||
|
@ -31,6 +31,7 @@ import { throwIfCancellationRequested } from '../common/cancellationUtils';
|
||||
import { ProgramView } from '../common/extensibility';
|
||||
import { convertPositionToOffset } from '../common/positionUtils';
|
||||
import { Position } from '../common/textRange';
|
||||
import { Uri } from '../common/uri/uri';
|
||||
import { CallNode, NameNode, ParseNodeType } from '../parser/parseNodes';
|
||||
import { ParseResults } from '../parser/parser';
|
||||
import { getDocumentationPartsForTypeAndDecl, getFunctionDocStringFromType } from './tooltipUtils';
|
||||
@ -41,7 +42,7 @@ export class SignatureHelpProvider {
|
||||
|
||||
constructor(
|
||||
private _program: ProgramView,
|
||||
private _filePath: string,
|
||||
private _fileUri: Uri,
|
||||
private _position: Position,
|
||||
private _format: MarkupKind,
|
||||
private _hasSignatureLabelOffsetCapability: boolean,
|
||||
@ -49,8 +50,8 @@ export class SignatureHelpProvider {
|
||||
private _context: SignatureHelpContext | undefined,
|
||||
private _token: CancellationToken
|
||||
) {
|
||||
this._parseResults = this._program.getParseResults(this._filePath);
|
||||
this._sourceMapper = this._program.getSourceMapper(this._filePath, this._token, /* mapCompiled */ true);
|
||||
this._parseResults = this._program.getParseResults(this._fileUri);
|
||||
this._sourceMapper = this._program.getSourceMapper(this._fileUri, this._token, /* mapCompiled */ true);
|
||||
}
|
||||
|
||||
getSignatureHelp(): SignatureHelp | undefined {
|
||||
|
@ -13,15 +13,16 @@ import * as AnalyzerNodeInfo from '../analyzer/analyzerNodeInfo';
|
||||
import { Declaration, DeclarationType } from '../analyzer/declaration';
|
||||
import { getLastTypedDeclaredForSymbol, isVisibleExternally } from '../analyzer/symbolUtils';
|
||||
import { throwIfCancellationRequested } from '../common/cancellationUtils';
|
||||
import { getSymbolKind } from '../common/lspUtils';
|
||||
import { convertOffsetsToRange } from '../common/positionUtils';
|
||||
import { Range } from '../common/textRange';
|
||||
import { Uri } from '../common/uri/uri';
|
||||
import { ParseResults } from '../parser/parser';
|
||||
import { convertSymbolKindToCompletionItemKind } from './autoImporter';
|
||||
import { getSymbolKind } from '../common/lspUtils';
|
||||
|
||||
export interface IndexAliasData {
|
||||
readonly originalName: string;
|
||||
readonly modulePath: string;
|
||||
readonly moduleUri: Uri;
|
||||
readonly kind: SymbolKind;
|
||||
readonly itemKind?: CompletionItemKind | undefined;
|
||||
}
|
||||
|
@ -16,7 +16,7 @@ import {
|
||||
getClassDocString,
|
||||
getFunctionDocStringInherited,
|
||||
getModuleDocString,
|
||||
getModuleDocStringFromPaths,
|
||||
getModuleDocStringFromUris,
|
||||
getOverloadedFunctionDocStringsInherited,
|
||||
getPropertyDocStringInherited,
|
||||
getVariableDocString,
|
||||
@ -323,7 +323,7 @@ export function getDocumentationPartsForTypeAndDecl(
|
||||
}
|
||||
}
|
||||
|
||||
typeDoc = getModuleDocStringFromPaths([resolvedDecl.path], sourceMapper);
|
||||
typeDoc = getModuleDocStringFromUris([resolvedDecl.uri], sourceMapper);
|
||||
}
|
||||
|
||||
typeDoc =
|
||||
|
@ -7,15 +7,15 @@
|
||||
*/
|
||||
|
||||
import { CancellationToken, Location, ResultProgressReporter, SymbolInformation } from 'vscode-languageserver';
|
||||
import { throwIfCancellationRequested } from '../common/cancellationUtils';
|
||||
import * as StringUtils from '../common/stringUtils';
|
||||
import { IndexSymbolData, SymbolIndexer } from './symbolIndexer';
|
||||
import { ProgramView } from '../common/extensibility';
|
||||
import { isUserCode } from '../analyzer/sourceFileInfoUtils';
|
||||
import { getFileInfo } from '../analyzer/analyzerNodeInfo';
|
||||
import { convertPathToUri } from '../common/pathUtils';
|
||||
import { Workspace } from '../workspaceFactory';
|
||||
import { isUserCode } from '../analyzer/sourceFileInfoUtils';
|
||||
import { throwIfCancellationRequested } from '../common/cancellationUtils';
|
||||
import { appendArray } from '../common/collectionUtils';
|
||||
import { ProgramView } from '../common/extensibility';
|
||||
import * as StringUtils from '../common/stringUtils';
|
||||
import { Uri } from '../common/uri/uri';
|
||||
import { Workspace } from '../workspaceFactory';
|
||||
import { IndexSymbolData, SymbolIndexer } from './symbolIndexer';
|
||||
|
||||
type WorkspaceSymbolCallback = (symbols: SymbolInformation[]) => void;
|
||||
|
||||
@ -55,10 +55,10 @@ export class WorkspaceSymbolProvider {
|
||||
return this._allSymbols;
|
||||
}
|
||||
|
||||
protected getSymbolsForDocument(program: ProgramView, filePath: string): SymbolInformation[] {
|
||||
protected getSymbolsForDocument(program: ProgramView, fileUri: Uri): SymbolInformation[] {
|
||||
const symbolList: SymbolInformation[] = [];
|
||||
|
||||
const parseResults = program.getParseResults(filePath);
|
||||
const parseResults = program.getParseResults(fileUri);
|
||||
if (!parseResults) {
|
||||
return symbolList;
|
||||
}
|
||||
@ -69,7 +69,7 @@ export class WorkspaceSymbolProvider {
|
||||
}
|
||||
|
||||
const indexSymbolData = SymbolIndexer.indexSymbols(fileInfo, parseResults, this._token);
|
||||
this.appendWorkspaceSymbolsRecursive(indexSymbolData, program, filePath, '', symbolList);
|
||||
this.appendWorkspaceSymbolsRecursive(indexSymbolData, program, fileUri, '', symbolList);
|
||||
|
||||
return symbolList;
|
||||
}
|
||||
@ -77,7 +77,7 @@ export class WorkspaceSymbolProvider {
|
||||
protected appendWorkspaceSymbolsRecursive(
|
||||
indexSymbolData: IndexSymbolData[] | undefined,
|
||||
program: ProgramView,
|
||||
filePath: string,
|
||||
fileUri: Uri,
|
||||
container: string,
|
||||
symbolList: SymbolInformation[]
|
||||
) {
|
||||
@ -94,7 +94,7 @@ export class WorkspaceSymbolProvider {
|
||||
|
||||
if (StringUtils.isPatternInSymbol(this._query, symbolData.name)) {
|
||||
const location: Location = {
|
||||
uri: convertPathToUri(program.fileSystem, filePath),
|
||||
uri: fileUri.toString(),
|
||||
range: symbolData.selectionRange!,
|
||||
};
|
||||
|
||||
@ -114,7 +114,7 @@ export class WorkspaceSymbolProvider {
|
||||
this.appendWorkspaceSymbolsRecursive(
|
||||
symbolData.children,
|
||||
program,
|
||||
filePath,
|
||||
fileUri,
|
||||
this._getContainerName(container, symbolData.name),
|
||||
symbolList
|
||||
);
|
||||
@ -134,7 +134,7 @@ export class WorkspaceSymbolProvider {
|
||||
continue;
|
||||
}
|
||||
|
||||
const symbolList = this.getSymbolsForDocument(program, sourceFileInfo.sourceFile.getFilePath());
|
||||
const symbolList = this.getSymbolsForDocument(program, sourceFileInfo.sourceFile.getUri());
|
||||
if (symbolList.length > 0) {
|
||||
this._reporter(symbolList);
|
||||
}
|
||||
|
@ -28,12 +28,14 @@ import { createDeferred } from './common/deferred';
|
||||
import { Diagnostic, DiagnosticCategory } from './common/diagnostic';
|
||||
import { FileDiagnostics } from './common/diagnosticSink';
|
||||
import { FullAccessHost } from './common/fullAccessHost';
|
||||
import { combinePaths, getFileSpec, normalizePath, tryStat } from './common/pathUtils';
|
||||
import { combinePaths, normalizePath } from './common/pathUtils';
|
||||
import { versionFromString } from './common/pythonVersion';
|
||||
import { RealTempFile, createFromRealFileSystem } from './common/realFileSystem';
|
||||
import { ServiceProvider } from './common/serviceProvider';
|
||||
import { createServiceProvider } from './common/serviceProviderExtensions';
|
||||
import { Range, isEmptyRange } from './common/textRange';
|
||||
import { Uri } from './common/uri/uri';
|
||||
import { getFileSpec, tryStat } from './common/uri/uriUtils';
|
||||
import { PyrightFileSystem } from './pyrightFileSystem';
|
||||
|
||||
const toolName = 'pyright';
|
||||
@ -64,11 +66,11 @@ interface PyrightSymbolCount {
|
||||
|
||||
interface PyrightTypeCompletenessReport {
|
||||
packageName: string;
|
||||
packageRootDirectory?: string | undefined;
|
||||
packageRootDirectory?: Uri | undefined;
|
||||
moduleName: string;
|
||||
moduleRootDirectory?: string | undefined;
|
||||
moduleRootDirectory?: Uri | undefined;
|
||||
ignoreUnknownTypesFromImports: boolean;
|
||||
pyTypedPath?: string | undefined;
|
||||
pyTypedPath?: Uri | undefined;
|
||||
exportedSymbolCounts: PyrightSymbolCount;
|
||||
otherSymbolCounts: PyrightSymbolCount;
|
||||
missingFunctionDocStringCount: number;
|
||||
@ -95,7 +97,7 @@ interface PyrightPublicSymbolReport {
|
||||
}
|
||||
|
||||
interface PyrightJsonDiagnostic {
|
||||
file: string;
|
||||
uri: Uri;
|
||||
severity: SeverityLevel;
|
||||
message: string;
|
||||
range?: Range | undefined;
|
||||
@ -244,10 +246,9 @@ async function processArgs(): Promise<ExitStatus> {
|
||||
|
||||
// Verify the specified file specs to make sure their wildcard roots exist.
|
||||
const tempFileSystem = new PyrightFileSystem(createFromRealFileSystem());
|
||||
const tempServiceProvider = createServiceProvider(tempFileSystem, console);
|
||||
|
||||
for (const fileDesc of options.includeFileSpecsOverride) {
|
||||
const includeSpec = getFileSpec(tempServiceProvider, '', fileDesc);
|
||||
const includeSpec = getFileSpec(Uri.file(process.cwd(), tempFileSystem.isCaseSensitive), fileDesc);
|
||||
try {
|
||||
const stat = tryStat(tempFileSystem, includeSpec.wildcardRoot);
|
||||
if (!stat) {
|
||||
@ -361,7 +362,7 @@ async function processArgs(): Promise<ExitStatus> {
|
||||
// up the JSON output, which goes to stdout.
|
||||
const output = args.outputjson ? new StderrConsole(logLevel) : new StandardConsole(logLevel);
|
||||
const fileSystem = new PyrightFileSystem(createFromRealFileSystem(output, new ChokidarFileWatcherProvider(output)));
|
||||
const tempFile = new RealTempFile();
|
||||
const tempFile = new RealTempFile(fileSystem.isCaseSensitive);
|
||||
const serviceProvider = createServiceProvider(fileSystem, output, tempFile);
|
||||
|
||||
// The package type verification uses a different path.
|
||||
@ -386,7 +387,7 @@ async function processArgs(): Promise<ExitStatus> {
|
||||
// Refresh service after 2 seconds after the last library file change is detected.
|
||||
const service = new AnalyzerService('<default>', serviceProvider, {
|
||||
console: output,
|
||||
hostFactory: () => new FullAccessHost(fileSystem),
|
||||
hostFactory: () => new FullAccessHost(serviceProvider),
|
||||
libraryReanalysisTimeProvider: () => 2 * 1000,
|
||||
});
|
||||
const exitStatus = createDeferred<ExitStatus>();
|
||||
@ -540,7 +541,7 @@ function buildTypeCompletenessReport(
|
||||
|
||||
// Add the general diagnostics.
|
||||
completenessReport.generalDiagnostics.forEach((diag) => {
|
||||
const jsonDiag = convertDiagnosticToJson('', diag);
|
||||
const jsonDiag = convertDiagnosticToJson(Uri.empty(), diag);
|
||||
if (isDiagnosticIncluded(jsonDiag.severity, minSeverityLevel)) {
|
||||
report.generalDiagnostics.push(jsonDiag);
|
||||
}
|
||||
@ -549,11 +550,11 @@ function buildTypeCompletenessReport(
|
||||
|
||||
report.typeCompleteness = {
|
||||
packageName,
|
||||
packageRootDirectory: completenessReport.packageRootDirectory,
|
||||
packageRootDirectory: completenessReport.packageRootDirectoryUri,
|
||||
moduleName: completenessReport.moduleName,
|
||||
moduleRootDirectory: completenessReport.moduleRootDirectory,
|
||||
moduleRootDirectory: completenessReport.moduleRootDirectoryUri,
|
||||
ignoreUnknownTypesFromImports: completenessReport.ignoreExternal,
|
||||
pyTypedPath: completenessReport.pyTypedPath,
|
||||
pyTypedPath: completenessReport.pyTypedPathUri,
|
||||
exportedSymbolCounts: {
|
||||
withKnownType: 0,
|
||||
withAmbiguousType: 0,
|
||||
@ -587,7 +588,7 @@ function buildTypeCompletenessReport(
|
||||
|
||||
// Convert and filter the diagnostics.
|
||||
symbol.diagnostics.forEach((diag) => {
|
||||
const jsonDiag = convertDiagnosticToJson(diag.filePath, diag.diagnostic);
|
||||
const jsonDiag = convertDiagnosticToJson(diag.uri, diag.diagnostic);
|
||||
if (isDiagnosticIncluded(jsonDiag.severity, minSeverityLevel)) {
|
||||
diagnostics.push(jsonDiag);
|
||||
}
|
||||
@ -811,7 +812,7 @@ function reportDiagnosticsAsJson(
|
||||
diag.category === DiagnosticCategory.Warning ||
|
||||
diag.category === DiagnosticCategory.Information
|
||||
) {
|
||||
const jsonDiag = convertDiagnosticToJson(fileDiag.filePath, diag);
|
||||
const jsonDiag = convertDiagnosticToJson(fileDiag.fileUri, diag);
|
||||
if (isDiagnosticIncluded(jsonDiag.severity, minSeverityLevel)) {
|
||||
report.generalDiagnostics.push(jsonDiag);
|
||||
}
|
||||
@ -862,9 +863,9 @@ function convertDiagnosticCategoryToSeverity(category: DiagnosticCategory): Seve
|
||||
}
|
||||
}
|
||||
|
||||
function convertDiagnosticToJson(filePath: string, diag: Diagnostic): PyrightJsonDiagnostic {
|
||||
function convertDiagnosticToJson(uri: Uri, diag: Diagnostic): PyrightJsonDiagnostic {
|
||||
return {
|
||||
file: filePath,
|
||||
uri,
|
||||
severity: convertDiagnosticCategoryToSeverity(diag.category),
|
||||
message: diag.message,
|
||||
range: isEmptyRange(diag.range) ? undefined : diag.range,
|
||||
@ -891,9 +892,9 @@ function reportDiagnosticsAsText(
|
||||
);
|
||||
|
||||
if (fileErrorsAndWarnings.length > 0) {
|
||||
console.info(`${fileDiagnostics.filePath}`);
|
||||
console.info(`${fileDiagnostics.fileUri.toUserVisibleString()}`);
|
||||
fileErrorsAndWarnings.forEach((diag) => {
|
||||
const jsonDiag = convertDiagnosticToJson(fileDiagnostics.filePath, diag);
|
||||
const jsonDiag = convertDiagnosticToJson(fileDiagnostics.fileUri, diag);
|
||||
logDiagnosticToConsole(jsonDiag);
|
||||
|
||||
if (diag.category === DiagnosticCategory.Error) {
|
||||
@ -923,8 +924,8 @@ function reportDiagnosticsAsText(
|
||||
|
||||
function logDiagnosticToConsole(diag: PyrightJsonDiagnostic, prefix = ' ') {
|
||||
let message = prefix;
|
||||
if (diag.file) {
|
||||
message += `${diag.file}:`;
|
||||
if (!diag.uri.isEmpty()) {
|
||||
message += `${diag.uri.toUserVisibleString()}:`;
|
||||
}
|
||||
if (diag.range && !isEmptyRange(diag.range)) {
|
||||
message +=
|
||||
|
@ -15,13 +15,14 @@ import { getPyTypedInfo } from './analyzer/pyTypedUtils';
|
||||
import { ExecutionEnvironment } from './common/configOptions';
|
||||
import { FileSystem, MkDirOptions } from './common/fileSystem';
|
||||
import { stubsSuffix } from './common/pathConsts';
|
||||
import { combinePaths, ensureTrailingDirectorySeparator, isDirectory, tryStat } from './common/pathUtils';
|
||||
import { Uri } from './common/uri/uri';
|
||||
import { isDirectory, tryStat } from './common/uri/uriUtils';
|
||||
import { ReadOnlyAugmentedFileSystem } from './readonlyAugmentedFileSystem';
|
||||
|
||||
export interface SupportPartialStubs {
|
||||
isPartialStubPackagesScanned(execEnv: ExecutionEnvironment): boolean;
|
||||
isPathScanned(path: string): boolean;
|
||||
processPartialStubPackages(paths: string[], roots: string[], bundledStubPath?: string): void;
|
||||
isPathScanned(path: Uri): boolean;
|
||||
processPartialStubPackages(paths: Uri[], roots: Uri[], bundledStubPath?: Uri): void;
|
||||
clearPartialStubs(): void;
|
||||
}
|
||||
|
||||
@ -49,49 +50,45 @@ export class PyrightFileSystem extends ReadOnlyAugmentedFileSystem implements IP
|
||||
super(realFS);
|
||||
}
|
||||
|
||||
override mkdirSync(path: string, options?: MkDirOptions): void {
|
||||
this.realFS.mkdirSync(path, options);
|
||||
override mkdirSync(uri: Uri, options?: MkDirOptions): void {
|
||||
this.realFS.mkdirSync(uri, options);
|
||||
}
|
||||
|
||||
override chdir(path: string): void {
|
||||
this.realFS.chdir(path);
|
||||
override chdir(uri: Uri): void {
|
||||
this.realFS.chdir(uri);
|
||||
}
|
||||
|
||||
override writeFileSync(path: string, data: string | Buffer, encoding: BufferEncoding | null): void {
|
||||
this.realFS.writeFileSync(this.getOriginalPath(path), data, encoding);
|
||||
override writeFileSync(uri: Uri, data: string | Buffer, encoding: BufferEncoding | null): void {
|
||||
this.realFS.writeFileSync(this.getOriginalPath(uri), data, encoding);
|
||||
}
|
||||
|
||||
override rmdirSync(path: string): void {
|
||||
this.realFS.rmdirSync(this.getOriginalPath(path));
|
||||
override rmdirSync(uri: Uri): void {
|
||||
this.realFS.rmdirSync(this.getOriginalPath(uri));
|
||||
}
|
||||
|
||||
override unlinkSync(path: string): void {
|
||||
this.realFS.unlinkSync(this.getOriginalPath(path));
|
||||
override unlinkSync(uri: Uri): void {
|
||||
this.realFS.unlinkSync(this.getOriginalPath(uri));
|
||||
}
|
||||
|
||||
override createWriteStream(path: string): fs.WriteStream {
|
||||
return this.realFS.createWriteStream(this.getOriginalPath(path));
|
||||
override createWriteStream(uri: Uri): fs.WriteStream {
|
||||
return this.realFS.createWriteStream(this.getOriginalPath(uri));
|
||||
}
|
||||
|
||||
override copyFileSync(src: string, dst: string): void {
|
||||
override copyFileSync(src: Uri, dst: Uri): void {
|
||||
this.realFS.copyFileSync(this.getOriginalPath(src), this.getOriginalPath(dst));
|
||||
}
|
||||
|
||||
override getUri(originalPath: string): string {
|
||||
return this.realFS.getUri(originalPath);
|
||||
}
|
||||
|
||||
isPartialStubPackagesScanned(execEnv: ExecutionEnvironment): boolean {
|
||||
return this.isPathScanned(execEnv.root ?? '');
|
||||
return execEnv.root ? this.isPathScanned(execEnv.root) : false;
|
||||
}
|
||||
|
||||
isPathScanned(path: string): boolean {
|
||||
return this._rootSearched.has(path);
|
||||
isPathScanned(uri: Uri): boolean {
|
||||
return this._rootSearched.has(uri.key);
|
||||
}
|
||||
|
||||
processPartialStubPackages(paths: string[], roots: string[], bundledStubPath?: string) {
|
||||
processPartialStubPackages(paths: Uri[], roots: Uri[], bundledStubPath?: Uri) {
|
||||
for (const path of paths) {
|
||||
this._rootSearched.add(path);
|
||||
this._rootSearched.add(path.key);
|
||||
|
||||
if (!this.realFS.existsSync(path) || !isDirectory(this.realFS, path)) {
|
||||
continue;
|
||||
@ -105,9 +102,9 @@ export class PyrightFileSystem extends ReadOnlyAugmentedFileSystem implements IP
|
||||
// Leave empty set of dir entries to process.
|
||||
}
|
||||
|
||||
const isBundledStub = path === bundledStubPath;
|
||||
const isBundledStub = path.equals(bundledStubPath);
|
||||
for (const entry of dirEntries) {
|
||||
const partialStubPackagePath = combinePaths(path, entry.name);
|
||||
const partialStubPackagePath = path.combinePaths(entry.name);
|
||||
const isDirectory = !entry.isSymbolicLink()
|
||||
? entry.isDirectory()
|
||||
: !!tryStat(this.realFS, partialStubPackagePath)?.isDirectory();
|
||||
@ -123,13 +120,13 @@ export class PyrightFileSystem extends ReadOnlyAugmentedFileSystem implements IP
|
||||
}
|
||||
|
||||
// We found partially typed stub-packages.
|
||||
this._partialStubPackagePaths.add(partialStubPackagePath);
|
||||
this._partialStubPackagePaths.add(partialStubPackagePath.key);
|
||||
|
||||
// Search the root to see whether we have matching package installed.
|
||||
let partialStubs: string[] | undefined;
|
||||
const packageName = entry.name.substr(0, entry.name.length - stubsSuffix.length);
|
||||
for (const root of roots) {
|
||||
const packagePath = combinePaths(root, packageName);
|
||||
const packagePath = root.combinePaths(packageName);
|
||||
try {
|
||||
const stat = tryStat(this.realFS, packagePath);
|
||||
if (!stat?.isDirectory()) {
|
||||
@ -149,8 +146,8 @@ export class PyrightFileSystem extends ReadOnlyAugmentedFileSystem implements IP
|
||||
// Merge partial stub packages to the library.
|
||||
partialStubs = partialStubs ?? this._getRelativePathPartialStubs(partialStubPackagePath);
|
||||
for (const partialStub of partialStubs) {
|
||||
const originalPyiFile = combinePaths(partialStubPackagePath, partialStub);
|
||||
const mappedPyiFile = combinePaths(packagePath, partialStub);
|
||||
const originalPyiFile = partialStubPackagePath.combinePaths(partialStub);
|
||||
const mappedPyiFile = packagePath.combinePaths(partialStub);
|
||||
this.recordMovedEntry(mappedPyiFile, originalPyiFile, packagePath);
|
||||
}
|
||||
} catch {
|
||||
@ -168,17 +165,15 @@ export class PyrightFileSystem extends ReadOnlyAugmentedFileSystem implements IP
|
||||
this._partialStubPackagePaths.clear();
|
||||
}
|
||||
|
||||
protected override isMovedEntry(path: string) {
|
||||
return this._partialStubPackagePaths.has(path) || super.isMovedEntry(path);
|
||||
protected override isMovedEntry(uri: Uri) {
|
||||
return this._partialStubPackagePaths.has(uri.key) || super.isMovedEntry(uri);
|
||||
}
|
||||
|
||||
private _getRelativePathPartialStubs(path: string) {
|
||||
const paths: string[] = [];
|
||||
|
||||
const partialStubPathLength = ensureTrailingDirectorySeparator(path).length;
|
||||
const searchAllStubs = (path: string) => {
|
||||
for (const entry of this.realFS.readdirEntriesSync(path)) {
|
||||
const filePath = combinePaths(path, entry.name);
|
||||
private _getRelativePathPartialStubs(partialStubPath: Uri) {
|
||||
const relativePaths: string[] = [];
|
||||
const searchAllStubs = (uri: Uri) => {
|
||||
for (const entry of this.realFS.readdirEntriesSync(uri)) {
|
||||
const filePath = uri.combinePaths(entry.name);
|
||||
|
||||
let isDirectory = entry.isDirectory();
|
||||
let isFile = entry.isFile();
|
||||
@ -195,15 +190,15 @@ export class PyrightFileSystem extends ReadOnlyAugmentedFileSystem implements IP
|
||||
}
|
||||
|
||||
if (isFile && entry.name.endsWith('.pyi')) {
|
||||
const relative = filePath.substring(partialStubPathLength);
|
||||
const relative = partialStubPath.getRelativePathComponents(filePath).join('/');
|
||||
if (relative) {
|
||||
paths.push(relative);
|
||||
relativePaths.push(relative);
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
searchAllStubs(path);
|
||||
return paths;
|
||||
searchAllStubs(partialStubPath);
|
||||
return relativePaths;
|
||||
}
|
||||
}
|
||||
|
@ -12,57 +12,53 @@ import type * as fs from 'fs';
|
||||
import { appendArray, getOrAdd } from './common/collectionUtils';
|
||||
import { FileSystem, MkDirOptions, Stats, VirtualDirent } from './common/fileSystem';
|
||||
import { FileWatcher, FileWatcherEventHandler } from './common/fileWatcher';
|
||||
import {
|
||||
combinePaths,
|
||||
ensureTrailingDirectorySeparator,
|
||||
getDirectoryPath,
|
||||
getFileName,
|
||||
getRelativePathComponentsFromDirectory,
|
||||
} from './common/pathUtils';
|
||||
import { Uri } from './common/uri/uri';
|
||||
|
||||
export class ReadOnlyAugmentedFileSystem implements FileSystem {
|
||||
// Mapped file to original file map
|
||||
private readonly _entryMap = new Map<string, string>();
|
||||
private readonly _entryMap = new Map<string, Uri>();
|
||||
|
||||
// Original file to mapped file map
|
||||
private readonly _reverseEntryMap = new Map<string, string>();
|
||||
private readonly _reverseEntryMap = new Map<string, Uri>();
|
||||
|
||||
// Mapped files per a containing folder map
|
||||
private readonly _folderMap = new Map<string, { name: string; isFile: boolean }[]>();
|
||||
|
||||
constructor(protected realFS: FileSystem) {}
|
||||
|
||||
existsSync(path: string): boolean {
|
||||
if (this.isMovedEntry(path)) {
|
||||
get isCaseSensitive(): boolean {
|
||||
return this.realFS.isCaseSensitive;
|
||||
}
|
||||
|
||||
existsSync(uri: Uri): boolean {
|
||||
if (this.isMovedEntry(uri)) {
|
||||
// Pretend partial stub folder and its files not exist
|
||||
return false;
|
||||
}
|
||||
|
||||
return this.realFS.existsSync(this.getOriginalPath(path));
|
||||
return this.realFS.existsSync(this.getOriginalPath(uri));
|
||||
}
|
||||
|
||||
mkdirSync(path: string, options?: MkDirOptions): void {
|
||||
mkdirSync(uri: Uri, options?: MkDirOptions): void {
|
||||
throw new Error('Operation is not allowed.');
|
||||
}
|
||||
|
||||
chdir(path: string): void {
|
||||
chdir(uri: Uri): void {
|
||||
throw new Error('Operation is not allowed.');
|
||||
}
|
||||
|
||||
readdirEntriesSync(path: string): fs.Dirent[] {
|
||||
const maybeDirectory = ensureTrailingDirectorySeparator(path);
|
||||
|
||||
readdirEntriesSync(uri: Uri): fs.Dirent[] {
|
||||
const entries: fs.Dirent[] = [];
|
||||
const movedEntries = this._folderMap.get(maybeDirectory);
|
||||
if (!movedEntries || this.realFS.existsSync(path)) {
|
||||
const movedEntries = this._folderMap.get(uri.key);
|
||||
if (!movedEntries || this.realFS.existsSync(uri)) {
|
||||
appendArray(
|
||||
entries,
|
||||
this.realFS.readdirEntriesSync(path).filter((item) => {
|
||||
this.realFS.readdirEntriesSync(uri).filter((item) => {
|
||||
// Filter out the stub package directory and any
|
||||
// entries that will be overwritten by stub package
|
||||
// virtual items.
|
||||
return (
|
||||
!this.isMovedEntry(combinePaths(path, item.name)) &&
|
||||
!this.isMovedEntry(uri.combinePaths(item.name)) &&
|
||||
!movedEntries?.some((movedEntry) => movedEntry.name === item.name)
|
||||
);
|
||||
})
|
||||
@ -76,129 +72,124 @@ export class ReadOnlyAugmentedFileSystem implements FileSystem {
|
||||
return entries.concat(movedEntries.map((e) => new VirtualDirent(e.name, e.isFile)));
|
||||
}
|
||||
|
||||
readdirSync(path: string): string[] {
|
||||
return this.readdirEntriesSync(path).map((p) => p.name);
|
||||
readdirSync(uri: Uri): string[] {
|
||||
return this.readdirEntriesSync(uri).map((p) => p.name);
|
||||
}
|
||||
|
||||
readFileSync(path: string, encoding?: null): Buffer;
|
||||
readFileSync(path: string, encoding: BufferEncoding): string;
|
||||
readFileSync(path: string, encoding?: BufferEncoding | null): string | Buffer {
|
||||
return this.realFS.readFileSync(this.getOriginalPath(path), encoding);
|
||||
readFileSync(uri: Uri, encoding?: null): Buffer;
|
||||
readFileSync(uri: Uri, encoding: BufferEncoding): string;
|
||||
readFileSync(uri: Uri, encoding?: BufferEncoding | null): string | Buffer {
|
||||
return this.realFS.readFileSync(this.getOriginalPath(uri), encoding);
|
||||
}
|
||||
|
||||
writeFileSync(path: string, data: string | Buffer, encoding: BufferEncoding | null): void {
|
||||
writeFileSync(uri: Uri, data: string | Buffer, encoding: BufferEncoding | null): void {
|
||||
throw new Error('Operation is not allowed.');
|
||||
}
|
||||
|
||||
statSync(path: string): Stats {
|
||||
return this.realFS.statSync(this.getOriginalPath(path));
|
||||
statSync(uri: Uri): Stats {
|
||||
return this.realFS.statSync(this.getOriginalPath(uri));
|
||||
}
|
||||
|
||||
rmdirSync(path: string): void {
|
||||
rmdirSync(uri: Uri): void {
|
||||
throw new Error('Operation is not allowed.');
|
||||
}
|
||||
|
||||
unlinkSync(path: string): void {
|
||||
unlinkSync(uri: Uri): void {
|
||||
throw new Error('Operation is not allowed.');
|
||||
}
|
||||
|
||||
realpathSync(path: string): string {
|
||||
if (this._entryMap.has(path)) {
|
||||
return path;
|
||||
realpathSync(uri: Uri): Uri {
|
||||
if (this._entryMap.has(uri.key)) {
|
||||
return uri;
|
||||
}
|
||||
|
||||
return this.realFS.realpathSync(path);
|
||||
return this.realFS.realpathSync(uri);
|
||||
}
|
||||
|
||||
getModulePath(): string {
|
||||
getModulePath(): Uri {
|
||||
return this.realFS.getModulePath();
|
||||
}
|
||||
|
||||
createFileSystemWatcher(paths: string[], listener: FileWatcherEventHandler): FileWatcher {
|
||||
createFileSystemWatcher(paths: Uri[], listener: FileWatcherEventHandler): FileWatcher {
|
||||
return this.realFS.createFileSystemWatcher(paths, listener);
|
||||
}
|
||||
|
||||
createReadStream(path: string): fs.ReadStream {
|
||||
return this.realFS.createReadStream(this.getOriginalPath(path));
|
||||
createReadStream(uri: Uri): fs.ReadStream {
|
||||
return this.realFS.createReadStream(this.getOriginalPath(uri));
|
||||
}
|
||||
|
||||
createWriteStream(path: string): fs.WriteStream {
|
||||
createWriteStream(uri: Uri): fs.WriteStream {
|
||||
throw new Error('Operation is not allowed.');
|
||||
}
|
||||
|
||||
copyFileSync(src: string, dst: string): void {
|
||||
copyFileSync(src: Uri, dst: Uri): void {
|
||||
throw new Error('Operation is not allowed.');
|
||||
}
|
||||
|
||||
// Async I/O
|
||||
readFile(path: string): Promise<Buffer> {
|
||||
return this.realFS.readFile(this.getOriginalPath(path));
|
||||
readFile(uri: Uri): Promise<Buffer> {
|
||||
return this.realFS.readFile(this.getOriginalPath(uri));
|
||||
}
|
||||
|
||||
readFileText(path: string, encoding?: BufferEncoding): Promise<string> {
|
||||
return this.realFS.readFileText(this.getOriginalPath(path), encoding);
|
||||
readFileText(uri: Uri, encoding?: BufferEncoding): Promise<string> {
|
||||
return this.realFS.readFileText(this.getOriginalPath(uri), encoding);
|
||||
}
|
||||
|
||||
realCasePath(path: string): string {
|
||||
return this.realFS.realCasePath(path);
|
||||
}
|
||||
|
||||
getUri(originalPath: string): string {
|
||||
return this.realFS.getUri(originalPath);
|
||||
realCasePath(uri: Uri): Uri {
|
||||
return this.realFS.realCasePath(uri);
|
||||
}
|
||||
|
||||
// See whether the file is mapped to another location.
|
||||
isMappedFilePath(filepath: string): boolean {
|
||||
return this._entryMap.has(filepath) || this.realFS.isMappedFilePath(filepath);
|
||||
isMappedUri(fileUri: Uri): boolean {
|
||||
return this._entryMap.has(fileUri.key) || this.realFS.isMappedUri(fileUri);
|
||||
}
|
||||
|
||||
// Get original filepath if the given filepath is mapped.
|
||||
getOriginalFilePath(mappedFilePath: string) {
|
||||
return this.realFS.getOriginalFilePath(this.getOriginalPath(mappedFilePath));
|
||||
getOriginalUri(mappedFileUri: Uri) {
|
||||
return this.realFS.getOriginalUri(this.getOriginalPath(mappedFileUri));
|
||||
}
|
||||
|
||||
// Get mapped filepath if the given filepath is mapped.
|
||||
getMappedFilePath(originalFilepath: string) {
|
||||
const mappedFilePath = this.realFS.getMappedFilePath(originalFilepath);
|
||||
return this._reverseEntryMap.get(mappedFilePath) ?? mappedFilePath;
|
||||
getMappedUri(originalFileUri: Uri) {
|
||||
const mappedFileUri = this.realFS.getMappedUri(originalFileUri);
|
||||
return this._reverseEntryMap.get(mappedFileUri.key) ?? mappedFileUri;
|
||||
}
|
||||
|
||||
isInZip(path: string): boolean {
|
||||
return this.realFS.isInZip(path);
|
||||
isInZip(uri: Uri): boolean {
|
||||
return this.realFS.isInZip(uri);
|
||||
}
|
||||
|
||||
protected recordMovedEntry(mappedPath: string, originalPath: string, rootPath: string) {
|
||||
this._entryMap.set(mappedPath, originalPath);
|
||||
this._reverseEntryMap.set(originalPath, mappedPath);
|
||||
protected recordMovedEntry(mappedUri: Uri, originalUri: Uri, rootPath: Uri) {
|
||||
this._entryMap.set(mappedUri.key, originalUri);
|
||||
this._reverseEntryMap.set(originalUri.key, mappedUri);
|
||||
|
||||
const directory = ensureTrailingDirectorySeparator(getDirectoryPath(mappedPath));
|
||||
const folderInfo = getOrAdd(this._folderMap, directory, () => []);
|
||||
const directory = mappedUri.getDirectory();
|
||||
const folderInfo = getOrAdd(this._folderMap, directory.key, () => []);
|
||||
|
||||
const name = getFileName(mappedPath);
|
||||
const name = mappedUri.fileName;
|
||||
if (!folderInfo.some((entry) => entry.name === name)) {
|
||||
folderInfo.push({ name, isFile: true });
|
||||
}
|
||||
|
||||
// Add the directory entries for the sub paths as well. We should ignoreCase here because
|
||||
// the paths are just combining of already known paths.
|
||||
const subPathEntries = getRelativePathComponentsFromDirectory(rootPath, directory, /* ignoreCase */ false);
|
||||
for (let i = 1; i < subPathEntries.length; i++) {
|
||||
const subdir = combinePaths(rootPath, ...subPathEntries.slice(1, i + 1));
|
||||
const parent = ensureTrailingDirectorySeparator(getDirectoryPath(subdir));
|
||||
// Add the directory entries for the sub paths as well.
|
||||
const subPathEntries = rootPath.getRelativePathComponents(directory);
|
||||
for (let i = 0; i < subPathEntries.length; i++) {
|
||||
const subdir = rootPath.combinePaths(...subPathEntries.slice(0, i + 1));
|
||||
const parent = subdir.getDirectory().key;
|
||||
const dirInfo = getOrAdd(this._folderMap, parent, () => []);
|
||||
const dirName = getFileName(subdir);
|
||||
const dirName = subdir.fileName;
|
||||
if (!dirInfo.some((entry) => entry.name === dirName)) {
|
||||
dirInfo.push({ name: dirName, isFile: false });
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected getOriginalPath(mappedFilePath: string) {
|
||||
return this._entryMap.get(mappedFilePath) ?? mappedFilePath;
|
||||
protected getOriginalPath(mappedFileUri: Uri) {
|
||||
return this._entryMap.get(mappedFileUri.key) ?? mappedFileUri;
|
||||
}
|
||||
|
||||
protected isMovedEntry(path: string) {
|
||||
return this._reverseEntryMap.has(path);
|
||||
protected isMovedEntry(uri: Uri) {
|
||||
return this._reverseEntryMap.has(uri.key);
|
||||
}
|
||||
|
||||
protected clear() {
|
||||
|
@ -30,11 +30,12 @@ import { expandPathVariables } from './common/envVarUtils';
|
||||
import { FileBasedCancellationProvider } from './common/fileBasedCancellationUtils';
|
||||
import { FullAccessHost } from './common/fullAccessHost';
|
||||
import { Host } from './common/host';
|
||||
import { realCasePath, resolvePaths } from './common/pathUtils';
|
||||
import { ProgressReporter } from './common/progressReporter';
|
||||
import { RealTempFile, WorkspaceFileWatcherProvider, createFromRealFileSystem } from './common/realFileSystem';
|
||||
import { ServiceProvider } from './common/serviceProvider';
|
||||
import { createServiceProvider } from './common/serviceProviderExtensions';
|
||||
import { Uri } from './common/uri/uri';
|
||||
import { getRootUri } from './common/uri/uriUtils';
|
||||
import { LanguageServerBase, ServerSettings } from './languageServerBase';
|
||||
import { CodeActionProvider } from './languageService/codeActionProvider';
|
||||
import { PyrightFileSystem } from './pyrightFileSystem';
|
||||
@ -49,20 +50,21 @@ export class PyrightServer extends LanguageServerBase {
|
||||
// eslint-disable-next-line @typescript-eslint/no-var-requires
|
||||
const version = require('../package.json').version || '';
|
||||
|
||||
// When executed from CLI command (pyright-langserver), __rootDirectory is
|
||||
// already defined. When executed from VSCode extension, rootDirectory should
|
||||
// be __dirname.
|
||||
const rootDirectory = (global as any).__rootDirectory || __dirname;
|
||||
|
||||
const console = new ConsoleWithLogLevel(connection.console);
|
||||
const fileWatcherProvider = new WorkspaceFileWatcherProvider();
|
||||
const fileSystem = createFromRealFileSystem(console, fileWatcherProvider);
|
||||
const pyrightFs = new PyrightFileSystem(fileSystem);
|
||||
const tempFile = new RealTempFile();
|
||||
const tempFile = new RealTempFile(pyrightFs.isCaseSensitive);
|
||||
const cacheManager = new CacheManager();
|
||||
|
||||
const serviceProvider = createServiceProvider(pyrightFs, tempFile, console, cacheManager);
|
||||
const realPathRoot = realCasePath(rootDirectory, pyrightFs);
|
||||
|
||||
// When executed from CLI command (pyright-langserver), __rootDirectory is
|
||||
// already defined. When executed from VSCode extension, rootDirectory should
|
||||
// be __dirname.
|
||||
const rootDirectory: Uri =
|
||||
getRootUri(pyrightFs.isCaseSensitive) || Uri.file(__dirname, pyrightFs.isCaseSensitive);
|
||||
const realPathRoot = pyrightFs.realCasePath(rootDirectory);
|
||||
|
||||
super(
|
||||
{
|
||||
@ -98,44 +100,40 @@ export class PyrightServer extends LanguageServerBase {
|
||||
};
|
||||
|
||||
try {
|
||||
const pythonSection = await this.getConfiguration(workspace.uri, 'python');
|
||||
const pythonSection = await this.getConfiguration(workspace.rootUri, 'python');
|
||||
if (pythonSection) {
|
||||
const pythonPath = pythonSection.pythonPath;
|
||||
if (pythonPath && isString(pythonPath) && !isPythonBinary(pythonPath)) {
|
||||
serverSettings.pythonPath = resolvePaths(
|
||||
workspace.rootPath,
|
||||
expandPathVariables(workspace.rootPath, pythonPath)
|
||||
serverSettings.pythonPath = workspace.rootUri.combinePaths(
|
||||
expandPathVariables(workspace.rootUri, pythonPath)
|
||||
);
|
||||
}
|
||||
|
||||
const venvPath = pythonSection.venvPath;
|
||||
|
||||
if (venvPath && isString(venvPath)) {
|
||||
serverSettings.venvPath = resolvePaths(
|
||||
workspace.rootPath,
|
||||
expandPathVariables(workspace.rootPath, venvPath)
|
||||
serverSettings.venvPath = workspace.rootUri.combinePaths(
|
||||
expandPathVariables(workspace.rootUri, venvPath)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
const pythonAnalysisSection = await this.getConfiguration(workspace.uri, 'python.analysis');
|
||||
const pythonAnalysisSection = await this.getConfiguration(workspace.rootUri, 'python.analysis');
|
||||
if (pythonAnalysisSection) {
|
||||
const typeshedPaths = pythonAnalysisSection.typeshedPaths;
|
||||
if (typeshedPaths && Array.isArray(typeshedPaths) && typeshedPaths.length > 0) {
|
||||
const typeshedPath = typeshedPaths[0];
|
||||
if (typeshedPath && isString(typeshedPath)) {
|
||||
serverSettings.typeshedPath = resolvePaths(
|
||||
workspace.rootPath,
|
||||
expandPathVariables(workspace.rootPath, typeshedPath)
|
||||
serverSettings.typeshedPath = workspace.rootUri.combinePaths(
|
||||
expandPathVariables(workspace.rootUri, typeshedPath)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
const stubPath = pythonAnalysisSection.stubPath;
|
||||
if (stubPath && isString(stubPath)) {
|
||||
serverSettings.stubPath = resolvePaths(
|
||||
workspace.rootPath,
|
||||
expandPathVariables(workspace.rootPath, stubPath)
|
||||
serverSettings.stubPath = workspace.rootUri.combinePaths(
|
||||
expandPathVariables(workspace.rootUri, stubPath)
|
||||
);
|
||||
}
|
||||
|
||||
@ -167,7 +165,7 @@ export class PyrightServer extends LanguageServerBase {
|
||||
if (extraPaths && Array.isArray(extraPaths) && extraPaths.length > 0) {
|
||||
serverSettings.extraPaths = extraPaths
|
||||
.filter((p) => p && isString(p))
|
||||
.map((p) => resolvePaths(workspace.rootPath, expandPathVariables(workspace.rootPath, p)));
|
||||
.map((p) => workspace.rootUri.combinePaths(expandPathVariables(workspace.rootUri, p)));
|
||||
}
|
||||
|
||||
serverSettings.includeFileSpecs = this._getStringValues(pythonAnalysisSection.include);
|
||||
@ -196,7 +194,7 @@ export class PyrightServer extends LanguageServerBase {
|
||||
serverSettings.autoSearchPaths = true;
|
||||
}
|
||||
|
||||
const pyrightSection = await this.getConfiguration(workspace.uri, 'pyright');
|
||||
const pyrightSection = await this.getConfiguration(workspace.rootUri, 'pyright');
|
||||
if (pyrightSection) {
|
||||
if (pyrightSection.openFilesOnly !== undefined) {
|
||||
serverSettings.openFilesOnly = !!pyrightSection.openFilesOnly;
|
||||
@ -227,11 +225,11 @@ export class PyrightServer extends LanguageServerBase {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
return new BackgroundAnalysis(this.console);
|
||||
return new BackgroundAnalysis(this.serverOptions.serviceProvider);
|
||||
}
|
||||
|
||||
protected override createHost() {
|
||||
return new FullAccessHost(this.fs);
|
||||
return new FullAccessHost(this.serverOptions.serviceProvider);
|
||||
}
|
||||
|
||||
protected override createImportResolver(
|
||||
@ -262,15 +260,9 @@ export class PyrightServer extends LanguageServerBase {
|
||||
): Promise<(Command | CodeAction)[] | undefined | null> {
|
||||
this.recordUserInteractionTime();
|
||||
|
||||
const filePath = this.uriParser.decodeTextDocumentUri(params.textDocument.uri);
|
||||
const workspace = await this.getWorkspaceForFile(filePath);
|
||||
return CodeActionProvider.getCodeActionsForPosition(
|
||||
workspace,
|
||||
filePath,
|
||||
params.range,
|
||||
params.context.only,
|
||||
token
|
||||
);
|
||||
const uri = Uri.parse(params.textDocument.uri, this.serverOptions.serviceProvider.fs().isCaseSensitive);
|
||||
const workspace = await this.getWorkspaceForFile(uri);
|
||||
return CodeActionProvider.getCodeActionsForPosition(workspace, uri, params.range, params.context.only, token);
|
||||
}
|
||||
|
||||
protected createProgressReporter(): ProgressReporter {
|
||||
|
@ -17,13 +17,14 @@ import { ConfigOptions } from '../common/configOptions';
|
||||
import { NullConsole } from '../common/console';
|
||||
import { normalizeSlashes } from '../common/pathUtils';
|
||||
import { convertOffsetsToRange, convertOffsetToPosition } from '../common/positionUtils';
|
||||
import { ServiceProvider } from '../common/serviceProvider';
|
||||
import { Uri } from '../common/uri/uri';
|
||||
import { CompletionProvider } from '../languageService/completionProvider';
|
||||
import { parseTestData } from './harness/fourslash/fourSlashParser';
|
||||
import { TestAccessHost } from './harness/testAccessHost';
|
||||
import * as host from './harness/testHost';
|
||||
import { createFromFileSystem, distlibFolder, libFolder } from './harness/vfs/factory';
|
||||
import * as vfs from './harness/vfs/filesystem';
|
||||
import { CompletionProvider } from '../languageService/completionProvider';
|
||||
import { ServiceProvider } from '../common/serviceProvider';
|
||||
|
||||
test('check chained files', () => {
|
||||
const code = `
|
||||
@ -40,16 +41,17 @@ test('check chained files', () => {
|
||||
//// [|foo/*marker*/|]
|
||||
`;
|
||||
|
||||
const basePath = normalizeSlashes('/');
|
||||
const basePath = Uri.file(normalizeSlashes('/'));
|
||||
const { data, service } = createServiceWithChainedSourceFiles(basePath, code);
|
||||
|
||||
const marker = data.markerPositions.get('marker')!;
|
||||
const markerUri = Uri.file(marker.fileName);
|
||||
|
||||
const parseResult = service.getParseResult(marker.fileName)!;
|
||||
const parseResult = service.getParseResult(markerUri)!;
|
||||
const result = new CompletionProvider(
|
||||
service.test_program,
|
||||
basePath,
|
||||
marker.fileName,
|
||||
markerUri,
|
||||
convertOffsetToPosition(marker.position, parseResult.tokenizerOutput.lines),
|
||||
{
|
||||
format: MarkupKind.Markdown,
|
||||
@ -80,15 +82,16 @@ test('modify chained files', () => {
|
||||
//// [|foo/*marker*/|]
|
||||
`;
|
||||
|
||||
const basePath = normalizeSlashes('/');
|
||||
const basePath = Uri.file(normalizeSlashes('/'));
|
||||
const { data, service } = createServiceWithChainedSourceFiles(basePath, code);
|
||||
|
||||
// Make sure files are all realized.
|
||||
const marker = data.markerPositions.get('marker')!;
|
||||
const parseResult = service.getParseResult(marker.fileName)!;
|
||||
const markerUri = Uri.file(marker.fileName);
|
||||
const parseResult = service.getParseResult(markerUri)!;
|
||||
|
||||
// Close file in the middle of the chain
|
||||
service.setFileClosed(data.markerPositions.get('delete')!.fileName);
|
||||
service.setFileClosed(Uri.file(data.markerPositions.get('delete')!.fileName));
|
||||
|
||||
// Make sure we don't get suggestion from auto import but from chained files.
|
||||
service.test_program.configOptions.autoImportCompletions = false;
|
||||
@ -96,7 +99,7 @@ test('modify chained files', () => {
|
||||
const result = new CompletionProvider(
|
||||
service.test_program,
|
||||
basePath,
|
||||
marker.fileName,
|
||||
markerUri,
|
||||
convertOffsetToPosition(marker.position, parseResult.tokenizerOutput.lines),
|
||||
{
|
||||
format: MarkupKind.Markdown,
|
||||
@ -129,18 +132,19 @@ test('modify chained files', async () => {
|
||||
//// [|/*marker*/foo1()|]
|
||||
`;
|
||||
|
||||
const basePath = normalizeSlashes('/');
|
||||
const basePath = Uri.file(normalizeSlashes('/'));
|
||||
const { data, service } = createServiceWithChainedSourceFiles(basePath, code);
|
||||
|
||||
const marker = data.markerPositions.get('marker')!;
|
||||
const markerUri = Uri.file(marker.fileName);
|
||||
const range = data.ranges.find((r) => r.marker === marker)!;
|
||||
|
||||
const parseResults = service.getParseResult(marker.fileName)!;
|
||||
const parseResults = service.getParseResult(markerUri)!;
|
||||
analyze(service.test_program);
|
||||
|
||||
// Initially, there should be no error.
|
||||
const initialDiags = await service.getDiagnosticsForRange(
|
||||
marker.fileName,
|
||||
markerUri,
|
||||
convertOffsetsToRange(range.pos, range.end, parseResults.tokenizerOutput.lines),
|
||||
CancellationToken.None
|
||||
);
|
||||
@ -148,11 +152,11 @@ test('modify chained files', async () => {
|
||||
assert.strictEqual(initialDiags.length, 0);
|
||||
|
||||
// Change test1 content
|
||||
service.updateOpenFileContents(data.markerPositions.get('changed')!.fileName, 2, 'def foo5(): pass');
|
||||
service.updateOpenFileContents(Uri.file(data.markerPositions.get('changed')!.fileName), 2, 'def foo5(): pass');
|
||||
analyze(service.test_program);
|
||||
|
||||
const finalDiags = await service.getDiagnosticsForRange(
|
||||
marker.fileName,
|
||||
markerUri,
|
||||
convertOffsetsToRange(range.pos, range.end, parseResults.tokenizerOutput.lines),
|
||||
CancellationToken.None
|
||||
);
|
||||
@ -178,17 +182,18 @@ test('chained files with 1000s of files', async () => {
|
||||
//// [|/*marker*/foo1()|]
|
||||
`;
|
||||
const code = generateChainedFiles(1000, lastFile);
|
||||
const basePath = normalizeSlashes('/');
|
||||
const basePath = Uri.file(normalizeSlashes('/'));
|
||||
const { data, service } = createServiceWithChainedSourceFiles(basePath, code);
|
||||
const marker = data.markerPositions.get('marker')!;
|
||||
const markerUri = Uri.file(marker.fileName);
|
||||
const range = data.ranges.find((r) => r.marker === marker)!;
|
||||
|
||||
const parseResults = service.getParseResult(marker.fileName)!;
|
||||
const parseResults = service.getParseResult(markerUri)!;
|
||||
analyze(service.test_program);
|
||||
|
||||
// There should be no error as it should find the foo1 in the first chained file.
|
||||
const initialDiags = await service.getDiagnosticsForRange(
|
||||
marker.fileName,
|
||||
markerUri,
|
||||
convertOffsetsToRange(range.pos, range.end, parseResults.tokenizerOutput.lines),
|
||||
CancellationToken.None
|
||||
);
|
||||
@ -205,16 +210,17 @@ test('imported by files', async () => {
|
||||
//// os.path.join()
|
||||
`;
|
||||
|
||||
const basePath = normalizeSlashes('/');
|
||||
const basePath = Uri.file(normalizeSlashes('/'));
|
||||
const { data, service } = createServiceWithChainedSourceFiles(basePath, code);
|
||||
analyze(service.test_program);
|
||||
|
||||
const marker = data.markerPositions.get('marker')!;
|
||||
const markerUri = Uri.file(marker.fileName);
|
||||
const range = data.ranges.find((r) => r.marker === marker)!;
|
||||
|
||||
const parseResults = service.getParseResult(marker.fileName)!;
|
||||
const parseResults = service.getParseResult(markerUri)!;
|
||||
const diagnostics = await service.getDiagnosticsForRange(
|
||||
marker.fileName,
|
||||
markerUri,
|
||||
convertOffsetsToRange(range.pos, range.end, parseResults.tokenizerOutput.lines),
|
||||
CancellationToken.None
|
||||
);
|
||||
@ -231,22 +237,24 @@ test('re ordering cells', async () => {
|
||||
//// /*bottom*/os.path.join()
|
||||
`;
|
||||
|
||||
const basePath = normalizeSlashes('/');
|
||||
const basePath = Uri.file(normalizeSlashes('/'));
|
||||
const { data, service } = createServiceWithChainedSourceFiles(basePath, code);
|
||||
analyze(service.test_program);
|
||||
|
||||
const marker = data.markerPositions.get('marker')!;
|
||||
const markerUri = Uri.file(marker.fileName);
|
||||
const range = data.ranges.find((r) => r.marker === marker)!;
|
||||
|
||||
const bottom = data.markerPositions.get('bottom')!;
|
||||
const bottomUri = Uri.file(bottom.fileName);
|
||||
|
||||
service.updateChainedFilePath(bottom.fileName, undefined);
|
||||
service.updateChainedFilePath(marker.fileName, bottom.fileName);
|
||||
service.updateChainedUri(bottomUri, undefined);
|
||||
service.updateChainedUri(markerUri, bottomUri);
|
||||
analyze(service.test_program);
|
||||
|
||||
const parseResults = service.getParseResult(marker.fileName)!;
|
||||
const parseResults = service.getParseResult(markerUri)!;
|
||||
const diagnostics = await service.getDiagnosticsForRange(
|
||||
marker.fileName,
|
||||
markerUri,
|
||||
convertOffsetsToRange(range.pos, range.end, parseResults.tokenizerOutput.lines),
|
||||
CancellationToken.None
|
||||
);
|
||||
@ -254,22 +262,23 @@ test('re ordering cells', async () => {
|
||||
assert.strictEqual(diagnostics.length, 1);
|
||||
});
|
||||
|
||||
function createServiceWithChainedSourceFiles(basePath: string, code: string) {
|
||||
const fs = createFromFileSystem(host.HOST, /*ignoreCase*/ false, { cwd: basePath });
|
||||
function createServiceWithChainedSourceFiles(basePath: Uri, code: string) {
|
||||
const fs = createFromFileSystem(host.HOST, /*ignoreCase*/ false, { cwd: basePath.getFilePath() });
|
||||
const service = new AnalyzerService('test service', new ServiceProvider(), {
|
||||
console: new NullConsole(),
|
||||
hostFactory: () => new TestAccessHost(vfs.MODULE_PATH, [libFolder, distlibFolder]),
|
||||
hostFactory: () => new TestAccessHost(Uri.file(vfs.MODULE_PATH), [libFolder, distlibFolder]),
|
||||
importResolverFactory: AnalyzerService.createImportResolver,
|
||||
configOptions: new ConfigOptions(basePath),
|
||||
fileSystem: fs,
|
||||
});
|
||||
|
||||
const data = parseTestData(basePath, code, '');
|
||||
const data = parseTestData(basePath.getFilePath(), code, '');
|
||||
|
||||
let chainedFilePath: string | undefined;
|
||||
let chainedFilePath: Uri | undefined;
|
||||
for (const file of data.files) {
|
||||
service.setFileOpened(file.fileName, 1, file.content, IPythonMode.CellDocs, chainedFilePath);
|
||||
chainedFilePath = file.fileName;
|
||||
const uri = Uri.file(file.fileName);
|
||||
service.setFileOpened(uri, 1, file.content, IPythonMode.CellDocs, chainedFilePath);
|
||||
chainedFilePath = uri;
|
||||
}
|
||||
return { data, service };
|
||||
}
|
||||
|
@ -11,6 +11,7 @@
|
||||
|
||||
import { ConfigOptions } from '../common/configOptions';
|
||||
import { PythonVersion } from '../common/pythonVersion';
|
||||
import { Uri } from '../common/uri/uri';
|
||||
import * as TestUtils from './testUtils';
|
||||
|
||||
test('BadToken1', () => {
|
||||
@ -34,7 +35,7 @@ test('CircularBaseClass', () => {
|
||||
});
|
||||
|
||||
test('Private1', () => {
|
||||
const configOptions = new ConfigOptions('.');
|
||||
const configOptions = new ConfigOptions(Uri.empty());
|
||||
|
||||
// By default, optional diagnostics are ignored.
|
||||
let analysisResults = TestUtils.typeAnalyzeSampleFiles(['private1.py'], configOptions);
|
||||
@ -47,7 +48,7 @@ test('Private1', () => {
|
||||
});
|
||||
|
||||
test('Constant1', () => {
|
||||
const configOptions = new ConfigOptions('.');
|
||||
const configOptions = new ConfigOptions(Uri.empty());
|
||||
|
||||
// By default, optional diagnostics are ignored.
|
||||
let analysisResults = TestUtils.typeAnalyzeSampleFiles(['constant1.py'], configOptions);
|
||||
@ -168,7 +169,7 @@ test('With3', () => {
|
||||
});
|
||||
|
||||
test('With4', () => {
|
||||
const configOptions = new ConfigOptions('.');
|
||||
const configOptions = new ConfigOptions(Uri.empty());
|
||||
|
||||
configOptions.defaultPythonVersion = PythonVersion.V3_8;
|
||||
const analysisResults1 = TestUtils.typeAnalyzeSampleFiles(['with4.py'], configOptions);
|
||||
@ -216,7 +217,7 @@ test('Mro4', () => {
|
||||
});
|
||||
|
||||
test('DefaultInitializer1', () => {
|
||||
const configOptions = new ConfigOptions('.');
|
||||
const configOptions = new ConfigOptions(Uri.empty());
|
||||
|
||||
// By default, the reportCallInDefaultInitializer is disabled.
|
||||
let analysisResults = TestUtils.typeAnalyzeSampleFiles(['defaultInitializer1.py'], configOptions);
|
||||
@ -229,7 +230,7 @@ test('DefaultInitializer1', () => {
|
||||
});
|
||||
|
||||
test('UnnecessaryIsInstance1', () => {
|
||||
const configOptions = new ConfigOptions('.');
|
||||
const configOptions = new ConfigOptions(Uri.empty());
|
||||
|
||||
let analysisResults = TestUtils.typeAnalyzeSampleFiles(['unnecessaryIsInstance1.py'], configOptions);
|
||||
TestUtils.validateResults(analysisResults, 1);
|
||||
@ -241,7 +242,7 @@ test('UnnecessaryIsInstance1', () => {
|
||||
});
|
||||
|
||||
test('UnnecessaryIsSubclass1', () => {
|
||||
const configOptions = new ConfigOptions('.');
|
||||
const configOptions = new ConfigOptions(Uri.empty());
|
||||
|
||||
let analysisResults = TestUtils.typeAnalyzeSampleFiles(['unnecessaryIsSubclass1.py'], configOptions);
|
||||
TestUtils.validateResults(analysisResults, 0);
|
||||
@ -253,7 +254,7 @@ test('UnnecessaryIsSubclass1', () => {
|
||||
});
|
||||
|
||||
test('UnnecessaryCast1', () => {
|
||||
const configOptions = new ConfigOptions('.');
|
||||
const configOptions = new ConfigOptions(Uri.empty());
|
||||
|
||||
let analysisResults = TestUtils.typeAnalyzeSampleFiles(['unnecessaryCast1.py'], configOptions);
|
||||
TestUtils.validateResults(analysisResults, 0);
|
||||
@ -265,7 +266,7 @@ test('UnnecessaryCast1', () => {
|
||||
});
|
||||
|
||||
test('UnnecessaryContains1', () => {
|
||||
const configOptions = new ConfigOptions('.');
|
||||
const configOptions = new ConfigOptions(Uri.empty());
|
||||
|
||||
let analysisResults = TestUtils.typeAnalyzeSampleFiles(['unnecessaryContains1.py'], configOptions);
|
||||
TestUtils.validateResults(analysisResults, 0);
|
||||
@ -277,7 +278,7 @@ test('UnnecessaryContains1', () => {
|
||||
});
|
||||
|
||||
test('TypeIgnore1', () => {
|
||||
const configOptions = new ConfigOptions('.');
|
||||
const configOptions = new ConfigOptions(Uri.empty());
|
||||
|
||||
let analysisResults = TestUtils.typeAnalyzeSampleFiles(['typeIgnore1.py'], configOptions);
|
||||
TestUtils.validateResults(analysisResults, 0);
|
||||
@ -289,7 +290,7 @@ test('TypeIgnore1', () => {
|
||||
});
|
||||
|
||||
test('TypeIgnore2', () => {
|
||||
const configOptions = new ConfigOptions('.');
|
||||
const configOptions = new ConfigOptions(Uri.empty());
|
||||
|
||||
let analysisResults = TestUtils.typeAnalyzeSampleFiles(['typeIgnore2.py'], configOptions);
|
||||
TestUtils.validateResults(analysisResults, 0);
|
||||
@ -301,7 +302,7 @@ test('TypeIgnore2', () => {
|
||||
});
|
||||
|
||||
test('TypeIgnore3', () => {
|
||||
const configOptions = new ConfigOptions('.');
|
||||
const configOptions = new ConfigOptions(Uri.empty());
|
||||
|
||||
let analysisResults = TestUtils.typeAnalyzeSampleFiles(['typeIgnore3.py'], configOptions);
|
||||
TestUtils.validateResults(analysisResults, 0);
|
||||
@ -313,7 +314,7 @@ test('TypeIgnore3', () => {
|
||||
});
|
||||
|
||||
test('TypeIgnore4', () => {
|
||||
const configOptions = new ConfigOptions('.');
|
||||
const configOptions = new ConfigOptions(Uri.empty());
|
||||
|
||||
let analysisResults = TestUtils.typeAnalyzeSampleFiles(['typeIgnore4.py'], configOptions);
|
||||
TestUtils.validateResults(analysisResults, 0);
|
||||
@ -324,7 +325,7 @@ test('TypeIgnore4', () => {
|
||||
});
|
||||
|
||||
test('TypeIgnore5', () => {
|
||||
const configOptions = new ConfigOptions('.');
|
||||
const configOptions = new ConfigOptions(Uri.empty());
|
||||
|
||||
let analysisResults = TestUtils.typeAnalyzeSampleFiles(['typeIgnore5.py'], configOptions);
|
||||
TestUtils.validateResults(analysisResults, 0);
|
||||
@ -335,14 +336,14 @@ test('TypeIgnore5', () => {
|
||||
});
|
||||
|
||||
test('PyrightIgnore1', () => {
|
||||
const configOptions = new ConfigOptions('.');
|
||||
const configOptions = new ConfigOptions(Uri.empty());
|
||||
|
||||
const analysisResults = TestUtils.typeAnalyzeSampleFiles(['pyrightIgnore1.py'], configOptions);
|
||||
TestUtils.validateResults(analysisResults, 1);
|
||||
});
|
||||
|
||||
test('PyrightIgnore2', () => {
|
||||
const configOptions = new ConfigOptions('.');
|
||||
const configOptions = new ConfigOptions(Uri.empty());
|
||||
|
||||
let analysisResults = TestUtils.typeAnalyzeSampleFiles(['pyrightIgnore2.py'], configOptions);
|
||||
TestUtils.validateResults(analysisResults, 2);
|
||||
@ -353,14 +354,14 @@ test('PyrightIgnore2', () => {
|
||||
});
|
||||
|
||||
test('PyrightComment1', () => {
|
||||
const configOptions = new ConfigOptions('.');
|
||||
const configOptions = new ConfigOptions(Uri.empty());
|
||||
|
||||
const analysisResults = TestUtils.typeAnalyzeSampleFiles(['pyrightComment1.py'], configOptions);
|
||||
TestUtils.validateResults(analysisResults, 9);
|
||||
});
|
||||
|
||||
test('DuplicateImports1', () => {
|
||||
const configOptions = new ConfigOptions('.');
|
||||
const configOptions = new ConfigOptions(Uri.empty());
|
||||
|
||||
// By default, optional diagnostics are ignored.
|
||||
let analysisResults = TestUtils.typeAnalyzeSampleFiles(['duplicateImports1.py'], configOptions);
|
||||
@ -373,7 +374,7 @@ test('DuplicateImports1', () => {
|
||||
});
|
||||
|
||||
test('ParamNames1', () => {
|
||||
const configOptions = new ConfigOptions('.');
|
||||
const configOptions = new ConfigOptions(Uri.empty());
|
||||
|
||||
let analysisResults = TestUtils.typeAnalyzeSampleFiles(['paramNames1.py'], configOptions);
|
||||
TestUtils.validateResults(analysisResults, 0, 7);
|
||||
@ -423,7 +424,7 @@ test('DuplicateDeclaration2', () => {
|
||||
});
|
||||
|
||||
test('Strings1', () => {
|
||||
const configOptions = new ConfigOptions('.');
|
||||
const configOptions = new ConfigOptions(Uri.empty());
|
||||
const analysisResults1 = TestUtils.typeAnalyzeSampleFiles(['strings1.py'], configOptions);
|
||||
TestUtils.validateResults(analysisResults1, 0);
|
||||
|
||||
@ -433,7 +434,7 @@ test('Strings1', () => {
|
||||
});
|
||||
|
||||
test('UnusedExpression1', () => {
|
||||
const configOptions = new ConfigOptions('.');
|
||||
const configOptions = new ConfigOptions(Uri.empty());
|
||||
|
||||
// By default, this is a warning.
|
||||
let analysisResults = TestUtils.typeAnalyzeSampleFiles(['unusedExpression1.py'], configOptions);
|
||||
@ -451,7 +452,7 @@ test('UnusedExpression1', () => {
|
||||
});
|
||||
|
||||
test('UnusedImport1', () => {
|
||||
const configOptions = new ConfigOptions('.');
|
||||
const configOptions = new ConfigOptions(Uri.empty());
|
||||
|
||||
// Enabled it
|
||||
configOptions.diagnosticRuleSet.reportUnusedImport = 'warning';
|
||||
@ -470,7 +471,7 @@ test('UnusedImport1', () => {
|
||||
});
|
||||
|
||||
test('UninitializedVariable1', () => {
|
||||
const configOptions = new ConfigOptions('.');
|
||||
const configOptions = new ConfigOptions(Uri.empty());
|
||||
|
||||
// By default, this is off.
|
||||
let analysisResults = TestUtils.typeAnalyzeSampleFiles(['uninitializedVariable1.py'], configOptions);
|
||||
@ -483,7 +484,7 @@ test('UninitializedVariable1', () => {
|
||||
});
|
||||
|
||||
test('UninitializedVariable2', () => {
|
||||
const configOptions = new ConfigOptions('.');
|
||||
const configOptions = new ConfigOptions(Uri.empty());
|
||||
|
||||
// By default, this is off.
|
||||
let analysisResults = TestUtils.typeAnalyzeSampleFiles(['uninitializedVariable2.py'], configOptions);
|
||||
@ -502,7 +503,7 @@ test('RegionComments1', () => {
|
||||
});
|
||||
|
||||
test('Deprecated1', () => {
|
||||
const configOptions = new ConfigOptions('.');
|
||||
const configOptions = new ConfigOptions(Uri.empty());
|
||||
|
||||
configOptions.defaultPythonVersion = PythonVersion.V3_8;
|
||||
const analysisResults1 = TestUtils.typeAnalyzeSampleFiles(['deprecated1.py'], configOptions);
|
||||
@ -548,7 +549,7 @@ test('Deprecated1', () => {
|
||||
});
|
||||
|
||||
test('Deprecated2', () => {
|
||||
const configOptions = new ConfigOptions('.');
|
||||
const configOptions = new ConfigOptions(Uri.empty());
|
||||
|
||||
const analysisResults1 = TestUtils.typeAnalyzeSampleFiles(['deprecated2.py'], configOptions);
|
||||
TestUtils.validateResults(analysisResults1, 0, 0, 0, undefined, undefined, 12);
|
||||
@ -559,7 +560,7 @@ test('Deprecated2', () => {
|
||||
});
|
||||
|
||||
test('Deprecated3', () => {
|
||||
const configOptions = new ConfigOptions('.');
|
||||
const configOptions = new ConfigOptions(Uri.empty());
|
||||
|
||||
const analysisResults1 = TestUtils.typeAnalyzeSampleFiles(['deprecated3.py'], configOptions);
|
||||
TestUtils.validateResults(analysisResults1, 0, 0, 0, undefined, undefined, 5);
|
||||
@ -570,7 +571,7 @@ test('Deprecated3', () => {
|
||||
});
|
||||
|
||||
test('Deprecated4', () => {
|
||||
const configOptions = new ConfigOptions('.');
|
||||
const configOptions = new ConfigOptions(Uri.empty());
|
||||
|
||||
const analysisResults1 = TestUtils.typeAnalyzeSampleFiles(['deprecated4.py'], configOptions);
|
||||
TestUtils.validateResults(analysisResults1, 0, 0, 0, undefined, undefined, 6);
|
||||
@ -581,7 +582,7 @@ test('Deprecated4', () => {
|
||||
});
|
||||
|
||||
test('Deprecated5', () => {
|
||||
const configOptions = new ConfigOptions('.');
|
||||
const configOptions = new ConfigOptions(Uri.empty());
|
||||
|
||||
const analysisResults1 = TestUtils.typeAnalyzeSampleFiles(['deprecated5.py'], configOptions);
|
||||
TestUtils.validateResults(analysisResults1, 0, 0, 0, undefined, undefined, 2);
|
||||
|
@ -8,6 +8,7 @@ import assert from 'assert';
|
||||
import { CancellationToken } from 'vscode-languageserver';
|
||||
import { CompletionItemKind, MarkupKind } from 'vscode-languageserver-types';
|
||||
|
||||
import { Uri } from '../common/uri/uri';
|
||||
import { CompletionOptions, CompletionProvider } from '../languageService/completionProvider';
|
||||
import { parseAndGetTestState } from './harness/fourslash/testState';
|
||||
|
||||
@ -798,6 +799,7 @@ test('completion quote trigger', async () => {
|
||||
const state = parseAndGetTestState(code).state;
|
||||
const marker = state.getMarkerByName('marker');
|
||||
const filePath = marker.fileName;
|
||||
const uri = Uri.file(filePath);
|
||||
const position = state.convertOffsetToPosition(filePath, marker.position);
|
||||
|
||||
const options: CompletionOptions = {
|
||||
@ -809,8 +811,8 @@ test('completion quote trigger', async () => {
|
||||
|
||||
const result = new CompletionProvider(
|
||||
state.program,
|
||||
state.workspace.rootPath,
|
||||
filePath,
|
||||
state.workspace.rootUri,
|
||||
uri,
|
||||
position,
|
||||
options,
|
||||
CancellationToken.None
|
||||
@ -836,6 +838,7 @@ test('completion quote trigger - middle', async () => {
|
||||
const state = parseAndGetTestState(code).state;
|
||||
const marker = state.getMarkerByName('marker');
|
||||
const filePath = marker.fileName;
|
||||
const uri = Uri.file(filePath);
|
||||
const position = state.convertOffsetToPosition(filePath, marker.position);
|
||||
|
||||
const options: CompletionOptions = {
|
||||
@ -847,8 +850,8 @@ test('completion quote trigger - middle', async () => {
|
||||
|
||||
const result = new CompletionProvider(
|
||||
state.program,
|
||||
state.workspace.rootPath,
|
||||
filePath,
|
||||
state.workspace.rootUri,
|
||||
uri,
|
||||
position,
|
||||
options,
|
||||
CancellationToken.None
|
||||
@ -882,6 +885,7 @@ test('auto import sort text', async () => {
|
||||
while (state.workspace.service.test_program.analyze());
|
||||
|
||||
const filePath = marker.fileName;
|
||||
const uri = Uri.file(filePath);
|
||||
const position = state.convertOffsetToPosition(filePath, marker.position);
|
||||
|
||||
const options: CompletionOptions = {
|
||||
@ -892,8 +896,8 @@ test('auto import sort text', async () => {
|
||||
|
||||
const result = new CompletionProvider(
|
||||
state.program,
|
||||
state.workspace.rootPath,
|
||||
filePath,
|
||||
state.workspace.rootUri,
|
||||
uri,
|
||||
position,
|
||||
options,
|
||||
CancellationToken.None
|
||||
|
@ -15,10 +15,11 @@ import { CommandLineOptions } from '../common/commandLineOptions';
|
||||
import { ConfigOptions, ExecutionEnvironment } from '../common/configOptions';
|
||||
import { ConsoleInterface, NullConsole } from '../common/console';
|
||||
import { NoAccessHost } from '../common/host';
|
||||
import { combinePaths, getBaseFileName, normalizePath, normalizeSlashes, realCasePath } from '../common/pathUtils';
|
||||
import { combinePaths, normalizePath, normalizeSlashes } from '../common/pathUtils';
|
||||
import { PythonVersion } from '../common/pythonVersion';
|
||||
import { createFromRealFileSystem } from '../common/realFileSystem';
|
||||
import { createServiceProvider } from '../common/serviceProviderExtensions';
|
||||
import { Uri } from '../common/uri/uri';
|
||||
import { TestAccessHost } from './harness/testAccessHost';
|
||||
import { TestFileSystem } from './harness/vfs/filesystem';
|
||||
|
||||
@ -41,8 +42,8 @@ test('FindFilesWithConfigFile', () => {
|
||||
// The config file specifies a single file spec (a directory).
|
||||
assert.strictEqual(configOptions.include.length, 1, `failed creating options from ${cwd}`);
|
||||
assert.strictEqual(
|
||||
normalizeSlashes(configOptions.projectRoot),
|
||||
realCasePath(combinePaths(cwd, commandLineOptions.configFilePath), service.fs)
|
||||
configOptions.projectRoot.key,
|
||||
service.fs.realCasePath(Uri.file(combinePaths(cwd, commandLineOptions.configFilePath))).key
|
||||
);
|
||||
|
||||
const fileList = service.test_getFileNamesFromFileSpecs();
|
||||
@ -67,7 +68,7 @@ test('FindFilesVirtualEnvAutoDetectExclude', () => {
|
||||
|
||||
// There are 3 python files in the workspace, outside of myvenv
|
||||
// There is 1 python file in myvenv, which should be excluded
|
||||
const fileNames = fileList.map((p) => getBaseFileName(p)).sort();
|
||||
const fileNames = fileList.map((p) => p.fileName).sort();
|
||||
assert.deepStrictEqual(fileNames, ['sample1.py', 'sample2.py', 'sample3.py']);
|
||||
});
|
||||
|
||||
@ -85,7 +86,7 @@ test('FindFilesVirtualEnvAutoDetectInclude', () => {
|
||||
// There are 3 python files in the workspace, outside of myvenv
|
||||
// There is 1 more python file in excluded folder
|
||||
// There is 1 python file in myvenv, which should be included
|
||||
const fileNames = fileList.map((p) => getBaseFileName(p)).sort();
|
||||
const fileNames = fileList.map((p) => p.fileName).sort();
|
||||
assert.deepStrictEqual(fileNames, ['library1.py', 'sample1.py', 'sample2.py', 'sample3.py']);
|
||||
});
|
||||
|
||||
@ -132,8 +133,8 @@ test('SomeFileSpecsAreInvalid', () => {
|
||||
assert.strictEqual(configOptions.include.length, 4, `failed creating options from ${cwd}`);
|
||||
assert.strictEqual(configOptions.exclude.length, 1);
|
||||
assert.strictEqual(
|
||||
normalizeSlashes(configOptions.projectRoot),
|
||||
realCasePath(combinePaths(cwd, commandLineOptions.configFilePath), service.fs)
|
||||
configOptions.projectRoot.getFilePath(),
|
||||
service.fs.realCasePath(Uri.file(combinePaths(cwd, commandLineOptions.configFilePath))).getFilePath()
|
||||
);
|
||||
|
||||
const fileList = service.test_getFileNamesFromFileSpecs();
|
||||
@ -157,13 +158,13 @@ test('ConfigBadJson', () => {
|
||||
});
|
||||
|
||||
test('FindExecEnv1', () => {
|
||||
const cwd = normalizePath(process.cwd());
|
||||
const cwd = Uri.file(normalizePath(process.cwd()));
|
||||
const configOptions = new ConfigOptions(cwd);
|
||||
|
||||
// Build a config option with three execution environments.
|
||||
const execEnv1 = new ExecutionEnvironment(
|
||||
'python',
|
||||
'src/foo',
|
||||
cwd.combinePaths('src/foo'),
|
||||
/* defaultPythonVersion */ undefined,
|
||||
/* defaultPythonPlatform */ undefined,
|
||||
/* defaultExtraPaths */ undefined
|
||||
@ -171,28 +172,29 @@ test('FindExecEnv1', () => {
|
||||
configOptions.executionEnvironments.push(execEnv1);
|
||||
const execEnv2 = new ExecutionEnvironment(
|
||||
'python',
|
||||
'src',
|
||||
cwd.combinePaths('src'),
|
||||
/* defaultPythonVersion */ undefined,
|
||||
/* defaultPythonPlatform */ undefined,
|
||||
/* defaultExtraPaths */ undefined
|
||||
);
|
||||
configOptions.executionEnvironments.push(execEnv2);
|
||||
|
||||
const file1 = normalizeSlashes(combinePaths(cwd, 'src/foo/bar.py'));
|
||||
const file1 = cwd.combinePaths('src/foo/bar.py');
|
||||
assert.strictEqual(configOptions.findExecEnvironment(file1), execEnv1);
|
||||
const file2 = normalizeSlashes(combinePaths(cwd, 'src/foo2/bar.py'));
|
||||
const file2 = cwd.combinePaths('src/foo2/bar.py');
|
||||
assert.strictEqual(configOptions.findExecEnvironment(file2), execEnv2);
|
||||
|
||||
// If none of the execution environments matched, we should get
|
||||
// a default environment with the root equal to that of the config.
|
||||
const file4 = '/nothing/bar.py';
|
||||
const file4 = Uri.file('/nothing/bar.py');
|
||||
const defaultExecEnv = configOptions.findExecEnvironment(file4);
|
||||
assert(defaultExecEnv.root);
|
||||
assert.strictEqual(normalizeSlashes(defaultExecEnv.root), normalizeSlashes(configOptions.projectRoot));
|
||||
const rootFilePath = Uri.isUri(defaultExecEnv.root) ? defaultExecEnv.root.getFilePath() : defaultExecEnv.root;
|
||||
assert.strictEqual(normalizeSlashes(rootFilePath), normalizeSlashes(configOptions.projectRoot.getFilePath()));
|
||||
});
|
||||
|
||||
test('PythonPlatform', () => {
|
||||
const cwd = normalizePath(process.cwd());
|
||||
const cwd = Uri.file(normalizePath(process.cwd()));
|
||||
|
||||
const configOptions = new ConfigOptions(cwd);
|
||||
|
||||
@ -216,16 +218,16 @@ test('PythonPlatform', () => {
|
||||
});
|
||||
|
||||
test('AutoSearchPathsOn', () => {
|
||||
const cwd = normalizePath(combinePaths(process.cwd(), 'src/tests/samples/project_src'));
|
||||
const cwd = Uri.file(normalizePath(combinePaths(process.cwd(), 'src/tests/samples/project_src')));
|
||||
const nullConsole = new NullConsole();
|
||||
const service = createAnalyzer(nullConsole);
|
||||
const commandLineOptions = new CommandLineOptions(cwd, /* fromVsCodeExtension */ false);
|
||||
const commandLineOptions = new CommandLineOptions(cwd.getFilePath(), /* fromVsCodeExtension */ false);
|
||||
commandLineOptions.autoSearchPaths = true;
|
||||
service.setOptions(commandLineOptions);
|
||||
|
||||
const configOptions = service.test_getConfigOptions(commandLineOptions);
|
||||
|
||||
const expectedExtraPaths = [realCasePath(combinePaths(cwd, 'src'), service.fs)];
|
||||
const expectedExtraPaths = [service.fs.realCasePath(cwd.combinePaths('src'))];
|
||||
assert.deepStrictEqual(configOptions.defaultExtraPaths, expectedExtraPaths);
|
||||
});
|
||||
|
||||
@ -274,19 +276,22 @@ test('AutoSearchPathsOnWithConfigExecEnv', () => {
|
||||
});
|
||||
|
||||
test('AutoSearchPathsOnAndExtraPaths', () => {
|
||||
const cwd = normalizePath(combinePaths(process.cwd(), 'src/tests/samples/project_src_with_config_no_extra_paths'));
|
||||
const nullConsole = new NullConsole();
|
||||
const service = createAnalyzer(nullConsole);
|
||||
const commandLineOptions = new CommandLineOptions(cwd, /* fromVsCodeExtension */ false);
|
||||
const cwd = Uri.file(
|
||||
normalizePath(combinePaths(process.cwd(), 'src/tests/samples/project_src_with_config_no_extra_paths')),
|
||||
service.fs.isCaseSensitive
|
||||
);
|
||||
const commandLineOptions = new CommandLineOptions(cwd.getFilePath(), /* fromVsCodeExtension */ false);
|
||||
commandLineOptions.autoSearchPaths = true;
|
||||
commandLineOptions.extraPaths = ['src/_vendored'];
|
||||
service.setOptions(commandLineOptions);
|
||||
|
||||
const configOptions = service.test_getConfigOptions(commandLineOptions);
|
||||
|
||||
const expectedExtraPaths: string[] = [
|
||||
realCasePath(combinePaths(cwd, 'src'), service.fs),
|
||||
realCasePath(combinePaths(cwd, 'src', '_vendored'), service.fs),
|
||||
const expectedExtraPaths: Uri[] = [
|
||||
service.fs.realCasePath(cwd.combinePaths('src')),
|
||||
service.fs.realCasePath(cwd.combinePaths('src', '_vendored')),
|
||||
];
|
||||
|
||||
assert.deepStrictEqual(configOptions.defaultExtraPaths, expectedExtraPaths);
|
||||
@ -314,11 +319,11 @@ test('FindFilesInMemoryOnly', () => {
|
||||
service.setOptions(commandLineOptions);
|
||||
|
||||
// Open a file that is not backed by the file system.
|
||||
const untitled = 'untitled:Untitled-1.py';
|
||||
const untitled = Uri.parse('untitled:Untitled-1.py', true);
|
||||
service.setFileOpened(untitled, 1, '# empty');
|
||||
|
||||
const fileList = service.test_getFileNamesFromFileSpecs();
|
||||
assert(fileList.filter((f) => f === untitled));
|
||||
assert(fileList.filter((f) => f.equals(untitled)));
|
||||
});
|
||||
|
||||
test('verify config fileSpecs after cloning', () => {
|
||||
@ -327,7 +332,7 @@ test('verify config fileSpecs after cloning', () => {
|
||||
ignore: ['**/node_modules/**'],
|
||||
};
|
||||
|
||||
const config = new ConfigOptions(process.cwd());
|
||||
const config = new ConfigOptions(Uri.file(process.cwd()));
|
||||
const sp = createServiceProvider(fs, new NullConsole());
|
||||
config.initializeFromJson(configFile, undefined, sp, new TestAccessHost());
|
||||
const cloned = createConfigOptionsFrom(config);
|
||||
|
@ -6,6 +6,7 @@
|
||||
* Tests documentSymbolCollector
|
||||
*/
|
||||
|
||||
import { Uri } from '../common/uri/uri';
|
||||
import { parseAndGetTestState } from './harness/fourslash/testState';
|
||||
import { verifyReferencesAtPosition } from './testStateUtils';
|
||||
|
||||
@ -225,7 +226,7 @@ test('use localName import alias', () => {
|
||||
const references = state
|
||||
.getRangesByText()
|
||||
.get('tools')!
|
||||
.map((r) => ({ path: r.fileName, range: state.convertPositionRange(r) }));
|
||||
.map((r) => ({ uri: Uri.file(r.fileName), range: state.convertPositionRange(r) }));
|
||||
|
||||
state.verifyFindAllReferences({
|
||||
marker1: { references },
|
||||
@ -288,7 +289,7 @@ test('use localName import module', () => {
|
||||
const references = state
|
||||
.getRangesByText()
|
||||
.get('tools')!
|
||||
.map((r) => ({ path: r.fileName, range: state.convertPositionRange(r) }));
|
||||
.map((r) => ({ uri: Uri.file(r.fileName), range: state.convertPositionRange(r) }));
|
||||
|
||||
state.verifyFindAllReferences({
|
||||
marker1: { references },
|
||||
|
@ -9,6 +9,7 @@
|
||||
import assert from 'assert';
|
||||
|
||||
import { combinePaths, normalizeSlashes } from '../common/pathUtils';
|
||||
import { Uri } from '../common/uri/uri';
|
||||
import * as host from './harness/testHost';
|
||||
import * as factory from './harness/vfs/factory';
|
||||
import * as vfs from './harness/vfs/filesystem';
|
||||
@ -20,54 +21,55 @@ test('CreateVFS', () => {
|
||||
});
|
||||
|
||||
test('Folders', () => {
|
||||
const cwd = normalizeSlashes('/');
|
||||
const fs = new vfs.TestFileSystem(/*ignoreCase*/ true, { cwd });
|
||||
const cwd = Uri.file(normalizeSlashes('/'));
|
||||
const fs = new vfs.TestFileSystem(/*ignoreCase*/ true, { cwd: cwd.getFilePath() });
|
||||
|
||||
// no such dir exist
|
||||
assert.throws(() => {
|
||||
fs.chdir('a');
|
||||
fs.chdir(cwd.combinePaths('a'));
|
||||
});
|
||||
|
||||
fs.mkdirSync('a');
|
||||
fs.chdir('a');
|
||||
fs.mkdirSync(cwd.combinePaths('a'));
|
||||
fs.chdir(cwd.combinePaths('a'));
|
||||
assert.equal(fs.cwd(), normalizeSlashes('/a'));
|
||||
|
||||
fs.chdir('..');
|
||||
fs.rmdirSync('a');
|
||||
fs.chdir(cwd.combinePaths('..'));
|
||||
fs.rmdirSync(cwd.combinePaths('a'));
|
||||
|
||||
// no such dir exist
|
||||
assert.throws(() => {
|
||||
fs.chdir('a');
|
||||
fs.chdir(cwd.combinePaths('a'));
|
||||
});
|
||||
});
|
||||
|
||||
test('Folders Recursive', () => {
|
||||
const cwd = normalizeSlashes('/');
|
||||
const fs = new vfs.TestFileSystem(/*ignoreCase*/ true, { cwd });
|
||||
const cwd = Uri.file(normalizeSlashes('/'));
|
||||
const fs = new vfs.TestFileSystem(/*ignoreCase*/ true, { cwd: cwd.getFilePath() });
|
||||
|
||||
// no such dir exist
|
||||
assert.throws(() => {
|
||||
fs.chdir('a');
|
||||
fs.chdir(cwd.combinePaths('a'));
|
||||
});
|
||||
|
||||
const path = combinePaths('/', 'a', 'b', 'c');
|
||||
const path = cwd.combinePaths('a', 'b', 'c');
|
||||
fs.mkdirSync(path, { recursive: true });
|
||||
|
||||
assert(fs.existsSync(path));
|
||||
});
|
||||
|
||||
test('Files', () => {
|
||||
const cwd = normalizeSlashes('/');
|
||||
const fs = new vfs.TestFileSystem(/*ignoreCase*/ true, { cwd });
|
||||
const cwd = Uri.file(normalizeSlashes('/'));
|
||||
const fs = new vfs.TestFileSystem(/*ignoreCase*/ true, { cwd: cwd.getFilePath() });
|
||||
|
||||
fs.writeFileSync('1.txt', 'hello', 'utf8');
|
||||
const buffer1 = fs.readFileSync('1.txt');
|
||||
const uri = cwd.combinePaths('1.txt');
|
||||
fs.writeFileSync(uri, 'hello', 'utf8');
|
||||
const buffer1 = fs.readFileSync(uri);
|
||||
assert.equal(buffer1.toString(), 'hello');
|
||||
|
||||
const p = normalizeSlashes('a/b/c');
|
||||
fs.mkdirpSync(p);
|
||||
const p = cwd.combinePaths('a/b/c');
|
||||
fs.mkdirpSync(p.getFilePath());
|
||||
|
||||
const f = combinePaths(p, '2.txt');
|
||||
const f = p.combinePaths('2.txt');
|
||||
fs.writeFileSync(f, 'hi');
|
||||
|
||||
const str = fs.readFileSync(f, 'utf8');
|
||||
@ -90,11 +92,11 @@ test('CreateRich', () => {
|
||||
// files + directory + root
|
||||
assert.equal(entries.length, 10);
|
||||
|
||||
assert.equal(fs.readFileSync(normalizeSlashes('/a/b/c/1.txt'), 'ascii'), 'hello1');
|
||||
assert.equal(fs.readFileSync(normalizeSlashes('/a/b/2.txt'), 'utf8'), 'hello2');
|
||||
assert.equal(fs.readFileSync(normalizeSlashes('/a/3.txt'), 'utf-8'), 'hello3');
|
||||
assert.equal(fs.readFileSync(normalizeSlashes('/4.txt'), 'utf16le'), 'hello4');
|
||||
assert.equal(fs.readFileSync(normalizeSlashes('/a/c/5.txt'), 'ucs2'), 'hello5');
|
||||
assert.equal(fs.readFileSync(Uri.file(normalizeSlashes('/a/b/c/1.txt')), 'ascii'), 'hello1');
|
||||
assert.equal(fs.readFileSync(Uri.file(normalizeSlashes('/a/b/2.txt')), 'utf8'), 'hello2');
|
||||
assert.equal(fs.readFileSync(Uri.file(normalizeSlashes('/a/3.txt')), 'utf-8'), 'hello3');
|
||||
assert.equal(fs.readFileSync(Uri.file(normalizeSlashes('/4.txt')), 'utf16le'), 'hello4');
|
||||
assert.equal(fs.readFileSync(Uri.file(normalizeSlashes('/a/c/5.txt')), 'ucs2'), 'hello5');
|
||||
});
|
||||
|
||||
test('Shadow', () => {
|
||||
@ -124,19 +126,19 @@ test('Shadow', () => {
|
||||
});
|
||||
|
||||
test('Diffing', () => {
|
||||
const cwd = normalizeSlashes('/');
|
||||
const fs = new vfs.TestFileSystem(/*ignoreCase*/ true, { cwd });
|
||||
const cwd = Uri.file(normalizeSlashes('/'));
|
||||
const fs = new vfs.TestFileSystem(/*ignoreCase*/ true, { cwd: cwd.getFilePath() });
|
||||
|
||||
// first snapshot
|
||||
fs.snapshot();
|
||||
fs.writeFileSync('test1.txt', 'hello1');
|
||||
fs.writeFileSync(cwd.combinePaths('test1.txt'), 'hello1');
|
||||
|
||||
// compared with original
|
||||
assert.equal(countFile(fs.diff()!), 1);
|
||||
|
||||
// second snapshot
|
||||
fs.snapshot();
|
||||
fs.writeFileSync('test2.txt', 'hello2');
|
||||
fs.writeFileSync(cwd.combinePaths('test2.txt'), 'hello2');
|
||||
|
||||
// compared with first snapshot
|
||||
assert.equal(countFile(fs.diff()!), 1);
|
||||
@ -148,11 +150,11 @@ test('Diffing', () => {
|
||||
const s = fs.shadowRoot!.shadow();
|
||||
|
||||
// "test2.txt" only exist in first snapshot
|
||||
assert(!s.existsSync('test2.txt'));
|
||||
assert(!s.existsSync(cwd.combinePaths('test2.txt')));
|
||||
|
||||
// create parallel universe where it has another version of test2.txt with different content
|
||||
// compared to second snapshot which forked from same first snapshot
|
||||
s.writeFileSync('test2.txt', 'hello3');
|
||||
s.writeFileSync(cwd.combinePaths('test2.txt'), 'hello3');
|
||||
|
||||
// diff between non direct snapshots
|
||||
// diff gives test2.txt even though it exist in both snapshot
|
||||
@ -170,16 +172,16 @@ test('createFromFileSystem1', () => {
|
||||
});
|
||||
|
||||
// check existing typeshed folder on virtual path inherited from base snapshot from physical file system
|
||||
const entries = fs.readdirSync(factory.typeshedFolder);
|
||||
const entries = fs.readdirSync(Uri.file(factory.typeshedFolder));
|
||||
assert(entries.length > 0);
|
||||
|
||||
// confirm file
|
||||
assert.equal(fs.readFileSync(filepath, 'utf8'), content);
|
||||
assert.equal(fs.readFileSync(Uri.file(filepath), 'utf8'), content);
|
||||
});
|
||||
|
||||
test('createFromFileSystem2', () => {
|
||||
const fs = factory.createFromFileSystem(host.HOST, /* ignoreCase */ true, { cwd: factory.srcFolder });
|
||||
const entries = fs.readdirSync(factory.typeshedFolder.toUpperCase());
|
||||
const entries = fs.readdirSync(Uri.file(factory.typeshedFolder.toUpperCase()));
|
||||
assert(entries.length > 0);
|
||||
});
|
||||
|
||||
@ -190,7 +192,7 @@ test('createFromFileSystemWithCustomTypeshedPath', () => {
|
||||
meta: { [factory.typeshedFolder]: invalidpath },
|
||||
});
|
||||
|
||||
const entries = fs.readdirSync(factory.typeshedFolder);
|
||||
const entries = fs.readdirSync(Uri.file(factory.typeshedFolder));
|
||||
assert(entries.filter((e) => e.endsWith('.md')).length > 0);
|
||||
});
|
||||
|
||||
@ -200,7 +202,7 @@ test('createFromFileSystemWithMetadata', () => {
|
||||
meta: { unused: 'unused' },
|
||||
});
|
||||
|
||||
assert(fs.existsSync(factory.srcFolder));
|
||||
assert(fs.existsSync(Uri.file(factory.srcFolder)));
|
||||
});
|
||||
|
||||
function countFile(files: vfs.FileSet): number {
|
||||
|
@ -9,8 +9,9 @@
|
||||
|
||||
import assert from 'assert';
|
||||
|
||||
import { combinePaths, getBaseFileName, normalizeSlashes } from '../common/pathUtils';
|
||||
import { getBaseFileName, normalizeSlashes } from '../common/pathUtils';
|
||||
import { compareStringsCaseSensitive } from '../common/stringUtils';
|
||||
import { Uri } from '../common/uri/uri';
|
||||
import { parseTestData } from './harness/fourslash/fourSlashParser';
|
||||
import { CompilerSettings } from './harness/fourslash/fourSlashTypes';
|
||||
import * as host from './harness/testHost';
|
||||
@ -89,7 +90,7 @@ test('Library options', () => {
|
||||
|
||||
const data = parseTestData('.', code, 'test.py');
|
||||
|
||||
assert.equal(data.files[0].fileName, normalizeSlashes(combinePaths(factory.libFolder, 'file1.py')));
|
||||
assert.equal(data.files[0].fileName, factory.libFolder.combinePaths('file1.py').getFilePath());
|
||||
});
|
||||
|
||||
test('Range', () => {
|
||||
@ -222,8 +223,7 @@ test('Multiple Files', () => {
|
||||
|
||||
assert.equal(data.files.filter((f) => f.fileName === normalizeSlashes('./src/A.py'))[0].content, getContent('A'));
|
||||
assert.equal(
|
||||
data.files.filter((f) => f.fileName === normalizeSlashes(combinePaths(factory.libFolder, 'src/B.py')))[0]
|
||||
.content,
|
||||
data.files.filter((f) => f.fileName === factory.libFolder.combinePaths('src/B.py').getFilePath())[0].content,
|
||||
getContent('B')
|
||||
);
|
||||
assert.equal(data.files.filter((f) => f.fileName === normalizeSlashes('./src/C.py'))[0].content, getContent('C'));
|
||||
@ -312,7 +312,10 @@ test('fourSlashWithFileSystem', () => {
|
||||
});
|
||||
|
||||
for (const file of data.files) {
|
||||
assert.equal(fs.readFileSync(file.fileName, 'utf8'), getContent(getBaseFileName(file.fileName, '.py', false)));
|
||||
assert.equal(
|
||||
fs.readFileSync(Uri.file(file.fileName), 'utf8'),
|
||||
getContent(getBaseFileName(file.fileName, '.py', false))
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
|
@ -17,13 +17,13 @@ import {
|
||||
} from '../../../common/pathUtils';
|
||||
import { distlibFolder, libFolder } from '../vfs/factory';
|
||||
import {
|
||||
fileMetadataNames,
|
||||
FourSlashData,
|
||||
FourSlashFile,
|
||||
GlobalMetadataOptionNames,
|
||||
Marker,
|
||||
MetadataOptionNames,
|
||||
Range,
|
||||
fileMetadataNames,
|
||||
} from './fourSlashTypes';
|
||||
|
||||
/**
|
||||
@ -70,13 +70,13 @@ export function parseTestData(basePath: string, contents: string, fileName: stri
|
||||
|
||||
if (toBoolean(currentFileOptions[MetadataOptionNames.library])) {
|
||||
currentFileName = normalizePath(
|
||||
combinePaths(libFolder, getRelativePath(currentFileName, normalizedBasePath))
|
||||
combinePaths(libFolder.getFilePath(), getRelativePath(currentFileName, normalizedBasePath))
|
||||
);
|
||||
}
|
||||
|
||||
if (toBoolean(currentFileOptions[MetadataOptionNames.distLibrary])) {
|
||||
currentFileName = normalizePath(
|
||||
combinePaths(distlibFolder, getRelativePath(currentFileName, normalizedBasePath))
|
||||
combinePaths(distlibFolder.getFilePath(), getRelativePath(currentFileName, normalizedBasePath))
|
||||
);
|
||||
}
|
||||
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user