Upgrade dependencies, fix first line code actions, progress bar tweak, logging, package scanning (#743)

Rollup of:

- Upgrade dependencies, including an LSP bump for new features.
- Fix code actions that appear on the first line.
- Tweak progress bar displaying to display less often for behind-the-scenes analysis.
- Make leveled logging more consistent, configurable.
- Add package scanner for auto-importing top-level package names.
- Fix docstring fetching in relative import cases, `-stub` packages.
This commit is contained in:
Jake Bailey 2020-06-18 15:14:24 -07:00 committed by GitHub
parent f28cf16fa1
commit 5b187d19fa
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
34 changed files with 2736 additions and 1275 deletions

View File

@ -11,9 +11,9 @@
"dev": true
},
"@types/node": {
"version": "13.11.1",
"resolved": "https://registry.npmjs.org/@types/node/-/node-13.11.1.tgz",
"integrity": "sha512-eWQGP3qtxwL8FGneRrC5DwrJLGN4/dH1clNTuLfN81HCrxVtxRjygDTUoZJ5ASlDEeo0ppYFQjQIlXhtXpOn6g==",
"version": "14.0.13",
"resolved": "https://registry.npmjs.org/@types/node/-/node-14.0.13.tgz",
"integrity": "sha512-rouEWBImiRaSJsVA+ITTFM6ZxibuAlTuNOCyxVbwreu6k6+ujs7DfnU9o+PShFhET78pMBl3eH+AGSI5eOTkPA==",
"dev": true
},
"agent-base": {
@ -380,9 +380,9 @@
},
"dependencies": {
"entities": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/entities/-/entities-2.0.0.tgz",
"integrity": "sha512-D9f7V0JSRwIxlRI2mjMqufDrRDnx8p+eEOz7aUM9SuvF8gsBzra0/6tbjl1m8eQHrZlYj6PxqE00hZ1SAIKPLw==",
"version": "2.0.3",
"resolved": "https://registry.npmjs.org/entities/-/entities-2.0.3.tgz",
"integrity": "sha512-MyoZ0jgnLvB2X3Lg5HqpFmn1kybDiIfEQmKzTb5apr51Rb+T3KdmMiqa70T+bhGnyv7bQ6WMj2QMHpGMmlrUYQ==",
"dev": true
}
}
@ -597,9 +597,9 @@
}
},
"safe-buffer": {
"version": "5.2.0",
"resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.0.tgz",
"integrity": "sha512-fZEwUGbVl7kouZs1jCdMLdt95hdIv0ZeHg6L7qPeciMZhZ+/gdesW4wgTARkrFWEpspjEATAzUGPG8N2jJiwbg==",
"version": "5.2.1",
"resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz",
"integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==",
"dev": true
},
"semver": {
@ -674,9 +674,9 @@
}
},
"typescript": {
"version": "3.9.3",
"resolved": "https://registry.npmjs.org/typescript/-/typescript-3.9.3.tgz",
"integrity": "sha512-D/wqnB2xzNFIcoBG9FG8cXRDjiqSTbG2wd8DMZeQyJlP1vfTkIxH4GKveWaEBYySKIg+USu+E+EDIR47SqnaMQ==",
"version": "3.9.5",
"resolved": "https://registry.npmjs.org/typescript/-/typescript-3.9.5.tgz",
"integrity": "sha512-hSAifV3k+i6lEoCJ2k6R2Z/rp/H3+8sdmcn5NrS3/3kE7+RyZXm9aqvxWqjEXHAd8b0pShatpcdMTvEdvAJltQ==",
"dev": true
},
"uc.micro": {
@ -704,9 +704,9 @@
"dev": true
},
"vsce": {
"version": "1.75.0",
"resolved": "https://registry.npmjs.org/vsce/-/vsce-1.75.0.tgz",
"integrity": "sha512-qyAQTmolxKWc9bV1z0yBTSH4WEIWhDueBJMKB0GUFD6lM4MiaU1zJ9BtzekUORZu094YeNSKz0RmVVuxfqPq0g==",
"version": "1.76.1",
"resolved": "https://registry.npmjs.org/vsce/-/vsce-1.76.1.tgz",
"integrity": "sha512-WNx6JzRywxAOuhVpjmrsI0eHMK0mCA0YKD8u++7sprmhwCHsoQIBpSf0vp6kVMHBmafknr1Z6K7IC5jIjsNL9Q==",
"dev": true,
"requires": {
"azure-devops-node-api": "^7.2.0",
@ -747,47 +747,61 @@
}
},
"vscode-jsonrpc": {
"version": "5.1.0-next.1",
"resolved": "https://registry.npmjs.org/vscode-jsonrpc/-/vscode-jsonrpc-5.1.0-next.1.tgz",
"integrity": "sha512-mwLDojZkbmpizSJSmp690oa9FB9jig18SIDGZeBCvFc2/LYSRvMm/WwWtMBJuJ1MfFh7rZXfQige4Uje5Z9NzA=="
"version": "6.0.0-next.3",
"resolved": "https://registry.npmjs.org/vscode-jsonrpc/-/vscode-jsonrpc-6.0.0-next.3.tgz",
"integrity": "sha512-bkAtRMXbTwU1pAXnlbkAMnrqXZX3q2/zttkcnPZOsDqgoycO2L7GK2aN3K5qCMZmhItwBU185T78Q9YH0v5jEA=="
},
"vscode-languageclient": {
"version": "6.2.0-next.2",
"resolved": "https://registry.npmjs.org/vscode-languageclient/-/vscode-languageclient-6.2.0-next.2.tgz",
"integrity": "sha512-RqOrj+OCZmiMcLgmQSx581O+3rHb/AIFpEL+yoBcqFkqfIwMkoowCChEJGwVe/I8FeEphZ4dzlMm0MPW+p/DNA==",
"version": "7.0.0-next.6",
"resolved": "https://registry.npmjs.org/vscode-languageclient/-/vscode-languageclient-7.0.0-next.6.tgz",
"integrity": "sha512-3CPGOXCSxpPGONNVg+ZhY4AYyJrjLDJSKJT2acJ6L5mSlzKACFkFd3yQEGVHS/O0v4n/9i/KRNpBGVSR8ngw7Q==",
"requires": {
"semver": "^6.3.0",
"vscode-languageserver-protocol": "3.16.0-next.2"
"vscode-languageserver-protocol": "3.16.0-next.5"
},
"dependencies": {
"semver": {
"version": "6.3.0",
"resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz",
"integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw=="
},
"vscode-jsonrpc": {
"version": "6.0.0-next.3",
"resolved": "https://registry.npmjs.org/vscode-jsonrpc/-/vscode-jsonrpc-6.0.0-next.3.tgz",
"integrity": "sha512-bkAtRMXbTwU1pAXnlbkAMnrqXZX3q2/zttkcnPZOsDqgoycO2L7GK2aN3K5qCMZmhItwBU185T78Q9YH0v5jEA=="
},
"vscode-languageserver-protocol": {
"version": "3.16.0-next.5",
"resolved": "https://registry.npmjs.org/vscode-languageserver-protocol/-/vscode-languageserver-protocol-3.16.0-next.5.tgz",
"integrity": "sha512-MVuLT4d5tdDo/14laViyZY+p1HT7wYDwJdvGmo8mCfkgON5c10AABhtqjD+LccidBcwdbKy+4dwwUUxiNg/8PA==",
"requires": {
"vscode-jsonrpc": "6.0.0-next.3",
"vscode-languageserver-types": "3.16.0-next.2"
}
}
}
},
"vscode-languageserver": {
"version": "6.2.0-next.2",
"resolved": "https://registry.npmjs.org/vscode-languageserver/-/vscode-languageserver-6.2.0-next.2.tgz",
"integrity": "sha512-UCXULa/RmT2zxcb6auNezl9ZIPwYHS15+a0XzybrpT2+xA0RbHEYQCsIQkTRYjGv8cm3yS9iPJMmxLilP+y1Xw==",
"version": "7.0.0-next.4",
"resolved": "https://registry.npmjs.org/vscode-languageserver/-/vscode-languageserver-7.0.0-next.4.tgz",
"integrity": "sha512-yyFMuKLNkSKYb//6dQHCo0wlDYWkgRPEXoV+/aYXHp9ziIBqQxKJJFrf/rnXYReCz1TroxYMcFGdO5UrBsLAaA==",
"requires": {
"vscode-languageserver-protocol": "3.16.0-next.2"
"vscode-languageserver-protocol": "3.16.0-next.5"
}
},
"vscode-languageserver-protocol": {
"version": "3.16.0-next.2",
"resolved": "https://registry.npmjs.org/vscode-languageserver-protocol/-/vscode-languageserver-protocol-3.16.0-next.2.tgz",
"integrity": "sha512-atmkGT/W6tF0cx4SaWFYtFs2UeSeC28RPiap9myv2YZTaTCFvTBEPNWrU5QRKfkyM0tbgtGo6T3UCQ8tkDpjzA==",
"version": "3.16.0-next.5",
"resolved": "https://registry.npmjs.org/vscode-languageserver-protocol/-/vscode-languageserver-protocol-3.16.0-next.5.tgz",
"integrity": "sha512-MVuLT4d5tdDo/14laViyZY+p1HT7wYDwJdvGmo8mCfkgON5c10AABhtqjD+LccidBcwdbKy+4dwwUUxiNg/8PA==",
"requires": {
"vscode-jsonrpc": "5.1.0-next.1",
"vscode-languageserver-types": "3.16.0-next.1"
"vscode-jsonrpc": "6.0.0-next.3",
"vscode-languageserver-types": "3.16.0-next.2"
}
},
"vscode-languageserver-types": {
"version": "3.16.0-next.1",
"resolved": "https://registry.npmjs.org/vscode-languageserver-types/-/vscode-languageserver-types-3.16.0-next.1.tgz",
"integrity": "sha512-tZFUSbyjUcrh+qQf13ALX4QDdOfDX0cVaBFgy7ktJ0VwS7AW/yRKgGPSxVqqP9OCMNPdqP57O5q47w2pEwfaUg=="
"version": "3.16.0-next.2",
"resolved": "https://registry.npmjs.org/vscode-languageserver-types/-/vscode-languageserver-types-3.16.0-next.2.tgz",
"integrity": "sha512-QjXB7CKIfFzKbiCJC4OWC8xUncLsxo19FzGVp/ADFvvi87PlmBSCAtZI5xwGjF5qE0xkLf0jjKUn3DzmpDP52Q=="
},
"vscode-test": {
"version": "0.4.3",
@ -846,9 +860,9 @@
}
},
"vscode-uri": {
"version": "2.1.1",
"resolved": "https://registry.npmjs.org/vscode-uri/-/vscode-uri-2.1.1.tgz",
"integrity": "sha512-eY9jmGoEnVf8VE8xr5znSah7Qt1P/xsCdErz+g8HYZtJ7bZqKH5E3d+6oVNm1AC/c6IHUDokbmVXKOi4qPAC9A=="
"version": "2.1.2",
"resolved": "https://registry.npmjs.org/vscode-uri/-/vscode-uri-2.1.2.tgz",
"integrity": "sha512-8TEXQxlldWAuIODdukIb+TR5s+9Ds40eSJrw+1iDDA9IFORPjMELarNQE3myz5XIkWWpdprmJjm1/SxMlWOC8A=="
},
"wrappy": {
"version": "1.0.2",

View File

@ -13,7 +13,7 @@
"url": "https://github.com/Microsoft/pyright"
},
"engines": {
"vscode": "^1.43.0"
"vscode": "^1.46.0"
},
"keywords": [
"python"
@ -163,14 +163,14 @@
"postinstall": "node ./node_modules/vscode/bin/install"
},
"devDependencies": {
"typescript": "^3.9.3",
"vsce": "^1.75.0",
"typescript": "^3.9.5",
"vsce": "^1.76.1",
"vscode": "^1.1.37"
},
"dependencies": {
"leven": "^3.1.0",
"vscode-languageclient": "^6.2.0-next.2",
"vscode-languageserver": "^6.2.0-next.2",
"vscode-uri": "^2.1.1"
"vscode-languageclient": "^7.0.0-next.6",
"vscode-languageserver": "^7.0.0-next.4",
"vscode-uri": "^2.1.2"
}
}

View File

@ -12,7 +12,13 @@
import * as fs from 'fs';
import * as path from 'path';
import { commands, ExtensionContext, Position, Range, TextEditor, TextEditorEdit } from 'vscode';
import { LanguageClient, LanguageClientOptions, ServerOptions, TextEdit, TransportKind } from 'vscode-languageclient';
import {
LanguageClient,
LanguageClientOptions,
ServerOptions,
TextEdit,
TransportKind,
} from 'vscode-languageclient/node';
import { Commands } from '../../server/src/commands/commands';
import { FileBasedCancellationStrategy } from './cancellationUtils';

View File

@ -9,7 +9,7 @@
*/
import { Progress, ProgressLocation, window } from 'vscode';
import { Disposable, LanguageClient } from 'vscode-languageclient';
import { Disposable, LanguageClient } from 'vscode-languageclient/node';
const AnalysisTimeoutInMs = 60000;

12
package-lock.json generated
View File

@ -92,9 +92,9 @@
"dev": true
},
"@types/node": {
"version": "13.13.9",
"resolved": "https://registry.npmjs.org/@types/node/-/node-13.13.9.tgz",
"integrity": "sha512-EPZBIGed5gNnfWCiwEIwTE2Jdg4813odnG8iNPMQGrqVxrI+wL68SPtPeCX+ZxGBaA6pKAVc6jaKgP/Q0QzfdQ==",
"version": "13.13.12",
"resolved": "https://registry.npmjs.org/@types/node/-/node-13.13.12.tgz",
"integrity": "sha512-zWz/8NEPxoXNT9YyF2osqyA9WjssZukYpgI4UYZpOjcyqwIUqWGkcCionaEb9Ki+FULyPyvNFpg/329Kd2/pbw==",
"dev": true
},
"@types/normalize-package-data": {
@ -1725,9 +1725,9 @@
"dev": true
},
"typescript": {
"version": "3.9.3",
"resolved": "https://registry.npmjs.org/typescript/-/typescript-3.9.3.tgz",
"integrity": "sha512-D/wqnB2xzNFIcoBG9FG8cXRDjiqSTbG2wd8DMZeQyJlP1vfTkIxH4GKveWaEBYySKIg+USu+E+EDIR47SqnaMQ==",
"version": "3.9.5",
"resolved": "https://registry.npmjs.org/typescript/-/typescript-3.9.5.tgz",
"integrity": "sha512-hSAifV3k+i6lEoCJ2k6R2Z/rp/H3+8sdmcn5NrS3/3kE7+RyZXm9aqvxWqjEXHAd8b0pShatpcdMTvEdvAJltQ==",
"dev": true
},
"uri-js": {

View File

@ -24,7 +24,7 @@
"package": "npm run install:all && npm run clean && npm run build:serverProd && npm run build:cli && cd client && npx vsce package && cd .."
},
"devDependencies": {
"@types/node": "^13.13.9",
"@types/node": "^13.13.12",
"@typescript-eslint/eslint-plugin": "^2.34.0",
"@typescript-eslint/parser": "^2.34.0",
"del-cli": "^3.0.1",
@ -32,7 +32,7 @@
"eslint-config-prettier": "^6.11.0",
"eslint-plugin-simple-import-sort": "^5.0.3",
"prettier": "2.0.4",
"typescript": "^3.9.3"
"typescript": "^3.9.5"
},
"main": "index.js",
"bin": {

2795
server/package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -25,8 +25,8 @@
"command-line-args": "^5.1.1",
"leven": "^3.1.0",
"typescript-char": "^0.0.0",
"vscode-languageserver": "^6.2.0-next.2",
"vscode-uri": "^2.1.1"
"vscode-languageserver": "^7.0.0-next.4",
"vscode-uri": "^2.1.2"
},
"devDependencies": {
"@types/chalk": "^2.2.0",
@ -34,19 +34,19 @@
"@types/command-line-args": "^5.0.0",
"@types/fs-extra": "^8.1.1",
"@types/jest": "^25.2.3",
"@types/node": "^13.13.9",
"@types/node": "^13.13.12",
"@typescript-eslint/eslint-plugin": "^2.34.0",
"@typescript-eslint/parser": "^2.34.0",
"eslint": "^6.8.0",
"eslint-config-prettier": "^6.11.0",
"fs-extra": "^8.1.0",
"jest": "^25.5.4",
"jest": "^26.0.1",
"jest-junit": "^10.0.0",
"node-loader": "^0.6.0",
"prettier": "2.0.4",
"ts-jest": "^25.5.1",
"ts-jest": "^26.1.0",
"ts-loader": "^7.0.5",
"typescript": "^3.9.3",
"typescript": "^3.9.5",
"webpack": "^4.43.0",
"webpack-cli": "^3.3.11"
},

View File

@ -78,7 +78,7 @@ export function analyzeProgram(
(e.stack ? e.stack.toString() : undefined) ||
(typeof e.message === 'string' ? e.message : undefined) ||
JSON.stringify(e);
console.log('Error performing analysis: ' + message);
console.error('Error performing analysis: ' + message);
callback({
diagnostics: [],

View File

@ -12,15 +12,19 @@ import { ConfigOptions, ExecutionEnvironment } from '../common/configOptions';
import { FileSystem } from '../common/fileSystem';
import {
changeAnyExtension,
combinePathComponents,
combinePaths,
containsPath,
ensureTrailingDirectorySeparator,
getDirectoryPath,
getFileExtension,
getFileName,
getFileSystemEntries,
getPathComponents,
getRelativePathComponentsFromDirectory,
isDirectory,
isFile,
resolvePaths,
stripFileExtension,
stripTrailingDirectorySeparator,
} from '../common/pathUtils';
@ -351,7 +355,7 @@ export class ImportResolver {
}
// Returns the implementation file(s) for the given stub file.
getSourceFilesFromStub(stubFilePath: string): string[] {
getSourceFilesFromStub(stubFilePath: string, execEnv: ExecutionEnvironment): string[] {
const sourceFilePaths: string[] = [];
// When ImportResolver resolves an import to a stub file, a second resolve is done
@ -381,6 +385,71 @@ export class ImportResolver {
sourceFilePaths.push(sourceFilePath);
}
}
if (sourceFilePaths.length === 0) {
// The stub and the source file may have the same name, but be located
// in different folder hierarchies.
// Example:
// <stubPath>\package\module.pyi
// <site-packages>\package\module.py
// We get the relative path(s) of the stub to its import root(s),
// in theory there can be more than one, then look for source
// files in all the import roots using the same relative path(s).
const importRootPaths = this.getImportRoots(execEnv, /*useTypeshedVersionedFolders*/ true);
const relativeStubPaths: string[] = [];
for (const importRootPath of importRootPaths) {
if (containsPath(importRootPath, stubFilePath, true)) {
const parts = getRelativePathComponentsFromDirectory(importRootPath, stubFilePath, true);
// Note that relative paths have an empty parts[0]
if (parts.length > 1) {
// Handle the case where the symbol was resolved to a stubs package
// rather than the real package. We'll strip off the "-stubs" suffix
// in this case.
const stubsSuffix = '-stubs';
if (parts[1].endsWith(stubsSuffix)) {
parts[1] = parts[1].substr(0, parts[1].length - stubsSuffix.length);
}
const relativeStubPath = combinePathComponents(parts);
if (relativeStubPath) {
relativeStubPaths.push(relativeStubPath);
}
}
}
}
for (const relativeStubPath of relativeStubPaths) {
for (const importRootPath of importRootPaths) {
const absoluteStubPath = resolvePaths(importRootPath, relativeStubPath);
let absoluteSourcePath = changeAnyExtension(absoluteStubPath, '.py');
if (this.fileSystem.existsSync(absoluteSourcePath)) {
sourceFilePaths.push(absoluteSourcePath);
} else {
const filePathWithoutExtension = stripFileExtension(absoluteSourcePath);
if (filePathWithoutExtension.endsWith('__init__')) {
// Did not match: <root>/package/__init__.py
// Try equivalent: <root>/package.py
absoluteSourcePath =
filePathWithoutExtension.substr(0, filePathWithoutExtension.length - 9) + '.py';
if (this.fileSystem.existsSync(absoluteSourcePath)) {
sourceFilePaths.push(absoluteSourcePath);
}
} else {
// Did not match: <root>/package.py
// Try equivalent: <root>/package/__init__.py
absoluteSourcePath = combinePaths(filePathWithoutExtension, '__init__.py');
if (this.fileSystem.existsSync(absoluteSourcePath)) {
sourceFilePaths.push(absoluteSourcePath);
}
}
}
}
}
}
return sourceFilePaths;
}
@ -394,7 +463,7 @@ export class ImportResolver {
const importFailureInfo: string[] = [];
// Is this ia stdlib typeshed path?
// Is this a stdlib typeshed path?
const stdLibTypeshedPath = this._getTypeshedPath(true, execEnv, importFailureInfo);
if (stdLibTypeshedPath) {
moduleName = this._getModuleNameFromPath(stdLibTypeshedPath, filePath, true);
@ -466,6 +535,50 @@ export class ImportResolver {
return { moduleName: '', importType: ImportType.Local, isLocalTypingsFile };
}
getImportRoots(execEnv: ExecutionEnvironment, useTypeshedVersionedFolders: boolean) {
const importFailureInfo: string[] = [];
const roots = [];
const pythonVersion = execEnv.pythonVersion;
const minorVersion = pythonVersion & 0xff;
const versionFolders = ['2and3', '3'];
if (minorVersion > 0) {
versionFolders.push(versionToString(0x300 + minorVersion));
}
const stdTypesheds = this._getTypeshedPath(true, execEnv, importFailureInfo);
if (stdTypesheds) {
if (useTypeshedVersionedFolders) {
roots.push(...versionFolders.map((vf) => combinePaths(stdTypesheds, vf)));
} else {
roots.push(stdTypesheds);
}
}
roots.push(execEnv.root);
roots.push(...execEnv.extraPaths);
if (this._configOptions.stubPath) {
roots.push(this._configOptions.stubPath);
}
const typesheds = this._getTypeshedPath(false, execEnv, importFailureInfo);
if (typesheds) {
if (useTypeshedVersionedFolders) {
roots.push(...versionFolders.map((vf) => combinePaths(typesheds, vf)));
} else {
roots.push(typesheds);
}
}
const pythonSearchPaths = this._getPythonSearchPaths(execEnv, importFailureInfo);
if (pythonSearchPaths.length > 0) {
roots.push(...pythonSearchPaths);
}
return roots;
}
private _lookUpResultsInCache(
execEnv: ExecutionEnvironment,
importName: string,

View File

@ -17,7 +17,7 @@ import {
} from 'vscode-languageserver';
import { OperationCanceledException, throwIfCancellationRequested } from '../common/cancellationUtils';
import { ConfigOptions } from '../common/configOptions';
import { ConfigOptions, ExecutionEnvironment } from '../common/configOptions';
import { ConsoleInterface, StandardConsole } from '../common/console';
import { isDebugMode } from '../common/core';
import { assert } from '../common/debug';
@ -40,6 +40,7 @@ import {
AutoImporter,
AutoImportResult,
buildModuleSymbolsMap,
ImportNameMap,
ModuleSymbolMap,
} from '../languageService/autoImporter';
import { HoverResults } from '../languageService/hoverProvider';
@ -403,30 +404,30 @@ export class Program {
const zeroImportFiles: SourceFile[] = [];
sortedFiles.forEach((sfInfo) => {
this._console.log('');
this._console.info('');
let filePath = sfInfo.sourceFile.getFilePath();
const relPath = getRelativePath(filePath, projectRootDir);
if (relPath) {
filePath = relPath;
}
this._console.log(`${filePath}`);
this._console.info(`${filePath}`);
this._console.log(
this._console.info(
` Imports ${sfInfo.imports.length} ` + `file${sfInfo.imports.length === 1 ? '' : 's'}`
);
if (verbose) {
sfInfo.imports.forEach((importInfo) => {
this._console.log(` ${importInfo.sourceFile.getFilePath()}`);
this._console.info(` ${importInfo.sourceFile.getFilePath()}`);
});
}
this._console.log(
this._console.info(
` Imported by ${sfInfo.importedBy.length} ` + `file${sfInfo.importedBy.length === 1 ? '' : 's'}`
);
if (verbose) {
sfInfo.importedBy.forEach((importInfo) => {
this._console.log(` ${importInfo.sourceFile.getFilePath()}`);
this._console.info(` ${importInfo.sourceFile.getFilePath()}`);
});
}
@ -436,12 +437,12 @@ export class Program {
});
if (zeroImportFiles.length > 0) {
this._console.log('');
this._console.log(
this._console.info('');
this._console.info(
`${zeroImportFiles.length} file${zeroImportFiles.length === 1 ? '' : 's'}` + ` not explicitly imported`
);
zeroImportFiles.forEach((importFile) => {
this._console.log(` ${importFile.getFilePath()}`);
this._console.info(` ${importFile.getFilePath()}`);
});
}
}
@ -706,7 +707,7 @@ export class Program {
// Don't allow the heap to get close to the 2GB limit imposed by
// the OS when running Node in a 32-bit process.
if (heapSizeInMb > 1536) {
this._console.log(`Emptying type cache to avoid heap overflow. Heap size used: ${heapSizeInMb}MB`);
this._console.info(`Emptying type cache to avoid heap overflow. Heap size used: ${heapSizeInMb}MB`);
this._createNewEvaluator();
}
}
@ -841,6 +842,7 @@ export class Program {
range: Range,
similarityLimit: number,
nameMap: Map<string, string> | undefined,
importMap: ImportNameMap | undefined,
token: CancellationToken
): AutoImportResult[] {
const sourceFileInfo = this._sourceFileMap.get(filePath);
@ -876,7 +878,8 @@ export class Program {
sourceFile.getFilePath(),
this._importResolver,
parseTree,
map
map,
importMap
);
// Filter out any name that is already defined in the current scope.
@ -956,8 +959,9 @@ export class Program {
this._bindFile(sourceFileInfo);
const execEnv = this._configOptions.findExecEnvironment(filePath);
return sourceFileInfo.sourceFile.getDefinitionsForPosition(
this._createSourceMapper(),
this._createSourceMapper(execEnv),
position,
this._evaluator,
token
@ -979,8 +983,9 @@ export class Program {
this._bindFile(sourceFileInfo);
const execEnv = this._configOptions.findExecEnvironment(filePath);
const referencesResult = sourceFileInfo.sourceFile.getDeclarationForPosition(
this._createSourceMapper(),
this._createSourceMapper(execEnv),
position,
this._evaluator,
token
@ -1087,8 +1092,9 @@ export class Program {
this._bindFile(sourceFileInfo);
const execEnv = this._configOptions.findExecEnvironment(filePath);
return sourceFileInfo.sourceFile.getHoverForPosition(
this._createSourceMapper(),
this._createSourceMapper(execEnv),
position,
this._evaluator,
token
@ -1132,6 +1138,7 @@ export class Program {
let completionList = this._runEvaluatorWithCancellationToken(token, () => {
this._bindFile(sourceFileInfo);
const execEnv = this._configOptions.findExecEnvironment(filePath);
return sourceFileInfo.sourceFile.getCompletionsForPosition(
position,
workspacePath,
@ -1139,7 +1146,7 @@ export class Program {
this._importResolver,
this._lookUpImport,
this._evaluator,
this._createSourceMapper(),
this._createSourceMapper(execEnv),
() => this._buildModuleSymbolsMap(sourceFileInfo, token),
token
);
@ -1153,7 +1160,7 @@ export class Program {
const content = sourceFileInfo.sourceFile.getFileContents();
if (pr?.parseTree && content) {
const offset = convertPositionToOffset(position, pr.tokenizerOutput.lines);
if (offset) {
if (offset !== undefined) {
completionList = await this._extension.completionListExtension.updateCompletionList(
completionList,
pr.parseTree,
@ -1177,12 +1184,13 @@ export class Program {
this._bindFile(sourceFileInfo);
const execEnv = this._configOptions.findExecEnvironment(filePath);
sourceFileInfo.sourceFile.resolveCompletionItem(
this._configOptions,
this._importResolver,
this._lookUpImport,
this._evaluator,
this._createSourceMapper(),
this._createSourceMapper(execEnv),
() => this._buildModuleSymbolsMap(sourceFileInfo, token),
completionItem,
token
@ -1204,8 +1212,9 @@ export class Program {
this._bindFile(sourceFileInfo);
const execEnv = this._configOptions.findExecEnvironment(filePath);
const referencesResult = sourceFileInfo.sourceFile.getDeclarationForPosition(
this._createSourceMapper(),
this._createSourceMapper(execEnv),
position,
this._evaluator,
token
@ -1421,9 +1430,10 @@ export class Program {
return false;
}
private _createSourceMapper() {
private _createSourceMapper(execEnv: ExecutionEnvironment) {
const sourceMapper = new SourceMapper(
this._importResolver,
execEnv,
this._evaluator,
(stubFilePath: string, implFilePath: string) => {
const stubFileInfo = this._sourceFileMap.get(stubFilePath);
@ -1517,13 +1527,13 @@ export class Program {
}
});
} else if (options.verboseOutput) {
this._console.log(
this._console.info(
`Could not import '${importResult.importName}' ` +
`in file '${sourceFileInfo.sourceFile.getFilePath()}'`
);
if (importResult.importFailureInfo) {
importResult.importFailureInfo.forEach((diag) => {
this._console.log(` ${diag}`);
this._console.info(` ${diag}`);
});
}
}

View File

@ -40,6 +40,7 @@ import {
} from '../common/pathUtils';
import { DocumentRange, Position, Range } from '../common/textRange';
import { timingStats } from '../common/timing';
import { ImportNameMap } from '../languageService/autoImporter';
import { HoverResults } from '../languageService/hoverProvider';
import { SignatureHelpResults } from '../languageService/signatureHelpProvider';
import { AnalysisCompleteCallback } from './analysis';
@ -175,9 +176,10 @@ export class AnalyzerService {
range: Range,
similarityLimit: number,
nameMap: Map<string, string> | undefined,
importMap: ImportNameMap | undefined,
token: CancellationToken
) {
return this._program.getAutoImports(filePath, range, similarityLimit, nameMap, token);
return this._program.getAutoImports(filePath, range, similarityLimit, nameMap, importMap, token);
}
getDefinitionForPosition(
@ -249,11 +251,11 @@ export class AnalyzerService {
}
printStats() {
this._console.log('');
this._console.log('Analysis stats');
this._console.info('');
this._console.info('Analysis stats');
const fileCount = this._program.getFileCount();
this._console.log('Total files analyzed: ' + fileCount.toString());
this._console.info('Total files analyzed: ' + fileCount.toString());
}
printDependencies(verbose: boolean) {
@ -264,6 +266,10 @@ export class AnalyzerService {
return this._backgroundAnalysisProgram.getDiagnosticsForRange(filePath, range, token);
}
getConfigOptions() {
return this._configOptions;
}
getImportResolver(): ImportResolver {
return this._backgroundAnalysisProgram.importResolver;
}
@ -279,10 +285,6 @@ export class AnalyzerService {
}
// test only APIs
get test_configOptions() {
return this._configOptions;
}
get test_program() {
return this._program;
}
@ -306,7 +308,7 @@ export class AnalyzerService {
// will be used. In this case, all file specs are assumed to be
// relative to the current working directory.
if (commandLineOptions.configFilePath) {
this._console.log('Project cannot be mixed with source files on a command line.');
this._console.info('Project cannot be mixed with source files on a command line.');
}
} else if (commandLineOptions.configFilePath) {
// If the config file path was specified, determine whether it's
@ -317,7 +319,7 @@ export class AnalyzerService {
normalizePath(commandLineOptions.configFilePath)
);
if (!this._fs.existsSync(configFilePath)) {
this._console.log(`Configuration file not found at ${configFilePath}.`);
this._console.info(`Configuration file not found at ${configFilePath}.`);
configFilePath = commandLineOptions.executionRoot;
} else {
if (configFilePath.toLowerCase().endsWith('.json')) {
@ -326,7 +328,7 @@ export class AnalyzerService {
projectRoot = configFilePath;
configFilePath = this._findConfigFile(configFilePath);
if (!configFilePath) {
this._console.log(`Configuration file not found at ${projectRoot}.`);
this._console.info(`Configuration file not found at ${projectRoot}.`);
}
}
}
@ -335,7 +337,7 @@ export class AnalyzerService {
if (configFilePath) {
projectRoot = getDirectoryPath(configFilePath);
} else {
this._console.log(`No configuration file found.`);
this._console.info(`No configuration file found.`);
configFilePath = undefined;
}
}
@ -365,7 +367,7 @@ export class AnalyzerService {
// If we found a config file, parse it to compute the effective options.
if (configFilePath) {
this._console.log(`Loading configuration file at ${configFilePath}`);
this._console.info(`Loading configuration file at ${configFilePath}`);
const configJsonObj = this._parseConfigFile(configFilePath);
if (configJsonObj) {
configOptions.initializeFromJson(
@ -381,14 +383,14 @@ export class AnalyzerService {
// If no include paths were provided, assume that all files within
// the project should be included.
if (configOptions.include.length === 0) {
this._console.log(`No include entries specified; assuming ${configFilePath}`);
this._console.info(`No include entries specified; assuming ${configFilePath}`);
configOptions.include.push(getFileSpec(configFileDir, '.'));
}
// If there was no explicit set of excludes, add a few common ones to avoid long scan times.
if (configOptions.exclude.length === 0) {
defaultExcludes.forEach((exclude) => {
this._console.log(`Auto-excluding ${exclude}`);
this._console.info(`Auto-excluding ${exclude}`);
configOptions.exclude.push(getFileSpec(configFileDir, exclude));
});
@ -425,7 +427,7 @@ export class AnalyzerService {
const settingSource = commandLineOptions.fromVsCodeExtension
? 'the VS Code settings'
: 'a command-line option';
this._console.log(
this._console.info(
`The ${settingName} has been specified in both the config file and ` +
`${settingSource}. The value in the config file (${configOptions.venvPath}) ` +
`will take precedence`
@ -444,7 +446,7 @@ export class AnalyzerService {
}
if (commandLineOptions.pythonPath) {
this._console.log(
this._console.info(
`Setting pythonPath for service "${this._instanceName}": ` + `"${commandLineOptions.pythonPath}"`
);
configOptions.pythonPath = commandLineOptions.pythonPath;
@ -479,7 +481,7 @@ export class AnalyzerService {
// or inconsistent information.
if (configOptions.venvPath) {
if (!this._fs.existsSync(configOptions.venvPath) || !isDirectory(this._fs, configOptions.venvPath)) {
this._console.log(`venvPath ${configOptions.venvPath} is not a valid directory.`);
this._console.info(`venvPath ${configOptions.venvPath} is not a valid directory.`);
}
// venvPath without defaultVenv means it won't do anything while resolveImport.
@ -490,21 +492,21 @@ export class AnalyzerService {
const fullVenvPath = combinePaths(configOptions.venvPath, configOptions.defaultVenv);
if (!this._fs.existsSync(fullVenvPath) || !isDirectory(this._fs, fullVenvPath)) {
this._console.log(
this._console.info(
`venv ${configOptions.defaultVenv} subdirectory not found ` +
`in venv path ${configOptions.venvPath}.`
);
} else {
const importFailureInfo: string[] = [];
if (findPythonSearchPaths(this._fs, configOptions, undefined, importFailureInfo) === undefined) {
this._console.log(
this._console.info(
`site-packages directory cannot be located for venvPath ` +
`${configOptions.venvPath} and venv ${configOptions.defaultVenv}.`
);
if (configOptions.verboseOutput) {
importFailureInfo.forEach((diag) => {
this._console.log(` ${diag}`);
this._console.info(` ${diag}`);
});
}
}
@ -519,22 +521,22 @@ export class AnalyzerService {
).paths;
if (pythonPaths.length === 0) {
if (configOptions.verboseOutput) {
this._console.log(`No search paths found for configured python interpreter.`);
this._console.info(`No search paths found for configured python interpreter.`);
}
} else {
if (configOptions.verboseOutput) {
this._console.log(`Search paths found for configured python interpreter:`);
this._console.info(`Search paths found for configured python interpreter:`);
pythonPaths.forEach((path) => {
this._console.log(` ${path}`);
this._console.info(` ${path}`);
});
}
}
if (configOptions.verboseOutput) {
if (importFailureInfo.length > 0) {
this._console.log(`When attempting to get search paths from python interpreter:`);
this._console.info(`When attempting to get search paths from python interpreter:`);
importFailureInfo.forEach((diag) => {
this._console.log(` ${diag}`);
this._console.info(` ${diag}`);
});
}
}
@ -543,7 +545,7 @@ export class AnalyzerService {
// Is there a reference to a venv? If so, there needs to be a valid venvPath.
if (configOptions.defaultVenv || configOptions.executionEnvironments.find((e) => !!e.venv)) {
if (!configOptions.venvPath) {
this._console.log(`venvPath not specified, so venv settings will be ignored.`);
this._console.info(`venvPath not specified, so venv settings will be ignored.`);
}
}
@ -552,13 +554,13 @@ export class AnalyzerService {
!this._fs.existsSync(configOptions.typeshedPath) ||
!isDirectory(this._fs, configOptions.typeshedPath)
) {
this._console.log(`typeshedPath ${configOptions.typeshedPath} is not a valid directory.`);
this._console.info(`typeshedPath ${configOptions.typeshedPath} is not a valid directory.`);
}
}
if (configOptions.stubPath) {
if (!this._fs.existsSync(configOptions.stubPath) || !isDirectory(this._fs, configOptions.stubPath)) {
this._console.log(`stubPath ${configOptions.stubPath} is not a valid directory.`);
this._console.info(`stubPath ${configOptions.stubPath} is not a valid directory.`);
}
}
@ -646,7 +648,7 @@ export class AnalyzerService {
// We should never get here because we always generate a
// default typings path if none was specified.
const errMsg = 'No typings path was specified';
this._console.log(errMsg);
this._console.info(errMsg);
throw new Error(errMsg);
}
const typeStubInputTargetParts = this._typeStubTargetImportName.split('.');
@ -705,7 +707,7 @@ export class AnalyzerService {
try {
configContents = this._fs.readFileSync(configPath, 'utf8');
} catch {
this._console.log(`Config file "${configPath}" could not be read.`);
this._console.info(`Config file "${configPath}" could not be read.`);
this._reportConfigParseError();
return undefined;
}
@ -729,7 +731,7 @@ export class AnalyzerService {
// resulting in parse errors. We'll give it a little more time and
// try again.
if (parseAttemptCount++ >= 5) {
this._console.log(`Config file "${configPath}" could not be parsed. Verify that JSON is correct.`);
this._console.info(`Config file "${configPath}" could not be parsed. Verify that JSON is correct.`);
this._reportConfigParseError();
return undefined;
}
@ -815,20 +817,20 @@ export class AnalyzerService {
this._backgroundAnalysisProgram.setAllowedThirdPartyImports([this._typeStubTargetImportName]);
this._backgroundAnalysisProgram.setTrackedFiles(filesToImport);
} else {
this._console.log(`Import '${this._typeStubTargetImportName}' not found`);
this._console.info(`Import '${this._typeStubTargetImportName}' not found`);
}
} else {
let fileList: string[] = [];
this._console.log(`Searching for source files`);
this._console.info(`Searching for source files`);
fileList = this._getFileNamesFromFileSpecs();
this._backgroundAnalysisProgram.setTrackedFiles(fileList);
this._backgroundAnalysisProgram.markAllFilesDirty(markFilesDirtyUnconditionally);
if (fileList.length === 0) {
this._console.log(`No source files found.`);
this._console.info(`No source files found.`);
} else {
this._console.log(`Found ${fileList.length} ` + `source ${fileList.length === 1 ? 'file' : 'files'}`);
this._console.info(`Found ${fileList.length} ` + `source ${fileList.length === 1 ? 'file' : 'files'}`);
}
}
@ -847,7 +849,7 @@ export class AnalyzerService {
const visitDirectory = (absolutePath: string, includeRegExp: RegExp) => {
if (this._configOptions.autoExcludeVenv) {
if (envMarkers.some((f) => this._fs.existsSync(combinePaths(absolutePath, ...f)))) {
this._console.log(`Auto-excluding ${absolutePath}`);
this._console.info(`Auto-excluding ${absolutePath}`);
return;
}
}
@ -895,7 +897,7 @@ export class AnalyzerService {
}
if (!foundFileSpec) {
this._console.log(`File or directory "${includeSpec.wildcardRoot}" does not exist.`);
this._console.info(`File or directory "${includeSpec.wildcardRoot}" does not exist.`);
}
});
@ -925,12 +927,12 @@ export class AnalyzerService {
try {
if (this._verboseOutput) {
this._console.log(`Adding fs watcher for directories:\n ${fileList.join('\n')}`);
this._console.info(`Adding fs watcher for directories:\n ${fileList.join('\n')}`);
}
this._sourceFileWatcher = this._fs.createFileSystemWatcher(fileList, (event, path) => {
if (this._verboseOutput) {
this._console.log(`Received fs event '${event}' for path '${path}'`);
this._console.info(`Received fs event '${event}' for path '${path}'`);
}
// Delete comes in as a change event, so try to distinguish here.
@ -961,7 +963,7 @@ export class AnalyzerService {
}
});
} catch {
this._console.log(`Exception caught when installing fs watcher for:\n ${fileList.join('\n')}`);
this._console.info(`Exception caught when installing fs watcher for:\n ${fileList.join('\n')}`);
}
}
}
@ -996,18 +998,18 @@ export class AnalyzerService {
if (watchList && watchList.length > 0) {
try {
if (this._verboseOutput) {
this._console.log(`Adding fs watcher for library directories:\n ${watchList.join('\n')}`);
this._console.info(`Adding fs watcher for library directories:\n ${watchList.join('\n')}`);
}
this._libraryFileWatcher = this._fs.createFileSystemWatcher(watchList, (event, path) => {
if (this._verboseOutput) {
this._console.log(`Received fs event '${event}' for path '${path}'`);
this._console.info(`Received fs event '${event}' for path '${path}'`);
}
this._scheduleLibraryAnalysis();
});
} catch {
this._console.log(`Exception caught when installing fs watcher for:\n ${watchList.join('\n')}`);
this._console.info(`Exception caught when installing fs watcher for:\n ${watchList.join('\n')}`);
}
}
}
@ -1053,7 +1055,7 @@ export class AnalyzerService {
if (this._configFilePath) {
this._configFileWatcher = this._fs.createFileSystemWatcher([this._configFilePath], (event) => {
if (this._verboseOutput) {
this._console.log(`Received fs event '${event}' for config file`);
this._console.info(`Received fs event '${event}' for config file`);
}
this._scheduleReloadConfigFile();
});
@ -1084,7 +1086,7 @@ export class AnalyzerService {
if (this._configFilePath) {
this._updateConfigFileWatcher();
this._console.log(`Reloading configuration file at ${this._configFilePath}`);
this._console.info(`Reloading configuration file at ${this._configFilePath}`);
// We can't just reload config file when it is changed; we need to consider
// command line options as well to construct new config Options.

View File

@ -500,7 +500,7 @@ export class SourceFile {
(e.stack ? e.stack.toString() : undefined) ||
(typeof e.message === 'string' ? e.message : undefined) ||
JSON.stringify(e);
this._console.log(`An internal error occurred while parsing ${this.getFilePath()}: ` + message);
this._console.error(`An internal error occurred while parsing ${this.getFilePath()}: ` + message);
// Create dummy parse results.
this._parseResults = {
@ -792,7 +792,7 @@ export class SourceFile {
(e.stack ? e.stack.toString() : undefined) ||
(typeof e.message === 'string' ? e.message : undefined) ||
JSON.stringify(e);
this._console.log(
this._console.error(
`An internal error occurred while performing name binding for ${this.getFilePath()}: ` + message
);
@ -835,7 +835,7 @@ export class SourceFile {
(e.stack ? e.stack.toString() : undefined) ||
(typeof e.message === 'string' ? e.message : undefined) ||
JSON.stringify(e);
this._console.log(
this._console.error(
`An internal error occurred while while performing type checking for ${this.getFilePath()}: ` +
message
);

View File

@ -8,6 +8,7 @@
import * as AnalyzerNodeInfo from '../analyzer/analyzerNodeInfo';
import * as ParseTreeUtils from '../analyzer/parseTreeUtils';
import { ExecutionEnvironment } from '../common/configOptions';
import { getAnyExtensionFromPath } from '../common/pathUtils';
import { ClassNode, ModuleNode, ParseNode } from '../parser/parseNodes';
import { ClassDeclaration, Declaration, DeclarationType, FunctionDeclaration } from './declaration';
@ -21,6 +22,7 @@ export type ShadowFileBinder = (stubFilePath: string, implFilePath: string) => S
export class SourceMapper {
constructor(
private _importResolver: ImportResolver,
private _execEnv: ExecutionEnvironment,
private _evaluator: TypeEvaluator,
private _fileBinder: ShadowFileBinder
) {}
@ -187,7 +189,7 @@ export class SourceMapper {
}
private _getBoundSourceFiles(stubFilePath: string): SourceFile[] {
const paths = this._importResolver.getSourceFilesFromStub(stubFilePath);
const paths = this._importResolver.getSourceFilesFromStub(stubFilePath, this._execEnv);
return paths.map((fp) => this._fileBinder(stubFilePath, fp)).filter(_isDefined);
}
}

View File

@ -21,7 +21,7 @@ import {
throwIfCancellationRequested,
} from './common/cancellationUtils';
import { ConfigOptions } from './common/configOptions';
import { ConsoleInterface } from './common/console';
import { ConsoleInterface, log, LogLevel } from './common/console';
import * as debug from './common/debug';
import { Diagnostic } from './common/diagnostic';
import { FileDiagnostics } from './common/diagnosticSink';
@ -46,9 +46,11 @@ export class BackgroundAnalysisBase {
// global channel to communicate from BG channel to main thread.
worker.on('message', (msg: AnalysisResponse) => {
switch (msg.requestType) {
case 'log':
this.log(msg.data);
case 'log': {
const logData = msg.data as LogData;
this.log(logData.level, logData.message);
break;
}
case 'analysisResult': {
// Change in diagnostics due to host such as file closed rather than
@ -65,7 +67,7 @@ export class BackgroundAnalysisBase {
// this will catch any exception thrown from background thread,
// print log and ignore exception
worker.on('error', (msg) => {
this.log(`Error occurred on background thread: ${JSON.stringify(msg)}`);
this.log(LogLevel.Error, `Error occurred on background thread: ${JSON.stringify(msg)}`);
});
}
@ -185,8 +187,8 @@ export class BackgroundAnalysisBase {
this._worker.postMessage(request, request.port ? [request.port] : undefined);
}
protected log(msg: string) {
this._console.log(msg);
protected log(level: LogLevel, msg: string) {
log(this._console, level, msg);
}
}
@ -202,7 +204,7 @@ export class BackgroundAnalysisRunnerBase {
// Stash the base directory into a global variable.
(global as any).__rootDirectory = data.rootDirectory;
this.log(`Background analysis root directory: ${data.rootDirectory}`);
this.log(LogLevel.Info, `Background analysis root directory: ${data.rootDirectory}`);
this._fs = createFromRealFileSystem(this._getConsole());
@ -212,7 +214,7 @@ export class BackgroundAnalysisRunnerBase {
}
start() {
this.log(`Background analysis started`);
this.log(LogLevel.Info, `Background analysis started`);
// Get requests from main thread.
parentPort?.on('message', (msg: AnalysisRequest) => {
@ -355,8 +357,8 @@ export class BackgroundAnalysisRunnerBase {
});
}
protected log(msg: string) {
parentPort?.postMessage({ requestType: 'log', data: msg });
protected log(level: LogLevel, msg: string) {
parentPort?.postMessage({ requestType: 'log', data: { level: level, message: msg } });
}
protected createImportResolver(fs: FileSystem, options: ConfigOptions): ImportResolver {
@ -388,10 +390,16 @@ export class BackgroundAnalysisRunnerBase {
private _getConsole() {
return {
log: (msg: string) => {
this.log(msg);
this.log(LogLevel.Log, msg);
},
info: (msg: string) => {
this.log(LogLevel.Info, msg);
},
warn: (msg: string) => {
this.log(LogLevel.Warn, msg);
},
error: (msg: string) => {
this.log(msg);
this.log(LogLevel.Error, msg);
},
};
}
@ -531,3 +539,8 @@ interface RequestResponse {
kind: 'ok' | 'failed' | 'cancelled';
data: any;
}
interface LogData {
level: LogLevel;
message: string;
}

View File

@ -564,14 +564,14 @@ export class ConfigOptions {
this.include = [];
if (configObj.include !== undefined) {
if (!Array.isArray(configObj.include)) {
console.log(`Config "include" entry must must contain an array.`);
console.info(`Config "include" entry must must contain an array.`);
} else {
const filesList = configObj.include as string[];
filesList.forEach((fileSpec, index) => {
if (typeof fileSpec !== 'string') {
console.log(`Index ${index} of "include" array should be a string.`);
console.info(`Index ${index} of "include" array should be a string.`);
} else if (isAbsolute(fileSpec)) {
console.log(`Ignoring path "${fileSpec}" in "include" array because it is not relative.`);
console.info(`Ignoring path "${fileSpec}" in "include" array because it is not relative.`);
} else {
this.include.push(getFileSpec(this.projectRoot, fileSpec));
}
@ -583,14 +583,14 @@ export class ConfigOptions {
this.exclude = [];
if (configObj.exclude !== undefined) {
if (!Array.isArray(configObj.exclude)) {
console.log(`Config "exclude" entry must contain an array.`);
console.info(`Config "exclude" entry must contain an array.`);
} else {
const filesList = configObj.exclude as string[];
filesList.forEach((fileSpec, index) => {
if (typeof fileSpec !== 'string') {
console.log(`Index ${index} of "exclude" array should be a string.`);
console.info(`Index ${index} of "exclude" array should be a string.`);
} else if (isAbsolute(fileSpec)) {
console.log(`Ignoring path "${fileSpec}" in "exclude" array because it is not relative.`);
console.info(`Ignoring path "${fileSpec}" in "exclude" array because it is not relative.`);
} else {
this.exclude.push(getFileSpec(this.projectRoot, fileSpec));
}
@ -602,14 +602,14 @@ export class ConfigOptions {
this.ignore = [];
if (configObj.ignore !== undefined) {
if (!Array.isArray(configObj.ignore)) {
console.log(`Config "ignore" entry must contain an array.`);
console.info(`Config "ignore" entry must contain an array.`);
} else {
const filesList = configObj.ignore as string[];
filesList.forEach((fileSpec, index) => {
if (typeof fileSpec !== 'string') {
console.log(`Index ${index} of "ignore" array should be a string.`);
console.info(`Index ${index} of "ignore" array should be a string.`);
} else if (isAbsolute(fileSpec)) {
console.log(`Ignoring path "${fileSpec}" in "ignore" array because it is not relative.`);
console.info(`Ignoring path "${fileSpec}" in "ignore" array because it is not relative.`);
} else {
this.ignore.push(getFileSpec(this.projectRoot, fileSpec));
}
@ -621,14 +621,14 @@ export class ConfigOptions {
this.strict = [];
if (configObj.strict !== undefined) {
if (!Array.isArray(configObj.strict)) {
console.log(`Config "strict" entry must contain an array.`);
console.info(`Config "strict" entry must contain an array.`);
} else {
const filesList = configObj.strict as string[];
filesList.forEach((fileSpec, index) => {
if (typeof fileSpec !== 'string') {
console.log(`Index ${index} of "strict" array should be a string.`);
console.info(`Index ${index} of "strict" array should be a string.`);
} else if (isAbsolute(fileSpec)) {
console.log(`Ignoring path "${fileSpec}" in "strict" array because it is not relative.`);
console.info(`Ignoring path "${fileSpec}" in "strict" array because it is not relative.`);
} else {
this.strict.push(getFileSpec(this.projectRoot, fileSpec));
}
@ -646,7 +646,7 @@ export class ConfigOptions {
) {
configTypeCheckingMode = configObj.typeCheckingMode;
} else {
console.log(`Config "typeCheckingMode" entry must contain "off", "basic", or "strict".`);
console.info(`Config "typeCheckingMode" entry must contain "off", "basic", or "strict".`);
}
}
@ -953,7 +953,7 @@ export class ConfigOptions {
this.venvPath = undefined;
if (configObj.venvPath !== undefined) {
if (typeof configObj.venvPath !== 'string') {
console.log(`Config "venvPath" field must contain a string.`);
console.info(`Config "venvPath" field must contain a string.`);
} else {
this.venvPath = normalizePath(combinePaths(this.projectRoot, configObj.venvPath));
}
@ -963,7 +963,7 @@ export class ConfigOptions {
this.defaultVenv = undefined;
if (configObj.venv !== undefined) {
if (typeof configObj.venv !== 'string') {
console.log(`Config "venv" field must contain a string.`);
console.info(`Config "venv" field must contain a string.`);
} else {
this.defaultVenv = configObj.venv;
}
@ -977,10 +977,10 @@ export class ConfigOptions {
if (version) {
this.defaultPythonVersion = version;
} else {
console.log(`Config "pythonVersion" field contains unsupported version.`);
console.info(`Config "pythonVersion" field contains unsupported version.`);
}
} else {
console.log(`Config "pythonVersion" field must contain a string.`);
console.info(`Config "pythonVersion" field must contain a string.`);
}
}
@ -989,7 +989,7 @@ export class ConfigOptions {
if (this.defaultPythonVersion === undefined) {
this.defaultPythonVersion = this._getPythonVersionFromPythonInterpreter(pythonPath, console);
if (this.defaultPythonVersion !== undefined) {
console.log(`Assuming Python version ${versionToString(this.defaultPythonVersion)}`);
console.info(`Assuming Python version ${versionToString(this.defaultPythonVersion)}`);
}
}
@ -997,7 +997,7 @@ export class ConfigOptions {
this.defaultPythonPlatform = undefined;
if (configObj.pythonPlatform !== undefined) {
if (typeof configObj.pythonPlatform !== 'string') {
console.log(`Config "pythonPlatform" field must contain a string.`);
console.info(`Config "pythonPlatform" field must contain a string.`);
} else {
this.defaultPythonPlatform = configObj.pythonPlatform;
}
@ -1015,7 +1015,7 @@ export class ConfigOptions {
}
if (this.defaultPythonPlatform !== undefined) {
console.log(`Assuming Python platform ${this.defaultPythonPlatform}`);
console.info(`Assuming Python platform ${this.defaultPythonPlatform}`);
}
}
@ -1023,7 +1023,7 @@ export class ConfigOptions {
this.typeshedPath = undefined;
if (configObj.typeshedPath !== undefined) {
if (typeof configObj.typeshedPath !== 'string') {
console.log(`Config "typeshedPath" field must contain a string.`);
console.info(`Config "typeshedPath" field must contain a string.`);
} else {
this.typeshedPath = configObj.typeshedPath
? normalizePath(combinePaths(this.projectRoot, configObj.typeshedPath))
@ -1037,16 +1037,16 @@ export class ConfigOptions {
// Keep this for backward compatibility
if (configObj.typingsPath !== undefined) {
if (typeof configObj.typingsPath !== 'string') {
console.log(`Config "typingsPath" field must contain a string.`);
console.info(`Config "typingsPath" field must contain a string.`);
} else {
console.log(`Config "typingsPath" is now deprecated. Please, use stubPath instead.`);
console.info(`Config "typingsPath" is now deprecated. Please, use stubPath instead.`);
this.stubPath = normalizePath(combinePaths(this.projectRoot, configObj.typingsPath));
}
}
if (configObj.stubPath !== undefined) {
if (typeof configObj.stubPath !== 'string') {
console.log(`Config "stubPath" field must contain a string.`);
console.info(`Config "stubPath" field must contain a string.`);
} else {
this.stubPath = normalizePath(combinePaths(this.projectRoot, configObj.stubPath));
}
@ -1057,7 +1057,7 @@ export class ConfigOptions {
// switch to apply if this setting isn't specified in the config file.
if (configObj.verboseOutput !== undefined) {
if (typeof configObj.verboseOutput !== 'boolean') {
console.log(`Config "verboseOutput" field must be true or false.`);
console.info(`Config "verboseOutput" field must be true or false.`);
} else {
this.verboseOutput = configObj.verboseOutput;
}
@ -1066,7 +1066,7 @@ export class ConfigOptions {
// Read the "useLibraryCodeForTypes" setting.
if (configObj.useLibraryCodeForTypes !== undefined) {
if (typeof configObj.useLibraryCodeForTypes !== 'boolean') {
console.log(`Config "useLibraryCodeForTypes" field must be true or false.`);
console.info(`Config "useLibraryCodeForTypes" field must be true or false.`);
} else {
this.useLibraryCodeForTypes = configObj.useLibraryCodeForTypes;
}
@ -1077,7 +1077,7 @@ export class ConfigOptions {
this.executionEnvironments = [];
if (configObj.executionEnvironments !== undefined) {
if (!Array.isArray(configObj.executionEnvironments)) {
console.log(`Config "executionEnvironments" field must contain an array.`);
console.info(`Config "executionEnvironments" field must contain an array.`);
} else {
const execEnvironments = configObj.executionEnvironments as ExecutionEnvironment[];
execEnvironments.forEach((env, index) => {
@ -1142,18 +1142,20 @@ export class ConfigOptions {
if (envObj.root && typeof envObj.root === 'string') {
newExecEnv.root = normalizePath(combinePaths(this.projectRoot, envObj.root));
} else {
console.log(`Config executionEnvironments index ${index}: missing root value.`);
console.info(`Config executionEnvironments index ${index}: missing root value.`);
}
// Validate the extraPaths.
if (envObj.extraPaths) {
if (!Array.isArray(envObj.extraPaths)) {
console.log(`Config executionEnvironments index ${index}: extraPaths field must contain an array.`);
console.info(
`Config executionEnvironments index ${index}: extraPaths field must contain an array.`
);
} else {
const pathList = envObj.extraPaths as string[];
pathList.forEach((path, pathIndex) => {
if (typeof path !== 'string') {
console.log(
console.info(
`Config executionEnvironments index ${index}:` +
` extraPaths field ${pathIndex} must be a string.`
);
@ -1171,10 +1173,10 @@ export class ConfigOptions {
if (version) {
newExecEnv.pythonVersion = version;
} else {
console.log(`Config executionEnvironments index ${index} contains unsupported pythonVersion.`);
console.info(`Config executionEnvironments index ${index} contains unsupported pythonVersion.`);
}
} else {
console.log(`Config executionEnvironments index ${index} pythonVersion must be a string.`);
console.info(`Config executionEnvironments index ${index} pythonVersion must be a string.`);
}
}
@ -1183,7 +1185,7 @@ export class ConfigOptions {
if (typeof envObj.pythonPlatform === 'string') {
newExecEnv.pythonPlatform = envObj.pythonPlatform;
} else {
console.log(`Config executionEnvironments index ${index} pythonPlatform must be a string.`);
console.info(`Config executionEnvironments index ${index} pythonPlatform must be a string.`);
}
}
@ -1192,13 +1194,13 @@ export class ConfigOptions {
if (typeof envObj.venv === 'string') {
newExecEnv.venv = envObj.venv;
} else {
console.log(`Config executionEnvironments index ${index} venv must be a string.`);
console.info(`Config executionEnvironments index ${index} venv must be a string.`);
}
}
return newExecEnv;
} catch {
console.log(`Config executionEnvironments index ${index} is not accessible.`);
console.info(`Config executionEnvironments index ${index} is not accessible.`);
}
return undefined;
@ -1221,25 +1223,25 @@ export class ConfigOptions {
// Parse the execOutput. It should be something like "Python 3.x.y".
const match = execOutput.match(/[0-9]+.[0-9]+.[0-9]+/);
if (!match) {
console.log(`Unable to get Python version from interpreter. Received "${execOutput}"`);
console.info(`Unable to get Python version from interpreter. Received "${execOutput}"`);
return undefined;
}
const versionString = match[0];
const version = versionFromString(versionString);
if (version === undefined) {
console.log(`Unable to get Python version from interpreter. Received "${execOutput}"`);
console.info(`Unable to get Python version from interpreter. Received "${execOutput}"`);
return undefined;
}
if (version < PythonVersion.V30) {
console.log(`Python version from interpreter is unsupported: "${execOutput}"`);
console.info(`Python version from interpreter is unsupported: "${execOutput}"`);
return undefined;
}
return version;
} catch {
console.log('Unable to get Python version from interpreter');
console.info('Unable to get Python version from interpreter');
return undefined;
}
}

View File

@ -8,9 +8,20 @@
* methods.
*/
import * as debug from './debug';
export enum LogLevel {
Error = 'error',
Warn = 'warn',
Info = 'info',
Log = 'log',
}
export interface ConsoleInterface {
log: (message: string) => void;
error: (message: string) => void;
warn: (message: string) => void;
info: (message: string) => void;
log: (message: string) => void;
}
// Avoids outputting errors to the console but counts
@ -18,12 +29,22 @@ export interface ConsoleInterface {
// for unit tests.
export class NullConsole implements ConsoleInterface {
logCount = 0;
infoCount = 0;
warnCount = 0;
errorCount = 0;
log(message: string) {
this.logCount++;
}
info(message: string) {
this.infoCount++;
}
warn(message: string) {
this.warnCount++;
}
error(message: string) {
this.errorCount++;
}
@ -31,10 +52,108 @@ export class NullConsole implements ConsoleInterface {
export class StandardConsole implements ConsoleInterface {
log(message: string) {
console.log(message);
console.info(message);
}
info(message: string) {
console.info(message);
}
warn(message: string) {
console.warn(message);
}
error(message: string) {
console.error(message);
}
}
export class ConsoleWithLogLevel implements ConsoleInterface {
private _levelMap: Map<string, number> = new Map([
[LogLevel.Error, 0],
[LogLevel.Warn, 1],
[LogLevel.Info, 2],
[LogLevel.Log, 3],
]);
private _maxLevel = 2;
constructor(private _console: ConsoleInterface) {}
get level(): LogLevel {
switch (this._maxLevel) {
case 0:
return LogLevel.Error;
case 1:
return LogLevel.Warn;
case 2:
return LogLevel.Info;
}
return LogLevel.Log;
}
set level(value: LogLevel) {
let maxLevel = this._levelMap.get(value);
if (maxLevel === undefined) {
maxLevel = this._levelMap.get(LogLevel.Info)!;
}
this._maxLevel = maxLevel;
}
error(message: string) {
this._log(LogLevel.Error, message);
}
warn(message: string) {
this._log(LogLevel.Warn, message);
}
info(message: string) {
this._log(LogLevel.Info, message);
}
log(message: string) {
this._log(LogLevel.Log, message);
}
private _log(level: LogLevel, message: string): void {
if (this._getNumericalLevel(level) > this._maxLevel) {
return;
}
log(this._console, level, message);
}
private _getNumericalLevel(level: LogLevel): number {
const numericLevel = this._levelMap.get(level);
debug.assert(numericLevel !== undefined, 'Logger: unknown log level.');
return numericLevel !== undefined ? numericLevel : 2;
}
}
export function log(console: ConsoleInterface, logType: LogLevel, msg: string) {
switch (logType) {
case LogLevel.Log:
console.log(msg);
break;
case LogLevel.Info:
console.info(msg);
break;
case LogLevel.Warn:
console.warn(msg);
break;
case LogLevel.Error:
console.error(msg);
break;
default:
debug.fail(`${logType} is not expected`);
}
}

View File

@ -13,6 +13,7 @@
// * NOTE * except tests, this should be only file that import "fs"
import * as chokidar from 'chokidar';
import * as fs from 'fs';
import * as os from 'os';
import { ConsoleInterface, NullConsole } from './console';
import { createDeferred } from './deferred';
@ -63,6 +64,7 @@ export interface FileSystem {
// Async I/O
readFile(path: string): Promise<Buffer>;
readFileText(path: string, encoding?: string): Promise<string>;
tmpdir(): string;
}
export interface FileWatcherProvider {
@ -176,6 +178,10 @@ class RealFileSystem implements FileSystem {
});
return d.promise;
}
tmpdir() {
return os.tmpdir();
}
}
class ChokidarFileWatcherProvider implements FileWatcherProvider {
@ -223,12 +229,12 @@ class ChokidarFileWatcherProvider implements FileWatcherProvider {
const watcher = chokidar.watch(paths, watcherOptions);
watcher.on('error', (_) => {
this._console.log('Error returned from file system watcher.');
this._console.error('Error returned from file system watcher.');
});
// Detect if for some reason the native watcher library fails to load
if (_isMacintosh && !watcher.options.useFsEvents) {
this._console.log('Watcher could not use native fsevents library. File system watcher disabled.');
this._console.info('Watcher could not use native fsevents library. File system watcher disabled.');
}
return watcher;

View File

@ -7,6 +7,7 @@
* Pathname utility functions.
*/
import { randomBytes } from 'crypto';
import * as path from 'path';
import Char from 'typescript-char';
import { URI } from 'vscode-uri';
@ -450,6 +451,15 @@ export function getRelativePathFromDirectory(
fromDirectory: string,
to: string,
getCanonicalFileNameOrIgnoreCase: GetCanonicalFileName | boolean
) {
const pathComponents = getRelativePathComponentsFromDirectory(fromDirectory, to, getCanonicalFileNameOrIgnoreCase);
return combinePathComponents(pathComponents);
}
export function getRelativePathComponentsFromDirectory(
fromDirectory: string,
to: string,
getCanonicalFileNameOrIgnoreCase: GetCanonicalFileName | boolean
) {
debug.assert(
getRootLength(fromDirectory) > 0 === getRootLength(to) > 0,
@ -465,7 +475,7 @@ export function getRelativePathFromDirectory(
getCanonicalFileName
);
return combinePathComponents(pathComponents);
return pathComponents;
}
/**
@ -856,3 +866,44 @@ export function convertUriToPath(uriString: string): string {
export function convertPathToUri(path: string): string {
return URI.file(path).toString();
}
let _fsCaseSensitivity: boolean | undefined = undefined;
export function isFileSystemCaseSensitive(fs: FileSystem) {
if (_fsCaseSensitivity !== undefined) {
return _fsCaseSensitivity;
}
_fsCaseSensitivity = isFileSystemCaseSensitiveInternal(fs);
return _fsCaseSensitivity;
}
export function isFileSystemCaseSensitiveInternal(fs: FileSystem) {
let filePath: string | undefined = undefined;
try {
// Make sure tmp dir exists.
if (!fs.existsSync(fs.tmpdir())) {
fs.mkdirSync(fs.tmpdir(), { recursive: true });
}
// Make unique file name.
let name: string;
let mangledFilePath: string;
do {
name = `${randomBytes(21).toString('hex')}-a`;
filePath = path.join(fs.tmpdir(), name);
mangledFilePath = path.join(fs.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) {
return false;
} finally {
if (filePath) {
// remove temp file created
fs.unlinkSync(filePath);
}
}
}

View File

@ -61,12 +61,12 @@ export function convertPositionToOffset(position: Position, lines: TextRangeColl
export function convertRangeToTextRange(range: Range, lines: TextRangeCollection<TextRange>): TextRange | undefined {
const start = convertPositionToOffset(range.start, lines);
if (!start) {
if (start === undefined) {
return undefined;
}
const end = convertPositionToOffset(range.end, lines);
if (!end) {
if (end === undefined) {
return undefined;
}

View File

@ -75,20 +75,20 @@ export class TimingStats {
typeCheckerTime = new TimingStat();
printSummary(console: ConsoleInterface) {
console.log(`Completed in ${this.totalDuration.getDurationInSeconds()}sec`);
console.info(`Completed in ${this.totalDuration.getDurationInSeconds()}sec`);
}
printDetails(console: ConsoleInterface) {
console.log('');
console.log('Timing stats');
console.log('Find Source Files: ' + this.findFilesTime.printTime());
console.log('Read Source Files: ' + this.readFileTime.printTime());
console.log('Tokenize: ' + this.tokenizeFileTime.printTime());
console.log('Parse: ' + this.parseFileTime.printTime());
console.log('Resolve Imports: ' + this.resolveImportsTime.printTime());
console.log('Bind: ' + this.bindTime.printTime());
console.log('Check: ' + this.typeCheckerTime.printTime());
console.log('Detect Cycles: ' + this.cycleDetectionTime.printTime());
console.info('');
console.info('Timing stats');
console.info('Find Source Files: ' + this.findFilesTime.printTime());
console.info('Read Source Files: ' + this.readFileTime.printTime());
console.info('Tokenize: ' + this.tokenizeFileTime.printTime());
console.info('Parse: ' + this.parseFileTime.printTime());
console.info('Resolve Imports: ' + this.resolveImportsTime.printTime());
console.info('Bind: ' + this.bindTime.printTime());
console.info('Check: ' + this.typeCheckerTime.printTime());
console.info('Detect Cycles: ' + this.cycleDetectionTime.printTime());
}
}

View File

@ -21,6 +21,7 @@ import {
Command,
CompletionTriggerKind,
ConfigurationItem,
Connection,
ConnectionOptions,
createConnection,
Diagnostic,
@ -30,11 +31,9 @@ import {
DidChangeWatchedFilesNotification,
DocumentSymbol,
ExecuteCommandParams,
IConnection,
InitializeResult,
Location,
ParameterInformation,
RemoteConsole,
RemoteWindow,
SignatureInformation,
SymbolInformation,
@ -42,7 +41,7 @@ import {
WatchKind,
WorkDoneProgressReporter,
WorkspaceEdit,
} from 'vscode-languageserver';
} from 'vscode-languageserver/node';
import { AnalysisResults } from './analyzer/analysis';
import { ImportResolver } from './analyzer/importResolver';
@ -57,7 +56,7 @@ import {
getDiagnosticSeverityOverrides,
} from './common/commandLineOptions';
import { ConfigOptions, getDiagLevelDiagnosticRules } from './common/configOptions';
import { ConsoleInterface } from './common/console';
import { ConsoleInterface, ConsoleWithLogLevel, LogLevel } from './common/console';
import { createDeferred, Deferred } from './common/deferred';
import { Diagnostic as AnalyzerDiagnostic, DiagnosticCategory } from './common/diagnostic';
import { DiagnosticRule } from './common/diagnosticRules';
@ -94,6 +93,7 @@ export interface ServerSettings {
watchForSourceChanges?: boolean;
watchForLibraryChanges?: boolean;
diagnosticSeverityOverrides?: DiagnosticSeverityOverridesMap;
logLevel?: LogLevel;
}
export interface WorkspaceServiceInstance {
@ -147,7 +147,7 @@ interface InternalFileWatcher extends FileWatcher {
export abstract class LanguageServerBase implements LanguageServerInterface {
// Create a connection for the server. The connection type can be changed by the process's arguments
protected _connection: IConnection = createConnection(this._GetConnectionOptions());
protected _connection: Connection = createConnection(this._GetConnectionOptions());
protected _workspaceMap: WorkspaceMap;
protected _hasConfigurationCapability = false;
protected _hasWatchFileCapability = false;
@ -170,13 +170,18 @@ export abstract class LanguageServerBase implements LanguageServerInterface {
// File system abstraction.
fs: FileSystem;
readonly console: ConsoleInterface;
constructor(private _serverOptions: ServerOptions) {
this._connection.console.log(
this.console = new ConsoleWithLogLevel(this._connection.console);
this.console.info(
`${_serverOptions.productName} language server ${
_serverOptions.version && _serverOptions.version + ' '
}starting`
);
this.fs = createFromRealFileSystem(this._connection.console, this);
this.fs = createFromRealFileSystem(this.console, this);
// Set the working directory to a known location within
// the extension directory. Otherwise the execution of
@ -188,7 +193,7 @@ export abstract class LanguageServerBase implements LanguageServerInterface {
// Stash the base directory into a global variable.
(global as any).__rootDirectory = _serverOptions.rootDirectory;
this._connection.console.log(`Server root directory: ${_serverOptions.rootDirectory}`);
this.console.info(`Server root directory: ${_serverOptions.rootDirectory}`);
// Create workspace map.
this._workspaceMap = new WorkspaceMap(this);
@ -271,11 +276,6 @@ export abstract class LanguageServerBase implements LanguageServerInterface {
this._serverOptions.extension = extension;
}
// Provides access to logging to the client output window.
get console(): RemoteConsole {
return this._connection.console;
}
// Provides access to the client's window.
get window(): RemoteWindow {
return this._connection.window;
@ -284,11 +284,11 @@ export abstract class LanguageServerBase implements LanguageServerInterface {
// Creates a service instance that's used for analyzing a
// program within a workspace.
createAnalyzerService(name: string): AnalyzerService {
this._connection.console.log(`Starting service instance "${name}"`);
this.console.log(`Starting service instance "${name}"`);
const service = new AnalyzerService(
name,
this.fs,
this._connection.console,
this.console,
this.createImportResolver.bind(this),
undefined,
this._serverOptions.extension,
@ -435,7 +435,7 @@ export abstract class LanguageServerBase implements LanguageServerInterface {
);
this._connection.onDidChangeConfiguration((params) => {
this._connection.console.log(`Received updated settings`);
this.console.log(`Received updated settings`);
if (params?.settings) {
this._defaultClientConfig = params?.settings;
}
@ -507,7 +507,7 @@ export abstract class LanguageServerBase implements LanguageServerInterface {
return locations.map((loc) => Location.create(convertPathToUri(loc.path), loc.range));
} finally {
progress.done();
progress.reporter.done();
source.dispose();
}
});
@ -788,7 +788,7 @@ export abstract class LanguageServerBase implements LanguageServerInterface {
try {
executeCommand(source.token);
} finally {
progress.done();
progress.reporter.done();
source.dispose();
}
} else {
@ -812,6 +812,10 @@ export abstract class LanguageServerBase implements LanguageServerInterface {
});
});
if (!this._progressReporter.isEnabled(results)) {
return;
}
// Update progress.
if (results.filesRequiringAnalysis > 0) {
this._progressReporter.begin();
@ -830,6 +834,10 @@ export abstract class LanguageServerBase implements LanguageServerInterface {
async updateSettingsForWorkspace(workspace: WorkspaceServiceInstance): Promise<void> {
const serverSettings = await this.getSettings(workspace);
// Set logging level first.
(this.console as ConsoleWithLogLevel).level = serverSettings.logLevel ?? LogLevel.Info;
this.updateOptionsAndRestartService(workspace, serverSettings);
workspace.disableLanguageServices = !!serverSettings.disableLanguageServices;
workspace.disableOrganizeImports = !!serverSettings.disableOrganizeImports;
@ -846,19 +854,41 @@ export abstract class LanguageServerBase implements LanguageServerInterface {
AnalyzerServiceExecutor.runWithOptions(this.rootPath, workspace, serverSettings, typeStubTargetImportName);
}
protected convertLogLevel(logLevel?: string): LogLevel {
if (!logLevel) {
return LogLevel.Info;
}
switch (logLevel.toLowerCase()) {
case 'error':
return LogLevel.Error;
case 'warning':
return LogLevel.Warn;
case 'info':
return LogLevel.Info;
case 'trace':
return LogLevel.Log;
default:
return LogLevel.Info;
}
}
private async _getProgressReporter(
workDoneToken: string | number | undefined,
reporter: WorkDoneProgressReporter,
clientReporter: WorkDoneProgressReporter,
title: string
) {
if (workDoneToken) {
return reporter;
return { reporter: clientReporter, token: CancellationToken.None };
}
const serverInitiatedReporter = await this._connection.window.createWorkDoneProgress();
serverInitiatedReporter.begin(title, undefined, undefined, true);
return serverInitiatedReporter;
return {
reporter: serverInitiatedReporter,
token: serverInitiatedReporter.token,
};
}
private _GetConnectionOptions(): ConnectionOptions {
@ -913,7 +943,7 @@ export abstract class LanguageServerBase implements LanguageServerInterface {
// Tell all of the services that the user is actively
// interacting with one or more editors, so they should
// back off from performing any work.
this._workspaceMap.forEach((workspace) => {
this._workspaceMap.forEach((workspace: { serviceInstance: { recordUserInteractionTime: () => void } }) => {
workspace.serviceInstance.recordUserInteractionTime();
});
}

View File

@ -31,6 +31,13 @@ import { ParseResults } from '../parser/parser';
export type ModuleSymbolMap = Map<string, SymbolTable>;
export type ImportName = string;
export type FullImportName = string;
export type FilePath = string;
export type ImportsMap = Map<FullImportName, FilePath>;
export type ImportNameMap = Map<ImportName, ImportsMap>;
// Build a map of all modules within this program and the module-
// level scope that contains the symbol table for the module.
export function buildModuleSymbolsMap(files: SourceFileInfo[], token: CancellationToken): ModuleSymbolMap {
@ -63,13 +70,22 @@ export interface AutoImportResult {
alias?: string;
}
interface ImportParts {
namePart?: string;
importName: string;
importSource: string;
filePath: string;
moduleAndType: ModuleNameAndType;
}
export class AutoImporter {
constructor(
private _configOptions: ConfigOptions,
private _filePath: string,
private _importResolver: ImportResolver,
private _parseResults: ParseResults,
private _moduleSymbolMap: ModuleSymbolMap
private _moduleSymbolMap: ModuleSymbolMap,
private _packageMap?: ImportNameMap
) {}
getAutoImportCandidates(
@ -80,8 +96,61 @@ export class AutoImporter {
token: CancellationToken
) {
const results: AutoImportResult[] = [];
const importStatements = getTopLevelImports(this._parseResults.parseTree);
this._addImportsFromModuleMap(word, similarityLimit, excludes, aliasName, importStatements, results, token);
this._addImportsFromPackages(word, similarityLimit, excludes, aliasName, importStatements, results, token);
return results;
}
private _addImportsFromPackages(
word: string,
similarityLimit: number,
excludes: string[],
aliasName: string | undefined,
importStatements: ImportStatements,
results: AutoImportResult[],
token: CancellationToken
) {
if (!this._packageMap) {
return;
}
this._packageMap.forEach((imports, name) => {
throwIfCancellationRequested(token);
// Don't offer imports from files that are named with private
// naming semantics like "_ast.py".
if (SymbolNameUtils.isPrivateOrProtectedName(name)) {
return;
}
const isSimilar = this._isSimilar(word, name, similarityLimit);
if (!isSimilar) {
return;
}
imports.forEach((filePath, fullImport) => {
throwIfCancellationRequested(token);
const importParts = this._getImportParts(filePath);
if (!importParts) {
return;
}
this._addAutoImports(importParts, excludes, importStatements, aliasName, results);
});
});
}
private _addImportsFromModuleMap(
word: string,
similarityLimit: number,
excludes: string[],
aliasName: string | undefined,
importStatements: ImportStatements,
results: AutoImportResult[],
token: CancellationToken
) {
this._moduleSymbolMap.forEach((symbolTable, filePath) => {
throwIfCancellationRequested(token);
@ -161,7 +230,6 @@ export class AutoImporter {
const fileDir = getDirectoryPath(filePath);
const initPathPy = combinePaths(fileDir, '__init__.py');
const initPathPyi = initPathPy + 'i';
const isStubFile = filePath.endsWith('.pyi');
const hasInit = this._moduleSymbolMap.has(initPathPy) || this._moduleSymbolMap.has(initPathPyi);
@ -172,66 +240,78 @@ export class AutoImporter {
return;
}
let importNamePart: string | undefined;
let name: string;
let importSource: string;
let moduleNameAndType: ModuleNameAndType;
if (hasInit) {
importNamePart = stripFileExtension(getFileName(filePath));
moduleNameAndType = this._getModuleNameAndTypeFromFilePath(getDirectoryPath(filePath));
importSource = moduleNameAndType.moduleName;
// See if we can import module as "import xxx"
if (importNamePart === '__init__') {
importNamePart = undefined;
name = importSource;
} else {
name = importNamePart;
}
} else {
// We don't have init.py[i] but this file is a stub file.
// See whether we can import it as "import xx"
importNamePart = undefined;
moduleNameAndType = this._getModuleNameAndTypeFromFilePath(filePath);
name = importSource = moduleNameAndType.moduleName;
}
if (!importSource) {
const importParts = this._getImportParts(filePath);
if (!importParts) {
return;
}
const isSimilar = this._isSimilar(word, name, similarityLimit);
const isSimilar = this._isSimilar(word, importParts.importName, similarityLimit);
if (!isSimilar) {
return;
}
const alreadyIncluded = this._containsName(name, importSource, excludes, results);
if (alreadyIncluded) {
this._addAutoImports(importParts, excludes, importStatements, aliasName, results);
});
}
private _addAutoImports(
importParts: ImportParts,
excludes: string[],
importStatements: ImportStatements,
aliasName: string | undefined,
results: AutoImportResult[]
) {
const alreadyIncluded = this._containsName(importParts.importName, importParts.importSource, excludes, results);
if (alreadyIncluded) {
return;
}
const importGroup = this._getImportGroupFromModuleNameAndType(importParts.moduleAndType);
const autoImportTextEdits = this._getTextEditsForAutoImportByFilePath(
importParts.namePart,
importStatements,
importParts.filePath,
importParts.importSource,
importGroup,
aliasName
);
results.push({
name: importParts.importName,
alias: aliasName,
symbol: undefined,
source: importParts.importSource,
edits: autoImportTextEdits,
isImportFrom: !!importParts.namePart,
});
}
private _getImportParts(filePath: string) {
const name = stripFileExtension(getFileName(filePath));
// See if we can import module as "import xxx"
if (name === '__init__') {
return createImportParts(this._getModuleNameAndTypeFromFilePath(getDirectoryPath(filePath)));
}
return createImportParts(this._getModuleNameAndTypeFromFilePath(filePath));
function createImportParts(module: ModuleNameAndType) {
const importSource = module.moduleName;
if (!importSource) {
return;
}
const importGroup = this._getImportGroupFromModuleNameAndType(moduleNameAndType);
const autoImportTextEdits = this._getTextEditsForAutoImportByFilePath(
importNamePart,
importStatements,
const index = importSource.lastIndexOf('.');
const importNamePart = index > 0 ? importSource.substring(index + 1) : undefined;
return {
namePart: importNamePart,
importName: index > 0 ? importNamePart! : importSource,
importSource: index > 0 ? importSource.substring(0, index) : importSource,
filePath,
importSource,
importGroup,
aliasName
);
results.push({
name: name,
alias: aliasName,
symbol: undefined,
source: importSource,
edits: autoImportTextEdits,
isImportFrom: !!importNamePart,
});
});
return results;
moduleAndType: module,
};
}
}
private _isSimilar(word: string, name: string, similarityLimit: number) {

View File

@ -10,9 +10,9 @@ import {
CodeAction,
CodeActionParams,
Command,
Connection,
ExecuteCommandParams,
IConnection,
} from 'vscode-languageserver';
} from 'vscode-languageserver/node';
import { isMainThread } from 'worker_threads';
import { AnalysisResults } from './analyzer/analysis';
@ -20,6 +20,7 @@ import { BackgroundAnalysis } from './backgroundAnalysis';
import { BackgroundAnalysisBase } from './backgroundAnalysisBase';
import { CommandController } from './commands/commandController';
import { getCancellationFolderName } from './common/cancellationUtils';
import { LogLevel } from './common/console';
import { isDebugMode, isString } from './common/core';
import { convertUriToPath, getDirectoryPath, normalizeSlashes } from './common/pathUtils';
import { ProgressReporter } from './common/progressReporter';
@ -48,6 +49,7 @@ class PyrightServer extends LanguageServerBase {
const serverSettings: ServerSettings = {
watchForSourceChanges: true,
diagnosticSeverityOverrides: {},
logLevel: LogLevel.Info,
};
try {
@ -86,6 +88,7 @@ class PyrightServer extends LanguageServerBase {
serverSettings.openFilesOnly = !!pythonAnalysisSection.openFilesOnly;
}
serverSettings.logLevel = this.convertLogLevel(pythonAnalysisSection.logLevel);
serverSettings.autoSearchPaths = !!pythonAnalysisSection.autoSearchPaths;
const extraPaths = pythonAnalysisSection.extraPaths;
@ -115,7 +118,7 @@ class PyrightServer extends LanguageServerBase {
serverSettings.typeCheckingMode = undefined;
}
} catch (error) {
this.console.log(`Error reading settings: ${error}`);
this.console.error(`Error reading settings: ${error}`);
}
return serverSettings;
}
@ -150,10 +153,10 @@ class PyrightServer extends LanguageServerBase {
}
}
function reporterFactory(connection: IConnection): ProgressReporter {
function reporterFactory(connection: Connection): ProgressReporter {
return {
isEnabled(data: AnalysisResults): boolean {
return !data.checkingOnlyOpenFiles;
return true;
},
begin(): void {

View File

@ -87,7 +87,7 @@ test('FileSpecNotAnArray', () => {
service.test_getConfigOptions(commandLineOptions);
// The method should return a default config and log an error.
assert(nullConsole.logCount > 0);
assert(nullConsole.infoCount > 0);
});
test('FileSpecNotAString', () => {
@ -101,7 +101,7 @@ test('FileSpecNotAString', () => {
service.test_getConfigOptions(commandLineOptions);
// The method should return a default config and log an error.
assert(nullConsole.logCount > 0);
assert(nullConsole.infoCount > 0);
});
test('SomeFileSpecsAreInvalid', () => {
@ -140,7 +140,7 @@ test('ConfigBadJson', () => {
service.test_getConfigOptions(commandLineOptions);
// The method should return a default config and log an error.
assert(nullConsole.logCount > 0);
assert(nullConsole.infoCount > 0);
});
test('FindExecEnv1', () => {

View File

@ -0,0 +1,29 @@
/// <reference path="fourslash.ts" />
// @filename: package1/__init__.py
// @library: true
//// from .subpackage import func1
// @filename: package1/subpackage.py
// @library: true
//// def func1():
//// '''func1 docs'''
//// return True
// @filename: typings/package1/__init__.pyi
//// from .subpackage import func1
// @filename: typings/package1/subpackage/__init__.pyi
//// def func1() -> bool: ...
// @filename: test.py
//// from package1 import func1
////
//// print([|/*func1_docs*/func1|]())
helper.verifyHover({
func1_docs: {
value: '```python\n(function) func1: () -> bool\n```\nfunc1 docs',
kind: 'markdown',
},
});

View File

@ -0,0 +1,29 @@
/// <reference path="fourslash.ts" />
// @filename: package1/__init__.py
// @library: true
//// from .subpackage import func1
// @filename: package1/subpackage/__init__.py
// @library: true
//// def func1():
//// '''func1 docs'''
//// return True
// @filename: typings/package1/__init__.pyi
//// from .subpackage import func1
// @filename: typings/package1/subpackage.pyi
//// def func1() -> bool: ...
// @filename: test.py
//// from package1 import func1
////
//// print([|/*func1_docs*/func1|]())
helper.verifyHover({
func1_docs: {
value: '```python\n(function) func1: () -> bool\n```\nfunc1 docs',
kind: 'markdown',
},
});

View File

@ -0,0 +1,49 @@
/// <reference path="fourslash.ts" />
// @filename: dj/__init__.py
// @library: true
//// # empty
// @filename: dj/db/__init__.py
// @library: true
//// # empty
// @filename: dj/db/models/__init__.py
// @library: true
//// from dj.db.models.base import Model
// @filename: dj/db/models/base.py
// @library: true
//// class Model:
//// def clean_fields(self):
//// '''clean_fields docs'''
//// pass
// @filename: typings/dj/__init__.pyi
//// # empty
// @filename: typings/dj/db/__init__.pyi
//// # empty
// @filename: typings/dj/db/models/__init__.pyi
//// from .base import Model as Model
// @filename: typings/dj/db/models/base.pyi
//// class Model:
//// def clean_fields(self) -> None: ...
// @filename: test.py
//// from dj.db import models
////
//// class Person(models.Model):
//// pass
////
//// p = Person()
//// p.[|/*marker*/clean_fields|]()
helper.verifyHover({
marker: {
value: '```python\n(method) clean_fields: () -> None\n```\nclean\\_fields docs',
kind: 'markdown',
},
});

View File

@ -0,0 +1,35 @@
/// <reference path="fourslash.ts" />
// @filename: package1-stubs/__init__.pyi
// @library: true
//// from .api import func1
// @filename: package1-stubs/api.pyi
// @library: true
//// def func1() -> bool: ...
// @filename: package1/__init__.py
// @library: true
//// from .api import func1
// @filename: package1/api.py
// @library: true
//// def func1():
//// '''func1 docs'''
//// return True
// @filename: test.py
//// import package1
////
//// print(package1.[|/*marker*/func1|]())
helper.verifyHover({
marker: {
value: '```python\n(function) func1: () -> bool\n```\nfunc1 docs',
kind: 'markdown',
},
marker2: {
value: '```python\n(function) func2: () -> bool\n```\nfunc2 docs',
kind: 'markdown',
},
});

View File

@ -0,0 +1,23 @@
/// <reference path="fourslash.ts" />
// @filename: requests/__init__.py
// @library: true
//// from .api import head
// @filename: requests/api.py
// @library: true
//// def head(url, **kwargs):
//// r"""Sends a HEAD request."""
//// pass
// @filename: test.py
//// import requests
////
//// print(requests.[|/*marker*/head|](''))
helper.verifyHover({
marker: {
value: '```python\n(function) head: (url: str | bytes, **kwargs) -> Response\n```\nSends a HEAD request.',
kind: 'markdown',
},
});

View File

@ -56,11 +56,11 @@ export class TestLanguageService implements LanguageServerInterface {
async getSettings(workspace: WorkspaceServiceInstance): Promise<ServerSettings> {
const settings: ServerSettings = {
venvPath: this._workspace.serviceInstance.test_configOptions.venvPath,
pythonPath: this._workspace.serviceInstance.test_configOptions.pythonPath,
typeshedPath: this._workspace.serviceInstance.test_configOptions.typeshedPath,
openFilesOnly: this._workspace.serviceInstance.test_configOptions.checkOnlyOpenFiles,
useLibraryCodeForTypes: this._workspace.serviceInstance.test_configOptions.useLibraryCodeForTypes,
venvPath: this._workspace.serviceInstance.getConfigOptions().venvPath,
pythonPath: this._workspace.serviceInstance.getConfigOptions().pythonPath,
typeshedPath: this._workspace.serviceInstance.getConfigOptions().typeshedPath,
openFilesOnly: this._workspace.serviceInstance.getConfigOptions().checkOnlyOpenFiles,
useLibraryCodeForTypes: this._workspace.serviceInstance.getConfigOptions().useLibraryCodeForTypes,
disableLanguageServices: this._workspace.disableLanguageServices,
};

View File

@ -174,7 +174,7 @@ export class TestState {
}
get configOptions(): ConfigOptions {
return this.workspace.serviceInstance.test_configOptions;
return this.workspace.serviceInstance.getConfigOptions();
}
get program(): Program {
@ -564,6 +564,8 @@ export class TestState {
},
verifyCodeActionCount?: boolean
): Promise<any> {
// make sure we don't use cache built from other tests
this.workspace.serviceInstance.invalidateAndForceReanalysis();
this._analyze();
for (const range of this.getRanges()) {

View File

@ -319,6 +319,10 @@ export class TestFileSystem implements FileSystem {
return MODULE_PATH;
}
tmpdir(): string {
return pathUtil.normalizeSlashes('/tmp');
}
private _scan(path: string, stats: Stats, axis: Axis, traversal: Traversal, noFollow: boolean, results: string[]) {
if (axis === 'ancestors-or-self' || axis === 'self' || axis === 'descendants-or-self') {
if (!traversal.accept || traversal.accept(path, stats)) {

View File

@ -31,6 +31,7 @@ import {
getWildcardRegexPattern,
getWildcardRoot,
hasTrailingDirectorySeparator,
isFileSystemCaseSensitiveInternal,
isRootedDiskPath,
normalizeSlashes,
reducePathComponents,
@ -38,6 +39,7 @@ import {
stripFileExtension,
stripTrailingDirectorySeparator,
} from '../common/pathUtils';
import * as vfs from './harness/vfs/filesystem';
test('getPathComponents1', () => {
const components = getPathComponents('');
@ -297,3 +299,13 @@ test('getRelativePath', () => {
normalizeSlashes('./d/e/f')
);
});
test('CaseSensitivity', () => {
const cwd = normalizeSlashes('/');
const fsCaseInsensitive = new vfs.TestFileSystem(/*ignoreCase*/ true, { cwd });
assert.equal(isFileSystemCaseSensitiveInternal(fsCaseInsensitive), false);
const fsCaseSensitive = new vfs.TestFileSystem(/*ignoreCase*/ false, { cwd });
assert.equal(isFileSystemCaseSensitiveInternal(fsCaseSensitive), true);
});