mirror of
https://github.com/microsoft/pyright.git
synced 2024-09-10 23:45:54 +03:00
Module abbreviation infrastructure, remove unused mappings on completion resolve, other (#1135)
Rollup of: - Verify `package-lock.json` state in checks to avoid lerna modifying things. - Add infrastructure for module abbreviations in completions. - Removed unused auto-import mapping from `completion/resolve` handler. - Add `isInImport` to completion data for checking the completion context. - Allow completion verification in fourslash tests to be overridden.
This commit is contained in:
parent
4634309d2a
commit
f071b877b5
45
build/checkLockIndent.js
Normal file
45
build/checkLockIndent.js
Normal file
@ -0,0 +1,45 @@
|
|||||||
|
/* eslint-disable @typescript-eslint/no-var-requires */
|
||||||
|
//@ts-check
|
||||||
|
|
||||||
|
// Lerna doesn't do a good job preserving the indention in lock files.
|
||||||
|
// Check that the lock files are still indented correctly, otherwise
|
||||||
|
// the change will cause problems with merging and the updateDeps script.
|
||||||
|
|
||||||
|
const detectIndent = require('detect-indent');
|
||||||
|
const fsExtra = require('fs-extra');
|
||||||
|
const util = require('util');
|
||||||
|
const glob = util.promisify(require('glob'));
|
||||||
|
|
||||||
|
async function findPackageLocks() {
|
||||||
|
const lernaFile = await fsExtra.readFile('lerna.json', 'utf-8');
|
||||||
|
|
||||||
|
/** @type {{ packages: string[] }} */
|
||||||
|
const lernaConfig = JSON.parse(lernaFile);
|
||||||
|
|
||||||
|
const matches = await Promise.all(lernaConfig.packages.map((pattern) => glob(pattern + '/package-lock.json')));
|
||||||
|
return ['package-lock.json'].concat(...matches);
|
||||||
|
}
|
||||||
|
|
||||||
|
async function main() {
|
||||||
|
const locks = await findPackageLocks();
|
||||||
|
|
||||||
|
let ok = true;
|
||||||
|
|
||||||
|
for (const filepath of locks) {
|
||||||
|
const input = await fsExtra.readFile(filepath, 'utf-8');
|
||||||
|
const indent = detectIndent(input);
|
||||||
|
|
||||||
|
if (indent.indent !== ' ') {
|
||||||
|
ok = false;
|
||||||
|
console.error(`${filepath} has invalid indent "${indent.indent}"`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!ok) {
|
||||||
|
console.error('Lerna may have modified package-lock.json during bootstrap.');
|
||||||
|
console.error('You may need to revert any package-lock changes and rerun install:all.');
|
||||||
|
process.exit(1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
main();
|
20
package-lock.json
generated
20
package-lock.json
generated
@ -4121,9 +4121,9 @@
|
|||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
"detect-indent": {
|
"detect-indent": {
|
||||||
"version": "5.0.0",
|
"version": "6.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/detect-indent/-/detect-indent-5.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/detect-indent/-/detect-indent-6.0.0.tgz",
|
||||||
"integrity": "sha1-OHHMCmoALow+Wzz38zYmRnXwa50=",
|
"integrity": "sha512-oSyFlqaTHCItVRGK5RmrmjB+CmaMOW7IaNA/kdxqhoa6d17j/5ce9O9eWXmV/KEdRwqpQA+Vqe8a8Bsybu4YnA==",
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
"dezalgo": {
|
"dezalgo": {
|
||||||
@ -10413,6 +10413,12 @@
|
|||||||
"write-file-atomic": "^2.4.2"
|
"write-file-atomic": "^2.4.2"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"detect-indent": {
|
||||||
|
"version": "5.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/detect-indent/-/detect-indent-5.0.0.tgz",
|
||||||
|
"integrity": "sha1-OHHMCmoALow+Wzz38zYmRnXwa50=",
|
||||||
|
"dev": true
|
||||||
|
},
|
||||||
"make-dir": {
|
"make-dir": {
|
||||||
"version": "2.1.0",
|
"version": "2.1.0",
|
||||||
"resolved": "https://registry.npmjs.org/make-dir/-/make-dir-2.1.0.tgz",
|
"resolved": "https://registry.npmjs.org/make-dir/-/make-dir-2.1.0.tgz",
|
||||||
@ -10459,6 +10465,14 @@
|
|||||||
"pify": "^3.0.0",
|
"pify": "^3.0.0",
|
||||||
"sort-keys": "^2.0.0",
|
"sort-keys": "^2.0.0",
|
||||||
"write-file-atomic": "^2.0.0"
|
"write-file-atomic": "^2.0.0"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"detect-indent": {
|
||||||
|
"version": "5.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/detect-indent/-/detect-indent-5.0.0.tgz",
|
||||||
|
"integrity": "sha1-OHHMCmoALow+Wzz38zYmRnXwa50=",
|
||||||
|
"dev": true
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -10,13 +10,14 @@
|
|||||||
"build:extension:dev": "cd packages/vscode-pyright && npm run webpack",
|
"build:extension:dev": "cd packages/vscode-pyright && npm run webpack",
|
||||||
"build:cli:dev": "cd packages/pyright && npm run webpack",
|
"build:cli:dev": "cd packages/pyright && npm run webpack",
|
||||||
"watch:extension": "cd packages/vscode-pyright && npm run webpack-dev",
|
"watch:extension": "cd packages/vscode-pyright && npm run webpack-dev",
|
||||||
"check": "npm run check:syncpack && npm run check:eslint && npm run check:prettier",
|
"check": "npm run check:syncpack && npm run check:eslint && npm run check:prettier && npm run check:lockindent",
|
||||||
"check:syncpack": "syncpack list-mismatches",
|
"check:syncpack": "syncpack list-mismatches",
|
||||||
"fix:syncpack": "syncpack fix-mismatches --indent \" \" && npm run install:all",
|
"fix:syncpack": "syncpack fix-mismatches --indent \" \" && npm run install:all",
|
||||||
"check:eslint": "eslint .",
|
"check:eslint": "eslint .",
|
||||||
"fix:eslint": "eslint --fix .",
|
"fix:eslint": "eslint --fix .",
|
||||||
"check:prettier": "prettier -c .",
|
"check:prettier": "prettier -c .",
|
||||||
"fix:prettier": "prettier --write ."
|
"fix:prettier": "prettier --write .",
|
||||||
|
"check:lockindent": "node ./build/checkLockIndent.js"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@types/fs-extra": "^9.0.2",
|
"@types/fs-extra": "^9.0.2",
|
||||||
@ -25,6 +26,7 @@
|
|||||||
"@types/yargs": "^15.0.9",
|
"@types/yargs": "^15.0.9",
|
||||||
"@typescript-eslint/eslint-plugin": "^4.6.0",
|
"@typescript-eslint/eslint-plugin": "^4.6.0",
|
||||||
"@typescript-eslint/parser": "^4.6.0",
|
"@typescript-eslint/parser": "^4.6.0",
|
||||||
|
"detect-indent": "^6.0.0",
|
||||||
"eslint": "^7.12.0",
|
"eslint": "^7.12.0",
|
||||||
"eslint-config-prettier": "^6.14.0",
|
"eslint-config-prettier": "^6.14.0",
|
||||||
"eslint-plugin-simple-import-sort": "^5.0.3",
|
"eslint-plugin-simple-import-sort": "^5.0.3",
|
||||||
|
@ -45,10 +45,11 @@ import {
|
|||||||
AutoImporter,
|
AutoImporter,
|
||||||
AutoImportResult,
|
AutoImportResult,
|
||||||
buildModuleSymbolsMap,
|
buildModuleSymbolsMap,
|
||||||
|
getAutoImportCandidatesForAbbr,
|
||||||
ModuleSymbolMap,
|
ModuleSymbolMap,
|
||||||
} from '../languageService/autoImporter';
|
} from '../languageService/autoImporter';
|
||||||
import { CallHierarchyProvider } from '../languageService/callHierarchyProvider';
|
import { CallHierarchyProvider } from '../languageService/callHierarchyProvider';
|
||||||
import { CompletionResults } from '../languageService/completionProvider';
|
import { AbbreviationMap, CompletionResults } from '../languageService/completionProvider';
|
||||||
import { IndexOptions, IndexResults, WorkspaceSymbolCallback } from '../languageService/documentSymbolProvider';
|
import { IndexOptions, IndexResults, WorkspaceSymbolCallback } from '../languageService/documentSymbolProvider';
|
||||||
import { HoverResults } from '../languageService/hoverProvider';
|
import { HoverResults } from '../languageService/hoverProvider';
|
||||||
import { ReferenceCallback, ReferencesResult } from '../languageService/referencesProvider';
|
import { ReferenceCallback, ReferencesResult } from '../languageService/referencesProvider';
|
||||||
@ -433,16 +434,16 @@ export class Program {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
indexWorkspace(callback: (path: string, results: IndexResults) => void, token: CancellationToken) {
|
indexWorkspace(callback: (path: string, results: IndexResults) => void, token: CancellationToken): number {
|
||||||
if (!this._configOptions.indexing) {
|
if (!this._configOptions.indexing) {
|
||||||
return;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
let count = 0;
|
|
||||||
return this._runEvaluatorWithCancellationToken(token, () => {
|
return this._runEvaluatorWithCancellationToken(token, () => {
|
||||||
// Go through all workspace files to create indexing data.
|
// Go through all workspace files to create indexing data.
|
||||||
// This will cause all files in the workspace to be parsed and bound. But
|
// This will cause all files in the workspace to be parsed and bound. But
|
||||||
// _handleMemoryHighUsage will make sure we don't OOM
|
// _handleMemoryHighUsage will make sure we don't OOM
|
||||||
|
let count = 0;
|
||||||
for (const sourceFileInfo of this._sourceFileList) {
|
for (const sourceFileInfo of this._sourceFileList) {
|
||||||
if (!this._isUserCode(sourceFileInfo)) {
|
if (!this._isUserCode(sourceFileInfo)) {
|
||||||
continue;
|
continue;
|
||||||
@ -453,7 +454,7 @@ export class Program {
|
|||||||
if (results) {
|
if (results) {
|
||||||
if (++count > 2000) {
|
if (++count > 2000) {
|
||||||
this._console.warn(`Workspace indexing has hit its upper limit: 2000 files`);
|
this._console.warn(`Workspace indexing has hit its upper limit: 2000 files`);
|
||||||
return;
|
return count;
|
||||||
}
|
}
|
||||||
|
|
||||||
callback(sourceFileInfo.sourceFile.getFilePath(), results);
|
callback(sourceFileInfo.sourceFile.getFilePath(), results);
|
||||||
@ -461,6 +462,8 @@ export class Program {
|
|||||||
|
|
||||||
this._handleMemoryHighUsage();
|
this._handleMemoryHighUsage();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return count;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -955,7 +958,7 @@ export class Program {
|
|||||||
filePath: string,
|
filePath: string,
|
||||||
range: Range,
|
range: Range,
|
||||||
similarityLimit: number,
|
similarityLimit: number,
|
||||||
nameMap: Map<string, string> | undefined,
|
nameMap: AbbreviationMap | undefined,
|
||||||
libraryMap: Map<string, IndexResults> | undefined,
|
libraryMap: Map<string, IndexResults> | undefined,
|
||||||
token: CancellationToken
|
token: CancellationToken
|
||||||
): AutoImportResult[] {
|
): AutoImportResult[] {
|
||||||
@ -1002,13 +1005,10 @@ export class Program {
|
|||||||
|
|
||||||
const currentScope = getScopeForNode(currentNode);
|
const currentScope = getScopeForNode(currentNode);
|
||||||
if (currentScope) {
|
if (currentScope) {
|
||||||
const translatedWord = nameMap?.get(writtenWord);
|
const info = nameMap?.get(writtenWord);
|
||||||
if (translatedWord) {
|
if (info) {
|
||||||
// No filter is needed since we only do exact match.
|
// No scope filter is needed since we only do exact match.
|
||||||
const exactMatch = 1;
|
results.push(...getAutoImportCandidatesForAbbr(autoImporter, writtenWord, info, token));
|
||||||
results.push(
|
|
||||||
...autoImporter.getAutoImportCandidates(translatedWord, exactMatch, writtenWord, token)
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
results.push(
|
results.push(
|
||||||
@ -1354,6 +1354,7 @@ export class Program {
|
|||||||
position: Position,
|
position: Position,
|
||||||
workspacePath: string,
|
workspacePath: string,
|
||||||
format: MarkupKind,
|
format: MarkupKind,
|
||||||
|
nameMap: AbbreviationMap | undefined,
|
||||||
libraryMap: Map<string, IndexResults> | undefined,
|
libraryMap: Map<string, IndexResults> | undefined,
|
||||||
token: CancellationToken
|
token: CancellationToken
|
||||||
): Promise<CompletionResults | undefined> {
|
): Promise<CompletionResults | undefined> {
|
||||||
@ -1378,6 +1379,7 @@ export class Program {
|
|||||||
this._evaluator!,
|
this._evaluator!,
|
||||||
format,
|
format,
|
||||||
this._createSourceMapper(execEnv, /* mapCompiled */ true),
|
this._createSourceMapper(execEnv, /* mapCompiled */ true),
|
||||||
|
nameMap,
|
||||||
libraryMap,
|
libraryMap,
|
||||||
() => this._buildModuleSymbolsMap(sourceFileInfo, !!libraryMap, token),
|
() => this._buildModuleSymbolsMap(sourceFileInfo, !!libraryMap, token),
|
||||||
token
|
token
|
||||||
@ -1416,7 +1418,6 @@ export class Program {
|
|||||||
filePath: string,
|
filePath: string,
|
||||||
completionItem: CompletionItem,
|
completionItem: CompletionItem,
|
||||||
format: MarkupKind,
|
format: MarkupKind,
|
||||||
libraryMap: Map<string, IndexResults> | undefined,
|
|
||||||
token: CancellationToken
|
token: CancellationToken
|
||||||
) {
|
) {
|
||||||
return this._runEvaluatorWithCancellationToken(token, () => {
|
return this._runEvaluatorWithCancellationToken(token, () => {
|
||||||
@ -1435,8 +1436,6 @@ export class Program {
|
|||||||
this._evaluator!,
|
this._evaluator!,
|
||||||
format,
|
format,
|
||||||
this._createSourceMapper(execEnv, /* mapCompiled */ true),
|
this._createSourceMapper(execEnv, /* mapCompiled */ true),
|
||||||
libraryMap,
|
|
||||||
() => this._buildModuleSymbolsMap(sourceFileInfo, !!libraryMap, token),
|
|
||||||
completionItem,
|
completionItem,
|
||||||
token
|
token
|
||||||
);
|
);
|
||||||
|
@ -47,7 +47,7 @@ import {
|
|||||||
} from '../common/pathUtils';
|
} from '../common/pathUtils';
|
||||||
import { DocumentRange, Position, Range } from '../common/textRange';
|
import { DocumentRange, Position, Range } from '../common/textRange';
|
||||||
import { timingStats } from '../common/timing';
|
import { timingStats } from '../common/timing';
|
||||||
import { CompletionResults } from '../languageService/completionProvider';
|
import { AbbreviationMap, CompletionResults } from '../languageService/completionProvider';
|
||||||
import { IndexResults, WorkspaceSymbolCallback } from '../languageService/documentSymbolProvider';
|
import { IndexResults, WorkspaceSymbolCallback } from '../languageService/documentSymbolProvider';
|
||||||
import { HoverResults } from '../languageService/hoverProvider';
|
import { HoverResults } from '../languageService/hoverProvider';
|
||||||
import { ReferenceCallback } from '../languageService/referencesProvider';
|
import { ReferenceCallback } from '../languageService/referencesProvider';
|
||||||
@ -226,7 +226,7 @@ export class AnalyzerService {
|
|||||||
filePath: string,
|
filePath: string,
|
||||||
range: Range,
|
range: Range,
|
||||||
similarityLimit: number,
|
similarityLimit: number,
|
||||||
nameMap: Map<string, string> | undefined,
|
nameMap: AbbreviationMap | undefined,
|
||||||
token: CancellationToken
|
token: CancellationToken
|
||||||
) {
|
) {
|
||||||
return this._program.getAutoImports(
|
return this._program.getAutoImports(
|
||||||
@ -295,6 +295,7 @@ export class AnalyzerService {
|
|||||||
position: Position,
|
position: Position,
|
||||||
workspacePath: string,
|
workspacePath: string,
|
||||||
format: MarkupKind,
|
format: MarkupKind,
|
||||||
|
nameMap: AbbreviationMap | undefined,
|
||||||
token: CancellationToken
|
token: CancellationToken
|
||||||
): Promise<CompletionResults | undefined> {
|
): Promise<CompletionResults | undefined> {
|
||||||
return this._program.getCompletionsForPosition(
|
return this._program.getCompletionsForPosition(
|
||||||
@ -302,6 +303,7 @@ export class AnalyzerService {
|
|||||||
position,
|
position,
|
||||||
workspacePath,
|
workspacePath,
|
||||||
format,
|
format,
|
||||||
|
nameMap,
|
||||||
this._backgroundAnalysisProgram.getIndexing(filePath),
|
this._backgroundAnalysisProgram.getIndexing(filePath),
|
||||||
token
|
token
|
||||||
);
|
);
|
||||||
@ -313,13 +315,7 @@ export class AnalyzerService {
|
|||||||
format: MarkupKind,
|
format: MarkupKind,
|
||||||
token: CancellationToken
|
token: CancellationToken
|
||||||
) {
|
) {
|
||||||
this._program.resolveCompletionItem(
|
this._program.resolveCompletionItem(filePath, completionItem, format, token);
|
||||||
filePath,
|
|
||||||
completionItem,
|
|
||||||
format,
|
|
||||||
this._backgroundAnalysisProgram.getIndexing(filePath),
|
|
||||||
token
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
performQuickAction(
|
performQuickAction(
|
||||||
|
@ -33,7 +33,7 @@ import { DocumentRange, getEmptyRange, Position, TextRange } from '../common/tex
|
|||||||
import { TextRangeCollection } from '../common/textRangeCollection';
|
import { TextRangeCollection } from '../common/textRangeCollection';
|
||||||
import { timingStats } from '../common/timing';
|
import { timingStats } from '../common/timing';
|
||||||
import { ModuleSymbolMap } from '../languageService/autoImporter';
|
import { ModuleSymbolMap } from '../languageService/autoImporter';
|
||||||
import { CompletionResults } from '../languageService/completionProvider';
|
import { AbbreviationMap, CompletionResults } from '../languageService/completionProvider';
|
||||||
import { CompletionItemData, CompletionProvider } from '../languageService/completionProvider';
|
import { CompletionItemData, CompletionProvider } from '../languageService/completionProvider';
|
||||||
import { DefinitionProvider } from '../languageService/definitionProvider';
|
import { DefinitionProvider } from '../languageService/definitionProvider';
|
||||||
import { DocumentHighlightProvider } from '../languageService/documentHighlightProvider';
|
import { DocumentHighlightProvider } from '../languageService/documentHighlightProvider';
|
||||||
@ -790,6 +790,7 @@ export class SourceFile {
|
|||||||
evaluator: TypeEvaluator,
|
evaluator: TypeEvaluator,
|
||||||
format: MarkupKind,
|
format: MarkupKind,
|
||||||
sourceMapper: SourceMapper,
|
sourceMapper: SourceMapper,
|
||||||
|
nameMap: AbbreviationMap | undefined,
|
||||||
libraryMap: Map<string, IndexResults> | undefined,
|
libraryMap: Map<string, IndexResults> | undefined,
|
||||||
moduleSymbolsCallback: () => ModuleSymbolMap,
|
moduleSymbolsCallback: () => ModuleSymbolMap,
|
||||||
token: CancellationToken
|
token: CancellationToken
|
||||||
@ -818,8 +819,11 @@ export class SourceFile {
|
|||||||
evaluator,
|
evaluator,
|
||||||
format,
|
format,
|
||||||
sourceMapper,
|
sourceMapper,
|
||||||
libraryMap,
|
{
|
||||||
moduleSymbolsCallback,
|
nameMap,
|
||||||
|
libraryMap,
|
||||||
|
getModuleSymbolsMap: moduleSymbolsCallback,
|
||||||
|
},
|
||||||
token
|
token
|
||||||
);
|
);
|
||||||
|
|
||||||
@ -833,8 +837,6 @@ export class SourceFile {
|
|||||||
evaluator: TypeEvaluator,
|
evaluator: TypeEvaluator,
|
||||||
format: MarkupKind,
|
format: MarkupKind,
|
||||||
sourceMapper: SourceMapper,
|
sourceMapper: SourceMapper,
|
||||||
libraryMap: Map<string, IndexResults> | undefined,
|
|
||||||
moduleSymbolsCallback: () => ModuleSymbolMap,
|
|
||||||
completionItem: CompletionItem,
|
completionItem: CompletionItem,
|
||||||
token: CancellationToken
|
token: CancellationToken
|
||||||
) {
|
) {
|
||||||
@ -856,8 +858,7 @@ export class SourceFile {
|
|||||||
evaluator,
|
evaluator,
|
||||||
format,
|
format,
|
||||||
sourceMapper,
|
sourceMapper,
|
||||||
libraryMap,
|
undefined,
|
||||||
moduleSymbolsCallback,
|
|
||||||
token
|
token
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@ -49,25 +49,7 @@ export class BackgroundAnalysisBase {
|
|||||||
this._worker = worker;
|
this._worker = worker;
|
||||||
|
|
||||||
// global channel to communicate from BG channel to main thread.
|
// global channel to communicate from BG channel to main thread.
|
||||||
worker.on('message', (msg: AnalysisResponse) => {
|
worker.on('message', (msg: AnalysisResponse) => this.onMessage(msg));
|
||||||
switch (msg.requestType) {
|
|
||||||
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
|
|
||||||
// analyzing files.
|
|
||||||
this._onAnalysisCompletion(convertAnalysisResults(msg.data));
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
default:
|
|
||||||
debug.fail(`${msg.requestType} is not expected`);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
// this will catch any exception thrown from background thread,
|
// this will catch any exception thrown from background thread,
|
||||||
// print log and ignore exception
|
// print log and ignore exception
|
||||||
@ -76,6 +58,26 @@ export class BackgroundAnalysisBase {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected onMessage(msg: AnalysisResponse) {
|
||||||
|
switch (msg.requestType) {
|
||||||
|
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
|
||||||
|
// analyzing files.
|
||||||
|
this._onAnalysisCompletion(convertAnalysisResults(msg.data));
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
default:
|
||||||
|
debug.fail(`${msg.requestType} is not expected`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
setCompletionCallback(callback?: AnalysisCompleteCallback) {
|
setCompletionCallback(callback?: AnalysisCompleteCallback) {
|
||||||
this._onAnalysisCompletion = callback ?? nullCallback;
|
this._onAnalysisCompletion = callback ?? nullCallback;
|
||||||
}
|
}
|
||||||
@ -530,7 +532,7 @@ export interface AnalysisRequest {
|
|||||||
port?: MessagePort;
|
port?: MessagePort;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface AnalysisResponse {
|
export interface AnalysisResponse {
|
||||||
requestType: 'log' | 'analysisResult' | 'analysisPaused' | 'indexResult' | 'analysisDone';
|
requestType: 'log' | 'telemetry' | 'analysisResult' | 'analysisPaused' | 'indexResult' | 'analysisDone';
|
||||||
data: any;
|
data: any;
|
||||||
}
|
}
|
||||||
|
@ -127,3 +127,18 @@ export function isDebugMode() {
|
|||||||
const argv = process.execArgv.join();
|
const argv = process.execArgv.join();
|
||||||
return argv.includes('inspect') || argv.includes('debug');
|
return argv.includes('inspect') || argv.includes('debug');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
interface Thenable<T> {
|
||||||
|
then<TResult>(
|
||||||
|
onfulfilled?: (value: T) => TResult | Thenable<TResult>,
|
||||||
|
onrejected?: (reason: any) => TResult | Thenable<TResult>
|
||||||
|
): Thenable<TResult>;
|
||||||
|
then<TResult>(
|
||||||
|
onfulfilled?: (value: T) => TResult | Thenable<TResult>,
|
||||||
|
onrejected?: (reason: any) => void
|
||||||
|
): Thenable<TResult>;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function isThenable<T>(v: any): v is Thenable<T> {
|
||||||
|
return typeof v?.then === 'function';
|
||||||
|
}
|
||||||
|
@ -891,6 +891,7 @@ export abstract class LanguageServerBase implements LanguageServerInterface {
|
|||||||
position,
|
position,
|
||||||
workspacePath,
|
workspacePath,
|
||||||
this._completionDocFormat,
|
this._completionDocFormat,
|
||||||
|
undefined,
|
||||||
token
|
token
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -114,6 +114,23 @@ export function buildModuleSymbolsMap(files: SourceFileInfo[], token: Cancellati
|
|||||||
return moduleSymbolMap;
|
return moduleSymbolMap;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface AbbreviationInfo {
|
||||||
|
importFrom?: string;
|
||||||
|
importName: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getAutoImportCandidatesForAbbr(
|
||||||
|
autoImporter: AutoImporter,
|
||||||
|
abbr: string | undefined,
|
||||||
|
abbrInfo: AbbreviationInfo,
|
||||||
|
token: CancellationToken
|
||||||
|
) {
|
||||||
|
const exactMatch = 1;
|
||||||
|
return autoImporter
|
||||||
|
.getAutoImportCandidates(abbrInfo.importName, exactMatch, abbr, token)
|
||||||
|
.filter((r) => r.source === abbrInfo.importFrom && r.name === abbrInfo.importName);
|
||||||
|
}
|
||||||
|
|
||||||
export interface AutoImportResult {
|
export interface AutoImportResult {
|
||||||
name: string;
|
name: string;
|
||||||
symbol?: Symbol;
|
symbol?: Symbol;
|
||||||
|
@ -60,7 +60,6 @@ import {
|
|||||||
getMembersForModule,
|
getMembersForModule,
|
||||||
isProperty,
|
isProperty,
|
||||||
makeTypeVarsConcrete,
|
makeTypeVarsConcrete,
|
||||||
specializeType,
|
|
||||||
} from '../analyzer/typeUtils';
|
} from '../analyzer/typeUtils';
|
||||||
import { throwIfCancellationRequested } from '../common/cancellationUtils';
|
import { throwIfCancellationRequested } from '../common/cancellationUtils';
|
||||||
import { ConfigOptions } from '../common/configOptions';
|
import { ConfigOptions } from '../common/configOptions';
|
||||||
@ -85,7 +84,13 @@ import {
|
|||||||
StringNode,
|
StringNode,
|
||||||
} from '../parser/parseNodes';
|
} from '../parser/parseNodes';
|
||||||
import { ParseResults } from '../parser/parser';
|
import { ParseResults } from '../parser/parser';
|
||||||
import { AutoImporter, ModuleSymbolMap } from './autoImporter';
|
import {
|
||||||
|
AbbreviationInfo,
|
||||||
|
AutoImporter,
|
||||||
|
AutoImportResult,
|
||||||
|
getAutoImportCandidatesForAbbr,
|
||||||
|
ModuleSymbolMap,
|
||||||
|
} from './autoImporter';
|
||||||
import { IndexResults } from './documentSymbolProvider';
|
import { IndexResults } from './documentSymbolProvider';
|
||||||
|
|
||||||
const _keywords: string[] = [
|
const _keywords: string[] = [
|
||||||
@ -179,6 +184,7 @@ export interface CompletionItemData {
|
|||||||
position: Position;
|
position: Position;
|
||||||
autoImportText?: string;
|
autoImportText?: string;
|
||||||
symbolLabel?: string;
|
symbolLabel?: string;
|
||||||
|
isInImport?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
// MemberAccessInfo attempts to gather info for unknown types
|
// MemberAccessInfo attempts to gather info for unknown types
|
||||||
@ -193,11 +199,40 @@ export interface CompletionResults {
|
|||||||
memberAccessInfo?: MemberAccessInfo;
|
memberAccessInfo?: MemberAccessInfo;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export type AbbreviationMap = Map<string, AbbreviationInfo>;
|
||||||
|
|
||||||
|
export interface AutoImportMaps {
|
||||||
|
nameMap?: AbbreviationMap;
|
||||||
|
libraryMap?: Map<string, IndexResults>;
|
||||||
|
getModuleSymbolsMap: () => ModuleSymbolMap;
|
||||||
|
}
|
||||||
|
|
||||||
interface RecentCompletionInfo {
|
interface RecentCompletionInfo {
|
||||||
label: string;
|
label: string;
|
||||||
autoImportText: string;
|
autoImportText: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
interface Edits {
|
||||||
|
textEdit?: TextEdit;
|
||||||
|
additionalTextEdits?: TextEditAction[];
|
||||||
|
}
|
||||||
|
|
||||||
|
interface SymbolDetail {
|
||||||
|
isInImport?: boolean;
|
||||||
|
autoImportSource?: string;
|
||||||
|
autoImportAlias?: string;
|
||||||
|
objectThrough?: ObjectType;
|
||||||
|
edits?: Edits;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface CompletionDetail {
|
||||||
|
isInImport?: boolean;
|
||||||
|
typeDetail?: string;
|
||||||
|
documentation?: string;
|
||||||
|
autoImportText?: string;
|
||||||
|
edits?: Edits;
|
||||||
|
}
|
||||||
|
|
||||||
// We'll use a somewhat-arbitrary cutoff value here to determine
|
// We'll use a somewhat-arbitrary cutoff value here to determine
|
||||||
// whether it's sufficiently similar.
|
// whether it's sufficiently similar.
|
||||||
const similarityLimit = 0.25;
|
const similarityLimit = 0.25;
|
||||||
@ -224,8 +259,7 @@ export class CompletionProvider {
|
|||||||
private _evaluator: TypeEvaluator,
|
private _evaluator: TypeEvaluator,
|
||||||
private _format: MarkupKind,
|
private _format: MarkupKind,
|
||||||
private _sourceMapper: SourceMapper,
|
private _sourceMapper: SourceMapper,
|
||||||
private _libraryMap: Map<string, IndexResults> | undefined,
|
private _autoImportMaps: AutoImportMaps | undefined,
|
||||||
private _moduleSymbolsCallback: () => ModuleSymbolMap,
|
|
||||||
private _cancellationToken: CancellationToken
|
private _cancellationToken: CancellationToken
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
@ -513,7 +547,7 @@ export class CompletionProvider {
|
|||||||
|
|
||||||
const methodSignature = this._printMethodSignature(decl.node) + ':';
|
const methodSignature = this._printMethodSignature(decl.node) + ':';
|
||||||
const textEdit = TextEdit.replace(range, methodSignature);
|
const textEdit = TextEdit.replace(range, methodSignature);
|
||||||
this._addSymbol(name, symbol, partialName.value, completionList, undefined, textEdit);
|
this._addSymbol(name, symbol, partialName.value, completionList, { edits: { textEdit } });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@ -603,7 +637,7 @@ export class CompletionProvider {
|
|||||||
const objectThrough: ObjectType | undefined = isObject(specializedLeftType)
|
const objectThrough: ObjectType | undefined = isObject(specializedLeftType)
|
||||||
? specializedLeftType
|
? specializedLeftType
|
||||||
: undefined;
|
: undefined;
|
||||||
this._addSymbolsForSymbolTable(symbolTable, (_) => true, priorWord, objectThrough, completionList);
|
this._addSymbolsForSymbolTable(symbolTable, (_) => true, priorWord, false, objectThrough, completionList);
|
||||||
|
|
||||||
// If we don't know this type, look for a module we should stub
|
// If we don't know this type, look for a module we should stub
|
||||||
if (!leftType || isUnknown(leftType) || isUnbound(leftType)) {
|
if (!leftType || isUnknown(leftType) || isUnbound(leftType)) {
|
||||||
@ -965,46 +999,49 @@ export class CompletionProvider {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private _getAutoImportCompletions(priorWord: string, completionList: CompletionList) {
|
private _getAutoImportCompletions(priorWord: string, completionList: CompletionList) {
|
||||||
const moduleSymbolMap = this._moduleSymbolsCallback();
|
if (!this._autoImportMaps) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const moduleSymbolMap = this._autoImportMaps.getModuleSymbolsMap();
|
||||||
|
const excludes = completionList.items.filter((i) => !i.data?.autoImport).map((i) => i.label);
|
||||||
const autoImporter = new AutoImporter(
|
const autoImporter = new AutoImporter(
|
||||||
this._configOptions.findExecEnvironment(this._filePath),
|
this._configOptions.findExecEnvironment(this._filePath),
|
||||||
this._importResolver,
|
this._importResolver,
|
||||||
this._parseResults,
|
this._parseResults,
|
||||||
this._position,
|
this._position,
|
||||||
completionList.items.filter((i) => !i.data?.autoImport).map((i) => i.label),
|
excludes,
|
||||||
moduleSymbolMap,
|
moduleSymbolMap,
|
||||||
this._libraryMap
|
this._autoImportMaps.libraryMap
|
||||||
);
|
);
|
||||||
|
|
||||||
for (const result of autoImporter.getAutoImportCandidates(
|
const results: AutoImportResult[] = [];
|
||||||
priorWord,
|
const info = this._autoImportMaps.nameMap?.get(priorWord);
|
||||||
similarityLimit,
|
if (info && priorWord.length > 1 && !excludes.some((e) => e === priorWord)) {
|
||||||
undefined,
|
results.push(...getAutoImportCandidatesForAbbr(autoImporter, priorWord, info, this._cancellationToken));
|
||||||
this._cancellationToken
|
}
|
||||||
)) {
|
|
||||||
|
results.push(
|
||||||
|
...autoImporter.getAutoImportCandidates(priorWord, similarityLimit, undefined, this._cancellationToken)
|
||||||
|
);
|
||||||
|
|
||||||
|
for (const result of results) {
|
||||||
if (result.symbol) {
|
if (result.symbol) {
|
||||||
this._addSymbol(
|
this._addSymbol(result.name, result.symbol, priorWord, completionList, {
|
||||||
result.name,
|
autoImportSource: result.source,
|
||||||
result.symbol,
|
autoImportAlias: result.alias,
|
||||||
priorWord,
|
edits: { additionalTextEdits: result.edits },
|
||||||
completionList,
|
});
|
||||||
result.source,
|
|
||||||
undefined,
|
|
||||||
result.edits
|
|
||||||
);
|
|
||||||
} else {
|
} else {
|
||||||
this._addNameToCompletionList(
|
this._addNameToCompletionList(
|
||||||
result.name,
|
result.alias ?? result.name,
|
||||||
result.kind ?? CompletionItemKind.Module,
|
result.kind ?? CompletionItemKind.Module,
|
||||||
priorWord,
|
priorWord,
|
||||||
completionList,
|
completionList,
|
||||||
undefined,
|
{
|
||||||
'',
|
autoImportText: this._getAutoImportText(result.name, result.source, result.alias),
|
||||||
result.source
|
edits: { additionalTextEdits: result.edits },
|
||||||
? `\`\`\`\nfrom ${result.source} import ${result.name}\n\`\`\``
|
}
|
||||||
: `\`\`\`\nimport ${result.name}\n\`\`\``,
|
|
||||||
undefined,
|
|
||||||
result.edits
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -1040,6 +1077,7 @@ export class CompletionProvider {
|
|||||||
return !importFromNode.imports.find((imp) => imp.name.value === name);
|
return !importFromNode.imports.find((imp) => imp.name.value === name);
|
||||||
},
|
},
|
||||||
priorWord,
|
priorWord,
|
||||||
|
true,
|
||||||
undefined,
|
undefined,
|
||||||
completionList
|
completionList
|
||||||
);
|
);
|
||||||
@ -1120,7 +1158,14 @@ export class CompletionProvider {
|
|||||||
let scope = AnalyzerNodeInfo.getScope(curNode);
|
let scope = AnalyzerNodeInfo.getScope(curNode);
|
||||||
if (scope) {
|
if (scope) {
|
||||||
while (scope) {
|
while (scope) {
|
||||||
this._addSymbolsForSymbolTable(scope.symbolTable, () => true, priorWord, undefined, completionList);
|
this._addSymbolsForSymbolTable(
|
||||||
|
scope.symbolTable,
|
||||||
|
() => true,
|
||||||
|
priorWord,
|
||||||
|
false,
|
||||||
|
undefined,
|
||||||
|
completionList
|
||||||
|
);
|
||||||
scope = scope.parent;
|
scope = scope.parent;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1144,6 +1189,7 @@ export class CompletionProvider {
|
|||||||
.some((decl) => decl.type === DeclarationType.Variable);
|
.some((decl) => decl.type === DeclarationType.Variable);
|
||||||
},
|
},
|
||||||
priorWord,
|
priorWord,
|
||||||
|
false,
|
||||||
undefined,
|
undefined,
|
||||||
completionList
|
completionList
|
||||||
);
|
);
|
||||||
@ -1162,6 +1208,7 @@ export class CompletionProvider {
|
|||||||
symbolTable: SymbolTable,
|
symbolTable: SymbolTable,
|
||||||
includeSymbolCallback: (name: string) => boolean,
|
includeSymbolCallback: (name: string) => boolean,
|
||||||
priorWord: string,
|
priorWord: string,
|
||||||
|
isInImport: boolean,
|
||||||
objectThrough: ObjectType | undefined,
|
objectThrough: ObjectType | undefined,
|
||||||
completionList: CompletionList
|
completionList: CompletionList
|
||||||
) {
|
) {
|
||||||
@ -1173,16 +1220,10 @@ export class CompletionProvider {
|
|||||||
// Don't add a symbol more than once. It may have already been
|
// Don't add a symbol more than once. It may have already been
|
||||||
// added from an inner scope's symbol table.
|
// added from an inner scope's symbol table.
|
||||||
if (!completionList.items.some((item) => item.label === name)) {
|
if (!completionList.items.some((item) => item.label === name)) {
|
||||||
this._addSymbol(
|
this._addSymbol(name, symbol, priorWord, completionList, {
|
||||||
name,
|
objectThrough,
|
||||||
symbol,
|
isInImport,
|
||||||
priorWord,
|
});
|
||||||
completionList,
|
|
||||||
undefined,
|
|
||||||
undefined,
|
|
||||||
undefined,
|
|
||||||
objectThrough
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@ -1193,10 +1234,7 @@ export class CompletionProvider {
|
|||||||
symbol: Symbol,
|
symbol: Symbol,
|
||||||
priorWord: string,
|
priorWord: string,
|
||||||
completionList: CompletionList,
|
completionList: CompletionList,
|
||||||
autoImportSource?: string,
|
detail: SymbolDetail
|
||||||
textEdit?: TextEdit,
|
|
||||||
additionalTextEdits?: TextEditAction[],
|
|
||||||
objectThrough?: ObjectType
|
|
||||||
) {
|
) {
|
||||||
let primaryDecl = getLastTypedDeclaredForSymbol(symbol);
|
let primaryDecl = getLastTypedDeclaredForSymbol(symbol);
|
||||||
if (!primaryDecl) {
|
if (!primaryDecl) {
|
||||||
@ -1235,11 +1273,11 @@ export class CompletionProvider {
|
|||||||
break;
|
break;
|
||||||
|
|
||||||
case DeclarationType.Function: {
|
case DeclarationType.Function: {
|
||||||
const functionType = objectThrough
|
const functionType = detail.objectThrough
|
||||||
? this._evaluator.bindFunctionToClassOrObject(objectThrough, type, false)
|
? this._evaluator.bindFunctionToClassOrObject(detail.objectThrough, type, false)
|
||||||
: type;
|
: type;
|
||||||
if (functionType) {
|
if (functionType) {
|
||||||
if (isProperty(functionType) && objectThrough) {
|
if (isProperty(functionType) && detail.objectThrough) {
|
||||||
const propertyType =
|
const propertyType =
|
||||||
this._evaluator.getGetterTypeFromProperty(
|
this._evaluator.getGetterTypeFromProperty(
|
||||||
functionType.classType,
|
functionType.classType,
|
||||||
@ -1339,161 +1377,165 @@ export class CompletionProvider {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let autoImportText: string | undefined;
|
const autoImportText = detail.autoImportSource
|
||||||
if (autoImportSource) {
|
? this._getAutoImportText(name, detail.autoImportSource, detail.autoImportAlias)
|
||||||
if (this._format === MarkupKind.Markdown) {
|
: undefined;
|
||||||
autoImportText = `\`\`\`\nfrom ${autoImportSource} import ${name}\n\`\`\``;
|
|
||||||
} else if (this._format === MarkupKind.PlainText) {
|
|
||||||
autoImportText = `from ${autoImportSource} import ${name}`;
|
|
||||||
} else {
|
|
||||||
fail(`Unsupported markup type: ${this._format}`);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
this._addNameToCompletionList(
|
this._addNameToCompletionList(detail.autoImportAlias ?? name, itemKind, priorWord, completionList, {
|
||||||
name,
|
|
||||||
itemKind,
|
|
||||||
priorWord,
|
|
||||||
completionList,
|
|
||||||
undefined,
|
|
||||||
undefined,
|
|
||||||
autoImportText,
|
autoImportText,
|
||||||
textEdit,
|
isInImport: detail.isInImport,
|
||||||
additionalTextEdits
|
edits: detail.edits,
|
||||||
);
|
});
|
||||||
} else {
|
} else {
|
||||||
// Does the symbol have no declaration but instead has a synthesized type?
|
// Does the symbol have no declaration but instead has a synthesized type?
|
||||||
const synthesizedType = symbol.getSynthesizedType();
|
const synthesizedType = symbol.getSynthesizedType();
|
||||||
if (synthesizedType) {
|
if (synthesizedType) {
|
||||||
const itemKind: CompletionItemKind = CompletionItemKind.Variable;
|
const itemKind: CompletionItemKind = CompletionItemKind.Variable;
|
||||||
this._addNameToCompletionList(
|
this._addNameToCompletionList(name, itemKind, priorWord, completionList, {
|
||||||
name,
|
isInImport: detail.isInImport,
|
||||||
itemKind,
|
edits: detail.edits,
|
||||||
priorWord,
|
});
|
||||||
completionList,
|
|
||||||
undefined,
|
|
||||||
undefined,
|
|
||||||
undefined,
|
|
||||||
textEdit,
|
|
||||||
additionalTextEdits
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private _getAutoImportText(importName: string, importFrom?: string, importAlias?: string) {
|
||||||
|
let autoImportText: string | undefined;
|
||||||
|
if (!importFrom) {
|
||||||
|
autoImportText = `import ${importName}`;
|
||||||
|
} else {
|
||||||
|
autoImportText = `from ${importFrom} import ${importName}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (importAlias) {
|
||||||
|
autoImportText = `${autoImportText} as ${importAlias}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this._format === MarkupKind.Markdown) {
|
||||||
|
return `\`\`\`\n${autoImportText}\n\`\`\``;
|
||||||
|
} else if (this._format === MarkupKind.PlainText) {
|
||||||
|
return autoImportText;
|
||||||
|
} else {
|
||||||
|
fail(`Unsupported markup type: ${this._format}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private _addNameToCompletionList(
|
private _addNameToCompletionList(
|
||||||
name: string,
|
name: string,
|
||||||
itemKind: CompletionItemKind,
|
itemKind: CompletionItemKind,
|
||||||
filter: string,
|
filter: string,
|
||||||
completionList: CompletionList,
|
completionList: CompletionList,
|
||||||
typeDetail?: string,
|
detail?: CompletionDetail
|
||||||
documentation?: string,
|
|
||||||
autoImportText?: string,
|
|
||||||
textEdit?: TextEdit,
|
|
||||||
additionalTextEdits?: TextEditAction[]
|
|
||||||
) {
|
) {
|
||||||
const similarity = StringUtils.computeCompletionSimilarity(filter, name);
|
// Auto importer already filtered out unnecessary ones. No need to do it again.
|
||||||
|
const similarity = detail?.autoImportText ? 1 : StringUtils.computeCompletionSimilarity(filter, name);
|
||||||
if (similarity > similarityLimit) {
|
if (similarity <= similarityLimit) {
|
||||||
const completionItem = CompletionItem.create(name);
|
return;
|
||||||
completionItem.kind = itemKind;
|
|
||||||
|
|
||||||
const completionItemData: CompletionItemData = {
|
|
||||||
workspacePath: this._workspacePath,
|
|
||||||
filePath: this._filePath,
|
|
||||||
position: this._position,
|
|
||||||
};
|
|
||||||
completionItem.data = completionItemData;
|
|
||||||
|
|
||||||
if (autoImportText) {
|
|
||||||
// Force auto-import entries to the end.
|
|
||||||
completionItem.sortText = this._makeSortText(SortCategory.AutoImport, name, autoImportText);
|
|
||||||
completionItemData.autoImportText = autoImportText;
|
|
||||||
completionItem.detail = 'Auto-import';
|
|
||||||
} else if (SymbolNameUtils.isDunderName(name)) {
|
|
||||||
// Force dunder-named symbols to appear after all other symbols.
|
|
||||||
completionItem.sortText = this._makeSortText(SortCategory.DunderSymbol, name);
|
|
||||||
} else if (filter === '' && SymbolNameUtils.isPrivateOrProtectedName(name)) {
|
|
||||||
// Distinguish between normal and private symbols only if there is
|
|
||||||
// currently no filter text. Once we get a single character to filter
|
|
||||||
// upon, we'll no longer differentiate.
|
|
||||||
completionItem.sortText = this._makeSortText(SortCategory.PrivateSymbol, name);
|
|
||||||
} else {
|
|
||||||
completionItem.sortText = this._makeSortText(SortCategory.NormalSymbol, name);
|
|
||||||
}
|
|
||||||
|
|
||||||
completionItemData.symbolLabel = name;
|
|
||||||
|
|
||||||
if (this._format === MarkupKind.Markdown) {
|
|
||||||
let markdownString = '';
|
|
||||||
|
|
||||||
if (autoImportText) {
|
|
||||||
markdownString += autoImportText + '\n\n';
|
|
||||||
}
|
|
||||||
|
|
||||||
if (typeDetail) {
|
|
||||||
markdownString += '```python\n' + typeDetail + '\n```\n';
|
|
||||||
}
|
|
||||||
|
|
||||||
if (documentation) {
|
|
||||||
markdownString += '---\n';
|
|
||||||
markdownString += convertDocStringToMarkdown(documentation);
|
|
||||||
}
|
|
||||||
|
|
||||||
markdownString = markdownString.trimEnd();
|
|
||||||
|
|
||||||
if (markdownString) {
|
|
||||||
completionItem.documentation = {
|
|
||||||
kind: MarkupKind.Markdown,
|
|
||||||
value: markdownString,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
} else if (this._format === MarkupKind.PlainText) {
|
|
||||||
let plainTextString = '';
|
|
||||||
|
|
||||||
if (autoImportText) {
|
|
||||||
plainTextString += autoImportText + '\n\n';
|
|
||||||
}
|
|
||||||
|
|
||||||
if (typeDetail) {
|
|
||||||
plainTextString += typeDetail + '\n';
|
|
||||||
}
|
|
||||||
|
|
||||||
if (documentation) {
|
|
||||||
plainTextString += '\n' + convertDocStringToPlainText(documentation);
|
|
||||||
}
|
|
||||||
|
|
||||||
plainTextString = plainTextString.trimEnd();
|
|
||||||
|
|
||||||
if (plainTextString) {
|
|
||||||
completionItem.documentation = {
|
|
||||||
kind: MarkupKind.PlainText,
|
|
||||||
value: plainTextString,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
fail(`Unsupported markup type: ${this._format}`);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (textEdit) {
|
|
||||||
completionItem.textEdit = textEdit;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (additionalTextEdits) {
|
|
||||||
completionItem.additionalTextEdits = additionalTextEdits.map((te) => {
|
|
||||||
const textEdit: TextEdit = {
|
|
||||||
range: {
|
|
||||||
start: { line: te.range.start.line, character: te.range.start.character },
|
|
||||||
end: { line: te.range.end.line, character: te.range.end.character },
|
|
||||||
},
|
|
||||||
newText: te.replacementText,
|
|
||||||
};
|
|
||||||
return textEdit;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
completionList.items.push(completionItem);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const completionItem = CompletionItem.create(name);
|
||||||
|
completionItem.kind = itemKind;
|
||||||
|
|
||||||
|
const completionItemData: CompletionItemData = {
|
||||||
|
workspacePath: this._workspacePath,
|
||||||
|
filePath: this._filePath,
|
||||||
|
position: this._position,
|
||||||
|
};
|
||||||
|
|
||||||
|
if (detail?.isInImport) {
|
||||||
|
completionItemData.isInImport = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
completionItem.data = completionItemData;
|
||||||
|
|
||||||
|
if (detail?.autoImportText) {
|
||||||
|
// Force auto-import entries to the end.
|
||||||
|
completionItem.sortText = this._makeSortText(SortCategory.AutoImport, name, detail.autoImportText);
|
||||||
|
completionItemData.autoImportText = detail.autoImportText;
|
||||||
|
completionItem.detail = 'Auto-import';
|
||||||
|
} else if (SymbolNameUtils.isDunderName(name)) {
|
||||||
|
// Force dunder-named symbols to appear after all other symbols.
|
||||||
|
completionItem.sortText = this._makeSortText(SortCategory.DunderSymbol, name);
|
||||||
|
} else if (filter === '' && SymbolNameUtils.isPrivateOrProtectedName(name)) {
|
||||||
|
// Distinguish between normal and private symbols only if there is
|
||||||
|
// currently no filter text. Once we get a single character to filter
|
||||||
|
// upon, we'll no longer differentiate.
|
||||||
|
completionItem.sortText = this._makeSortText(SortCategory.PrivateSymbol, name);
|
||||||
|
} else {
|
||||||
|
completionItem.sortText = this._makeSortText(SortCategory.NormalSymbol, name);
|
||||||
|
}
|
||||||
|
|
||||||
|
completionItemData.symbolLabel = name;
|
||||||
|
|
||||||
|
if (this._format === MarkupKind.Markdown) {
|
||||||
|
let markdownString = '';
|
||||||
|
|
||||||
|
if (detail?.autoImportText) {
|
||||||
|
markdownString += detail.autoImportText + '\n\n';
|
||||||
|
}
|
||||||
|
|
||||||
|
if (detail?.typeDetail) {
|
||||||
|
markdownString += '```python\n' + detail.typeDetail + '\n```\n';
|
||||||
|
}
|
||||||
|
|
||||||
|
if (detail?.documentation) {
|
||||||
|
markdownString += '---\n';
|
||||||
|
markdownString += convertDocStringToMarkdown(detail.documentation);
|
||||||
|
}
|
||||||
|
|
||||||
|
markdownString = markdownString.trimEnd();
|
||||||
|
|
||||||
|
if (markdownString) {
|
||||||
|
completionItem.documentation = {
|
||||||
|
kind: MarkupKind.Markdown,
|
||||||
|
value: markdownString,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
} else if (this._format === MarkupKind.PlainText) {
|
||||||
|
let plainTextString = '';
|
||||||
|
|
||||||
|
if (detail?.autoImportText) {
|
||||||
|
plainTextString += detail.autoImportText + '\n\n';
|
||||||
|
}
|
||||||
|
|
||||||
|
if (detail?.typeDetail) {
|
||||||
|
plainTextString += detail.typeDetail + '\n';
|
||||||
|
}
|
||||||
|
|
||||||
|
if (detail?.documentation) {
|
||||||
|
plainTextString += '\n' + convertDocStringToPlainText(detail.documentation);
|
||||||
|
}
|
||||||
|
|
||||||
|
plainTextString = plainTextString.trimEnd();
|
||||||
|
|
||||||
|
if (plainTextString) {
|
||||||
|
completionItem.documentation = {
|
||||||
|
kind: MarkupKind.PlainText,
|
||||||
|
value: plainTextString,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
fail(`Unsupported markup type: ${this._format}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (detail?.edits?.textEdit) {
|
||||||
|
completionItem.textEdit = detail.edits.textEdit;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (detail?.edits?.additionalTextEdits) {
|
||||||
|
completionItem.additionalTextEdits = detail.edits.additionalTextEdits.map((te) => {
|
||||||
|
const textEdit: TextEdit = {
|
||||||
|
range: {
|
||||||
|
start: { line: te.range.start.line, character: te.range.start.character },
|
||||||
|
end: { line: te.range.end.line, character: te.range.end.character },
|
||||||
|
},
|
||||||
|
newText: te.replacementText,
|
||||||
|
};
|
||||||
|
return textEdit;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
completionList.items.push(completionItem);
|
||||||
}
|
}
|
||||||
|
|
||||||
private _getRecentListIndex(name: string, autoImportText: string) {
|
private _getRecentListIndex(name: string, autoImportText: string) {
|
||||||
|
@ -131,6 +131,11 @@ declare namespace _ {
|
|||||||
kind?: DocumentHighlightKind;
|
kind?: DocumentHighlightKind;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
interface AbbreviationInfo {
|
||||||
|
importFrom?: string;
|
||||||
|
importName: string;
|
||||||
|
}
|
||||||
|
|
||||||
interface Fourslash {
|
interface Fourslash {
|
||||||
getDocumentHighlightKind(m?: Marker): DocumentHighlightKind | undefined;
|
getDocumentHighlightKind(m?: Marker): DocumentHighlightKind | undefined;
|
||||||
|
|
||||||
@ -190,7 +195,8 @@ declare namespace _ {
|
|||||||
unknownMemberName?: string;
|
unknownMemberName?: string;
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
}
|
},
|
||||||
|
abbrMap?: { [abbr: string]: AbbreviationInfo }
|
||||||
): Promise<void>;
|
): Promise<void>;
|
||||||
verifySignature(map: {
|
verifySignature(map: {
|
||||||
[marker: string]: {
|
[marker: string]: {
|
||||||
|
@ -47,6 +47,7 @@ import { getStringComparer } from '../../../common/stringUtils';
|
|||||||
import { DocumentRange, Position, Range as PositionRange, rangesAreEqual, TextRange } from '../../../common/textRange';
|
import { DocumentRange, Position, Range as PositionRange, rangesAreEqual, TextRange } from '../../../common/textRange';
|
||||||
import { TextRangeCollection } from '../../../common/textRangeCollection';
|
import { TextRangeCollection } from '../../../common/textRangeCollection';
|
||||||
import { LanguageServerInterface, WorkspaceServiceInstance } from '../../../languageServerBase';
|
import { LanguageServerInterface, WorkspaceServiceInstance } from '../../../languageServerBase';
|
||||||
|
import { AbbreviationInfo } from '../../../languageService/autoImporter';
|
||||||
import { convertHoverResults } from '../../../languageService/hoverProvider';
|
import { convertHoverResults } from '../../../languageService/hoverProvider';
|
||||||
import { ParseResults } from '../../../parser/parser';
|
import { ParseResults } from '../../../parser/parser';
|
||||||
import { Tokenizer } from '../../../parser/tokenizer';
|
import { Tokenizer } from '../../../parser/tokenizer';
|
||||||
@ -94,6 +95,7 @@ export class TestState {
|
|||||||
readonly fs: vfs.TestFileSystem;
|
readonly fs: vfs.TestFileSystem;
|
||||||
readonly workspace: WorkspaceServiceInstance;
|
readonly workspace: WorkspaceServiceInstance;
|
||||||
readonly console: ConsoleInterface;
|
readonly console: ConsoleInterface;
|
||||||
|
readonly rawConfigJson: any | undefined;
|
||||||
|
|
||||||
// The current caret position in the active file
|
// The current caret position in the active file
|
||||||
currentCaretPosition = 0;
|
currentCaretPosition = 0;
|
||||||
@ -124,14 +126,13 @@ export class TestState {
|
|||||||
for (const file of testData.files) {
|
for (const file of testData.files) {
|
||||||
// if one of file is configuration file, set config options from the given json
|
// if one of file is configuration file, set config options from the given json
|
||||||
if (this._isConfig(file, ignoreCase)) {
|
if (this._isConfig(file, ignoreCase)) {
|
||||||
let configJson: any;
|
|
||||||
try {
|
try {
|
||||||
configJson = JSON.parse(file.content);
|
this.rawConfigJson = JSON.parse(file.content);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
throw new Error(`Failed to parse test ${file.fileName}: ${e.message}`);
|
throw new Error(`Failed to parse test ${file.fileName}: ${e.message}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
configOptions.initializeFromJson(configJson, 'basic', nullConsole);
|
configOptions.initializeFromJson(this.rawConfigJson, 'basic', nullConsole);
|
||||||
this._applyTestConfigOptions(configOptions);
|
this._applyTestConfigOptions(configOptions);
|
||||||
} else {
|
} else {
|
||||||
files[file.fileName] = new vfs.File(file.content, { meta: file.fileOptions, encoding: 'utf8' });
|
files[file.fileName] = new vfs.File(file.content, { meta: file.fileOptions, encoding: 'utf8' });
|
||||||
@ -817,7 +818,8 @@ export class TestState {
|
|||||||
unknownMemberName?: string;
|
unknownMemberName?: string;
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
}
|
},
|
||||||
|
abbrMap?: { [abbr: string]: AbbreviationInfo }
|
||||||
): Promise<void> {
|
): Promise<void> {
|
||||||
this._analyze();
|
this._analyze();
|
||||||
|
|
||||||
@ -836,6 +838,7 @@ export class TestState {
|
|||||||
completionPosition,
|
completionPosition,
|
||||||
this.workspace.rootPath,
|
this.workspace.rootPath,
|
||||||
docFormat,
|
docFormat,
|
||||||
|
abbrMap ? new Map<string, AbbreviationInfo>(Object.entries(abbrMap)) : undefined,
|
||||||
CancellationToken.None
|
CancellationToken.None
|
||||||
);
|
);
|
||||||
|
|
||||||
@ -866,23 +869,16 @@ export class TestState {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const actual: CompletionItem = result.completionList.items[actualIndex];
|
const actual: CompletionItem = result.completionList.items[actualIndex];
|
||||||
assert.equal(actual.label, expected.label);
|
this.verifyCompletionItem(expected, actual);
|
||||||
assert.equal(actual.detail, expected.detail);
|
|
||||||
assert.equal(actual.kind, expected.kind);
|
if (expected.documentation !== undefined) {
|
||||||
if (expectedCompletions[i].documentation !== undefined) {
|
|
||||||
if (actual.documentation === undefined) {
|
if (actual.documentation === undefined) {
|
||||||
this.program.resolveCompletionItem(
|
this.program.resolveCompletionItem(filePath, actual, docFormat, CancellationToken.None);
|
||||||
filePath,
|
|
||||||
actual,
|
|
||||||
docFormat,
|
|
||||||
undefined,
|
|
||||||
CancellationToken.None
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (MarkupContent.is(actual.documentation)) {
|
if (MarkupContent.is(actual.documentation)) {
|
||||||
assert.equal(actual.documentation.value, expected.documentation);
|
assert.strictEqual(actual.documentation.value, expected.documentation);
|
||||||
assert.equal(actual.documentation.kind, docFormat);
|
assert.strictEqual(actual.documentation.kind, docFormat);
|
||||||
} else {
|
} else {
|
||||||
assert.fail(
|
assert.fail(
|
||||||
`Unexpected type of contents object "${actual.documentation}", should be MarkupContent.`
|
`Unexpected type of contents object "${actual.documentation}", should be MarkupContent.`
|
||||||
@ -1551,4 +1547,10 @@ export class TestState {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected verifyCompletionItem(expected: _.FourSlashCompletionItem, actual: CompletionItem) {
|
||||||
|
assert.strictEqual(actual.label, expected.label);
|
||||||
|
assert.strictEqual(actual.detail, expected.detail);
|
||||||
|
assert.strictEqual(actual.kind, expected.kind);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -37,6 +37,7 @@ import {
|
|||||||
} from 'vscode-languageclient/node';
|
} from 'vscode-languageclient/node';
|
||||||
|
|
||||||
import { Commands } from 'pyright-internal/commands/commands';
|
import { Commands } from 'pyright-internal/commands/commands';
|
||||||
|
import { isThenable } from 'pyright-internal/common/core';
|
||||||
|
|
||||||
import { FileBasedCancellationStrategy } from './cancellationUtils';
|
import { FileBasedCancellationStrategy } from './cancellationUtils';
|
||||||
import { ProgressReporting } from './progress';
|
import { ProgressReporting } from './progress';
|
||||||
@ -279,10 +280,6 @@ async function getPythonPathFromPythonExtension(
|
|||||||
return undefined;
|
return undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
function isThenable<T>(v: any): v is Thenable<T> {
|
|
||||||
return typeof v?.then === 'function';
|
|
||||||
}
|
|
||||||
|
|
||||||
function installPythonPathChangedListener(
|
function installPythonPathChangedListener(
|
||||||
onDidChangeExecutionDetails: (callback: () => void) => void,
|
onDidChangeExecutionDetails: (callback: () => void) => void,
|
||||||
scopeUri: Uri | undefined,
|
scopeUri: Uri | undefined,
|
||||||
|
Loading…
Reference in New Issue
Block a user