mirror of
https://github.com/microsoft/pyright.git
synced 2024-09-11 16:06:39 +03:00
Added new eslint rule to enforce ordering of class methods by accessibility (public, protected, private).
This commit is contained in:
parent
006a22467d
commit
280d088afd
@ -28,6 +28,13 @@
|
||||
"@typescript-eslint/explicit-module-boundary-types": 0,
|
||||
"@typescript-eslint/ban-types": 0,
|
||||
"@typescript-eslint/camelcase": 0,
|
||||
"@typescript-eslint/member-ordering": [
|
||||
"error",
|
||||
{
|
||||
"classes": ["field", "constructor", ["public-get", "public-set"], "public-method", ["protected-get", "protected-set"], "protected-method", ["private-get", "private-set"], "private-method"],
|
||||
"interfaces": []
|
||||
}
|
||||
],
|
||||
"@typescript-eslint/no-empty-interface": 0,
|
||||
"@typescript-eslint/no-explicit-any": 0,
|
||||
"@typescript-eslint/no-namespace": 0,
|
||||
|
@ -244,6 +244,10 @@ export class BackgroundAnalysisProgram {
|
||||
this._backgroundAnalysis?.shutdown();
|
||||
}
|
||||
|
||||
protected _getIndices(): Indices | undefined {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
private _ensurePartialStubPackages(execEnv: ExecutionEnvironment) {
|
||||
this._backgroundAnalysis?.ensurePartialStubPackages(execEnv.root);
|
||||
return this._importResolver.ensurePartialStubPackages(execEnv);
|
||||
@ -267,10 +271,6 @@ export class BackgroundAnalysisProgram {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected _getIndices(): Indices | undefined {
|
||||
return undefined;
|
||||
}
|
||||
}
|
||||
|
||||
export type BackgroundAnalysisProgramFactory = (
|
||||
|
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@ -168,6 +168,26 @@ export class AnalyzerService {
|
||||
Extensions.createProgramExtensions(this._program, { addInterimFile: this.addInterimFile.bind(this) });
|
||||
}
|
||||
|
||||
get fs() {
|
||||
return this._backgroundAnalysisProgram.importResolver.fileSystem;
|
||||
}
|
||||
|
||||
get cancellationProvider() {
|
||||
return this._options.cancellationProvider!;
|
||||
}
|
||||
|
||||
get librarySearchPathsToWatch() {
|
||||
return this._librarySearchPathsToWatch;
|
||||
}
|
||||
|
||||
get backgroundAnalysisProgram(): BackgroundAnalysisProgram {
|
||||
return this._backgroundAnalysisProgram;
|
||||
}
|
||||
|
||||
get test_program() {
|
||||
return this._program;
|
||||
}
|
||||
|
||||
clone(
|
||||
instanceName: string,
|
||||
serviceId: string,
|
||||
@ -221,30 +241,6 @@ export class AnalyzerService {
|
||||
this._clearLibraryReanalysisTimer();
|
||||
}
|
||||
|
||||
private get _console() {
|
||||
return this._options.console!;
|
||||
}
|
||||
|
||||
private get _hostFactory() {
|
||||
return this._options.hostFactory!;
|
||||
}
|
||||
|
||||
private get _importResolverFactory() {
|
||||
return this._options.importResolverFactory!;
|
||||
}
|
||||
|
||||
get cancellationProvider() {
|
||||
return this._options.cancellationProvider!;
|
||||
}
|
||||
|
||||
get librarySearchPathsToWatch() {
|
||||
return this._librarySearchPathsToWatch;
|
||||
}
|
||||
|
||||
get backgroundAnalysisProgram(): BackgroundAnalysisProgram {
|
||||
return this._backgroundAnalysisProgram;
|
||||
}
|
||||
|
||||
static createImportResolver(fs: FileSystem, options: ConfigOptions, host: Host): ImportResolver {
|
||||
return new ImportResolver(fs, options, host);
|
||||
}
|
||||
@ -529,11 +525,6 @@ export class AnalyzerService {
|
||||
}
|
||||
}
|
||||
|
||||
// test only APIs
|
||||
get test_program() {
|
||||
return this._program;
|
||||
}
|
||||
|
||||
test_getConfigOptions(commandLineOptions: CommandLineOptions): ConfigOptions {
|
||||
return this._getConfigOptions(this._backgroundAnalysisProgram.host, commandLineOptions);
|
||||
}
|
||||
@ -546,6 +537,92 @@ export class AnalyzerService {
|
||||
return this._shouldHandleSourceFileWatchChanges(path, isFile);
|
||||
}
|
||||
|
||||
writeTypeStub(token: CancellationToken): void {
|
||||
const typingsSubdirPath = this._getTypeStubFolder();
|
||||
|
||||
this._program.writeTypeStub(
|
||||
this._typeStubTargetPath ?? '',
|
||||
this._typeStubTargetIsSingleFile,
|
||||
typingsSubdirPath,
|
||||
token
|
||||
);
|
||||
}
|
||||
|
||||
writeTypeStubInBackground(token: CancellationToken): Promise<any> {
|
||||
const typingsSubdirPath = this._getTypeStubFolder();
|
||||
|
||||
return this._backgroundAnalysisProgram.writeTypeStub(
|
||||
this._typeStubTargetPath ?? '',
|
||||
this._typeStubTargetIsSingleFile,
|
||||
typingsSubdirPath,
|
||||
token
|
||||
);
|
||||
}
|
||||
|
||||
invalidateAndForceReanalysis(
|
||||
rebuildUserFileIndexing = true,
|
||||
rebuildLibraryIndexing = true,
|
||||
refreshOptions?: RefreshOptions
|
||||
) {
|
||||
this._backgroundAnalysisProgram.invalidateAndForceReanalysis(
|
||||
rebuildUserFileIndexing,
|
||||
rebuildLibraryIndexing,
|
||||
refreshOptions
|
||||
);
|
||||
}
|
||||
|
||||
// Forces the service to stop all analysis, discard all its caches,
|
||||
// and research for files.
|
||||
restart() {
|
||||
this._applyConfigOptions(this._hostFactory());
|
||||
|
||||
this._backgroundAnalysisProgram.restart();
|
||||
}
|
||||
|
||||
private get _console() {
|
||||
return this._options.console!;
|
||||
}
|
||||
|
||||
private get _hostFactory() {
|
||||
return this._options.hostFactory!;
|
||||
}
|
||||
|
||||
private get _importResolverFactory() {
|
||||
return this._options.importResolverFactory!;
|
||||
}
|
||||
|
||||
private get _program() {
|
||||
return this._backgroundAnalysisProgram.program;
|
||||
}
|
||||
|
||||
private get _configOptions() {
|
||||
return this._backgroundAnalysisProgram.configOptions;
|
||||
}
|
||||
|
||||
private get _watchForSourceChanges() {
|
||||
return !!this._commandLineOptions?.watchForSourceChanges;
|
||||
}
|
||||
|
||||
private get _watchForLibraryChanges() {
|
||||
return !!this._commandLineOptions?.watchForLibraryChanges && !!this._options.libraryReanalysisTimeProvider;
|
||||
}
|
||||
|
||||
private get _watchForConfigChanges() {
|
||||
return !!this._commandLineOptions?.watchForConfigChanges;
|
||||
}
|
||||
|
||||
private get _typeCheckingMode() {
|
||||
return this._commandLineOptions?.typeCheckingMode;
|
||||
}
|
||||
|
||||
private get _verboseOutput(): boolean {
|
||||
return !!this._configOptions.verboseOutput;
|
||||
}
|
||||
|
||||
private get _typeStubTargetImportName() {
|
||||
return this._commandLineOptions?.typeStubTargetImportName;
|
||||
}
|
||||
|
||||
// Calculates the effective options based on the command-line options,
|
||||
// an optional config file, and default values.
|
||||
private _getConfigOptions(host: Host, commandLineOptions: CommandLineOptions): ConfigOptions {
|
||||
@ -840,84 +917,6 @@ export class AnalyzerService {
|
||||
return configOptions;
|
||||
}
|
||||
|
||||
writeTypeStub(token: CancellationToken): void {
|
||||
const typingsSubdirPath = this._getTypeStubFolder();
|
||||
|
||||
this._program.writeTypeStub(
|
||||
this._typeStubTargetPath ?? '',
|
||||
this._typeStubTargetIsSingleFile,
|
||||
typingsSubdirPath,
|
||||
token
|
||||
);
|
||||
}
|
||||
|
||||
writeTypeStubInBackground(token: CancellationToken): Promise<any> {
|
||||
const typingsSubdirPath = this._getTypeStubFolder();
|
||||
|
||||
return this._backgroundAnalysisProgram.writeTypeStub(
|
||||
this._typeStubTargetPath ?? '',
|
||||
this._typeStubTargetIsSingleFile,
|
||||
typingsSubdirPath,
|
||||
token
|
||||
);
|
||||
}
|
||||
|
||||
invalidateAndForceReanalysis(
|
||||
rebuildUserFileIndexing = true,
|
||||
rebuildLibraryIndexing = true,
|
||||
refreshOptions?: RefreshOptions
|
||||
) {
|
||||
this._backgroundAnalysisProgram.invalidateAndForceReanalysis(
|
||||
rebuildUserFileIndexing,
|
||||
rebuildLibraryIndexing,
|
||||
refreshOptions
|
||||
);
|
||||
}
|
||||
|
||||
// Forces the service to stop all analysis, discard all its caches,
|
||||
// and research for files.
|
||||
restart() {
|
||||
this._applyConfigOptions(this._hostFactory());
|
||||
|
||||
this._backgroundAnalysisProgram.restart();
|
||||
}
|
||||
|
||||
get fs() {
|
||||
return this._backgroundAnalysisProgram.importResolver.fileSystem;
|
||||
}
|
||||
|
||||
private get _program() {
|
||||
return this._backgroundAnalysisProgram.program;
|
||||
}
|
||||
|
||||
private get _configOptions() {
|
||||
return this._backgroundAnalysisProgram.configOptions;
|
||||
}
|
||||
|
||||
private get _watchForSourceChanges() {
|
||||
return !!this._commandLineOptions?.watchForSourceChanges;
|
||||
}
|
||||
|
||||
private get _watchForLibraryChanges() {
|
||||
return !!this._commandLineOptions?.watchForLibraryChanges && !!this._options.libraryReanalysisTimeProvider;
|
||||
}
|
||||
|
||||
private get _watchForConfigChanges() {
|
||||
return !!this._commandLineOptions?.watchForConfigChanges;
|
||||
}
|
||||
|
||||
private get _typeCheckingMode() {
|
||||
return this._commandLineOptions?.typeCheckingMode;
|
||||
}
|
||||
|
||||
private get _verboseOutput(): boolean {
|
||||
return !!this._configOptions.verboseOutput;
|
||||
}
|
||||
|
||||
private get _typeStubTargetImportName() {
|
||||
return this._commandLineOptions?.typeStubTargetImportName;
|
||||
}
|
||||
|
||||
private _getTypeStubFolder() {
|
||||
const stubPath =
|
||||
this._configOptions.stubPath ??
|
||||
|
@ -538,64 +538,6 @@ export class SourceFile {
|
||||
return diagList;
|
||||
}
|
||||
|
||||
// Get all task list diagnostics for the current file and add them
|
||||
// to the specified diagnostic list
|
||||
private _addTaskListDiagnostics(taskListTokens: TaskListToken[] | undefined, diagList: Diagnostic[]) {
|
||||
// input validation
|
||||
if (!taskListTokens || taskListTokens.length === 0 || !diagList) {
|
||||
return;
|
||||
}
|
||||
|
||||
// if we have no tokens, we're done
|
||||
if (!this._parseResults?.tokenizerOutput?.tokens) {
|
||||
return;
|
||||
}
|
||||
|
||||
const tokenizerOutput = this._parseResults.tokenizerOutput;
|
||||
for (let i = 0; i < tokenizerOutput.tokens.count; i++) {
|
||||
const token = tokenizerOutput.tokens.getItemAt(i);
|
||||
|
||||
// if there are no comments, skip this token
|
||||
if (!token.comments || token.comments.length === 0) {
|
||||
continue;
|
||||
}
|
||||
|
||||
for (const comment of token.comments) {
|
||||
for (const token of taskListTokens) {
|
||||
// Check if the comment matches the task list token.
|
||||
// The comment must start with zero or more whitespace characters,
|
||||
// followed by the taskListToken (case insensitive),
|
||||
// followed by (0+ whitespace + EOL) OR (1+ NON-alphanumeric characters)
|
||||
const regexStr = '^[\\s]*' + token.text + '([\\s]*$|[\\W]+)';
|
||||
const regex = RegExp(regexStr, 'i'); // case insensitive
|
||||
|
||||
// if the comment doesn't match, skip it
|
||||
if (!regex.test(comment.value)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Calculate the range for the diagnostic
|
||||
// This allows navigation to the comment via double clicking the item in the task list pane
|
||||
let rangeStart = comment.start;
|
||||
|
||||
// The comment technically starts right after the comment identifier (#), but we want the caret right
|
||||
// before the task list token (since there might be whitespace before it)
|
||||
const indexOfToken = comment.value.toLowerCase().indexOf(token.text.toLowerCase());
|
||||
rangeStart += indexOfToken;
|
||||
|
||||
const rangeEnd = TextRange.getEnd(comment);
|
||||
const range = convertOffsetsToRange(rangeStart, rangeEnd, tokenizerOutput.lines!);
|
||||
|
||||
// Add the diagnostic to the list to send to VS,
|
||||
// and trim whitespace from the comment so it's easier to read in the task list
|
||||
diagList.push(
|
||||
new Diagnostic(DiagnosticCategory.TaskItem, comment.value.trim(), range, token.priority)
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
getImports(): ImportResult[] {
|
||||
return this._imports || [];
|
||||
}
|
||||
@ -1300,6 +1242,64 @@ export class SourceFile {
|
||||
this._ipythonMode = enable ? IPythonMode.CellDocs : IPythonMode.None;
|
||||
}
|
||||
|
||||
// Get all task list diagnostics for the current file and add them
|
||||
// to the specified diagnostic list
|
||||
private _addTaskListDiagnostics(taskListTokens: TaskListToken[] | undefined, diagList: Diagnostic[]) {
|
||||
// input validation
|
||||
if (!taskListTokens || taskListTokens.length === 0 || !diagList) {
|
||||
return;
|
||||
}
|
||||
|
||||
// if we have no tokens, we're done
|
||||
if (!this._parseResults?.tokenizerOutput?.tokens) {
|
||||
return;
|
||||
}
|
||||
|
||||
const tokenizerOutput = this._parseResults.tokenizerOutput;
|
||||
for (let i = 0; i < tokenizerOutput.tokens.count; i++) {
|
||||
const token = tokenizerOutput.tokens.getItemAt(i);
|
||||
|
||||
// if there are no comments, skip this token
|
||||
if (!token.comments || token.comments.length === 0) {
|
||||
continue;
|
||||
}
|
||||
|
||||
for (const comment of token.comments) {
|
||||
for (const token of taskListTokens) {
|
||||
// Check if the comment matches the task list token.
|
||||
// The comment must start with zero or more whitespace characters,
|
||||
// followed by the taskListToken (case insensitive),
|
||||
// followed by (0+ whitespace + EOL) OR (1+ NON-alphanumeric characters)
|
||||
const regexStr = '^[\\s]*' + token.text + '([\\s]*$|[\\W]+)';
|
||||
const regex = RegExp(regexStr, 'i'); // case insensitive
|
||||
|
||||
// if the comment doesn't match, skip it
|
||||
if (!regex.test(comment.value)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Calculate the range for the diagnostic
|
||||
// This allows navigation to the comment via double clicking the item in the task list pane
|
||||
let rangeStart = comment.start;
|
||||
|
||||
// The comment technically starts right after the comment identifier (#), but we want the caret right
|
||||
// before the task list token (since there might be whitespace before it)
|
||||
const indexOfToken = comment.value.toLowerCase().indexOf(token.text.toLowerCase());
|
||||
rangeStart += indexOfToken;
|
||||
|
||||
const rangeEnd = TextRange.getEnd(comment);
|
||||
const range = convertOffsetsToRange(rangeStart, rangeEnd, tokenizerOutput.lines!);
|
||||
|
||||
// Add the diagnostic to the list to send to VS,
|
||||
// and trim whitespace from the comment so it's easier to read in the task list
|
||||
diagList.push(
|
||||
new Diagnostic(DiagnosticCategory.TaskItem, comment.value.trim(), range, token.priority)
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private _buildFileInfo(
|
||||
configOptions: ConfigOptions,
|
||||
fileContents: string,
|
||||
|
@ -50,9 +50,9 @@ import { TypeEvaluator } from './typeEvaluatorTypes';
|
||||
import { ClassType, isFunction, isInstantiableClass, isNever, isUnknown, removeUnknownFromUnion } from './types';
|
||||
|
||||
class TrackedImport {
|
||||
constructor(public importName: string) {}
|
||||
|
||||
isAccessed = false;
|
||||
|
||||
constructor(public importName: string) {}
|
||||
}
|
||||
|
||||
class TrackedImportAs extends TrackedImport {
|
||||
|
@ -3693,10 +3693,6 @@ class ExpectedConstructorTypeTransformer extends TypeVarTransformer {
|
||||
super();
|
||||
}
|
||||
|
||||
private _isTypeVarLive(typeVar: TypeVarType) {
|
||||
return this._liveTypeVarScopes.some((scopeId) => typeVar.scopeId === scopeId);
|
||||
}
|
||||
|
||||
override transformTypeVar(typeVar: TypeVarType) {
|
||||
// If the type variable is unrelated to the scopes we're solving,
|
||||
// don't transform that type variable.
|
||||
@ -3706,4 +3702,8 @@ class ExpectedConstructorTypeTransformer extends TypeVarTransformer {
|
||||
|
||||
return AnyType.create();
|
||||
}
|
||||
|
||||
private _isTypeVarLive(typeVar: TypeVarType) {
|
||||
return this._liveTypeVarScopes.some((scopeId) => typeVar.scopeId === scopeId);
|
||||
}
|
||||
}
|
||||
|
@ -47,39 +47,6 @@ export class BackgroundAnalysisBase {
|
||||
// Don't allow instantiation of this type directly.
|
||||
}
|
||||
|
||||
protected setup(worker: Worker) {
|
||||
this._worker = worker;
|
||||
|
||||
// global channel to communicate from BG channel to main thread.
|
||||
worker.on('message', (msg: AnalysisResponse) => this.onMessage(msg));
|
||||
|
||||
// this will catch any exception thrown from background thread,
|
||||
// print log and ignore exception
|
||||
worker.on('error', (msg) => {
|
||||
this.log(LogLevel.Error, `Error occurred on background thread: ${JSON.stringify(msg)}`);
|
||||
});
|
||||
}
|
||||
|
||||
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) {
|
||||
this._onAnalysisCompletion = callback ?? nullCallback;
|
||||
}
|
||||
@ -146,53 +113,6 @@ export class BackgroundAnalysisBase {
|
||||
this._startOrResumeAnalysis('analyze', indices, token);
|
||||
}
|
||||
|
||||
private _startOrResumeAnalysis(
|
||||
requestType: 'analyze' | 'resumeAnalysis',
|
||||
indices: Indices | undefined,
|
||||
token: CancellationToken
|
||||
) {
|
||||
const { port1, port2 } = new MessageChannel();
|
||||
|
||||
// Handle response from background thread to main thread.
|
||||
port1.on('message', (msg: AnalysisResponse) => {
|
||||
switch (msg.requestType) {
|
||||
case 'analysisResult': {
|
||||
this._onAnalysisCompletion(convertAnalysisResults(msg.data));
|
||||
break;
|
||||
}
|
||||
|
||||
case 'analysisPaused': {
|
||||
port2.close();
|
||||
port1.close();
|
||||
|
||||
// Analysis request has completed, but there is more to
|
||||
// analyze, so queue another message to resume later.
|
||||
this._startOrResumeAnalysis('resumeAnalysis', indices, token);
|
||||
break;
|
||||
}
|
||||
|
||||
case 'indexResult': {
|
||||
const { path, indexResults } = msg.data;
|
||||
indices?.setWorkspaceIndex(path, indexResults);
|
||||
break;
|
||||
}
|
||||
|
||||
case 'analysisDone': {
|
||||
disposeCancellationToken(token);
|
||||
port2.close();
|
||||
port1.close();
|
||||
break;
|
||||
}
|
||||
|
||||
default:
|
||||
debug.fail(`${msg.requestType} is not expected`);
|
||||
}
|
||||
});
|
||||
|
||||
const cancellationId = getCancellationTokenId(token);
|
||||
this.enqueueRequest({ requestType, data: cancellationId, port: port2 });
|
||||
}
|
||||
|
||||
startIndexing(
|
||||
indexOptions: IndexOptions,
|
||||
configOptions: ConfigOptions,
|
||||
@ -272,6 +192,39 @@ export class BackgroundAnalysisBase {
|
||||
this.enqueueRequest({ requestType: 'shutdown', data: null });
|
||||
}
|
||||
|
||||
protected setup(worker: Worker) {
|
||||
this._worker = worker;
|
||||
|
||||
// global channel to communicate from BG channel to main thread.
|
||||
worker.on('message', (msg: AnalysisResponse) => this.onMessage(msg));
|
||||
|
||||
// this will catch any exception thrown from background thread,
|
||||
// print log and ignore exception
|
||||
worker.on('error', (msg) => {
|
||||
this.log(LogLevel.Error, `Error occurred on background thread: ${JSON.stringify(msg)}`);
|
||||
});
|
||||
}
|
||||
|
||||
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`);
|
||||
}
|
||||
}
|
||||
|
||||
protected enqueueRequest(request: AnalysisRequest) {
|
||||
if (this._worker) {
|
||||
this._worker.postMessage(request, request.port ? [request.port] : undefined);
|
||||
@ -281,19 +234,62 @@ export class BackgroundAnalysisBase {
|
||||
protected log(level: LogLevel, msg: string) {
|
||||
log(this.console, level, msg);
|
||||
}
|
||||
|
||||
private _startOrResumeAnalysis(
|
||||
requestType: 'analyze' | 'resumeAnalysis',
|
||||
indices: Indices | undefined,
|
||||
token: CancellationToken
|
||||
) {
|
||||
const { port1, port2 } = new MessageChannel();
|
||||
|
||||
// Handle response from background thread to main thread.
|
||||
port1.on('message', (msg: AnalysisResponse) => {
|
||||
switch (msg.requestType) {
|
||||
case 'analysisResult': {
|
||||
this._onAnalysisCompletion(convertAnalysisResults(msg.data));
|
||||
break;
|
||||
}
|
||||
|
||||
case 'analysisPaused': {
|
||||
port2.close();
|
||||
port1.close();
|
||||
|
||||
// Analysis request has completed, but there is more to
|
||||
// analyze, so queue another message to resume later.
|
||||
this._startOrResumeAnalysis('resumeAnalysis', indices, token);
|
||||
break;
|
||||
}
|
||||
|
||||
case 'indexResult': {
|
||||
const { path, indexResults } = msg.data;
|
||||
indices?.setWorkspaceIndex(path, indexResults);
|
||||
break;
|
||||
}
|
||||
|
||||
case 'analysisDone': {
|
||||
disposeCancellationToken(token);
|
||||
port2.close();
|
||||
port1.close();
|
||||
break;
|
||||
}
|
||||
|
||||
default:
|
||||
debug.fail(`${msg.requestType} is not expected`);
|
||||
}
|
||||
});
|
||||
|
||||
const cancellationId = getCancellationTokenId(token);
|
||||
this.enqueueRequest({ requestType, data: cancellationId, port: port2 });
|
||||
}
|
||||
}
|
||||
|
||||
export abstract class BackgroundAnalysisRunnerBase extends BackgroundThreadBase {
|
||||
private _configOptions: ConfigOptions;
|
||||
protected _importResolver: ImportResolver;
|
||||
private _program: Program;
|
||||
|
||||
protected _importResolver: ImportResolver;
|
||||
protected _logTracker: LogTracker;
|
||||
|
||||
get program(): Program {
|
||||
return this._program;
|
||||
}
|
||||
|
||||
protected constructor() {
|
||||
super(workerData as InitializationData);
|
||||
|
||||
@ -315,6 +311,10 @@ export abstract class BackgroundAnalysisRunnerBase extends BackgroundThreadBase
|
||||
});
|
||||
}
|
||||
|
||||
get program(): Program {
|
||||
return this._program;
|
||||
}
|
||||
|
||||
start() {
|
||||
this.log(LogLevel.Info, `Background analysis(${threadId}) started`);
|
||||
|
||||
@ -499,6 +499,28 @@ export abstract class BackgroundAnalysisRunnerBase extends BackgroundThreadBase
|
||||
}
|
||||
}
|
||||
|
||||
protected abstract createHost(): Host;
|
||||
|
||||
protected abstract createImportResolver(fs: FileSystem, options: ConfigOptions, host: Host): ImportResolver;
|
||||
|
||||
protected processIndexing(port: MessagePort, token: CancellationToken): void {
|
||||
/* noop */
|
||||
}
|
||||
|
||||
protected reportIndex(port: MessagePort, result: { path: string; indexResults: IndexResults }) {
|
||||
port.postMessage({ requestType: 'indexResult', data: result });
|
||||
}
|
||||
|
||||
protected override shutdown() {
|
||||
this._program.dispose();
|
||||
Extensions.destroyProgramExtensions(this._program.id);
|
||||
super.shutdown();
|
||||
}
|
||||
|
||||
protected analysisDone(port: MessagePort, cancellationId: string) {
|
||||
port.postMessage({ requestType: 'analysisDone', data: cancellationId });
|
||||
}
|
||||
|
||||
private _onMessageWrapper(msg: AnalysisRequest) {
|
||||
try {
|
||||
return this.onMessage(msg);
|
||||
@ -541,24 +563,6 @@ export abstract class BackgroundAnalysisRunnerBase extends BackgroundThreadBase
|
||||
}
|
||||
}
|
||||
|
||||
protected abstract createHost(): Host;
|
||||
|
||||
protected abstract createImportResolver(fs: FileSystem, options: ConfigOptions, host: Host): ImportResolver;
|
||||
|
||||
protected processIndexing(port: MessagePort, token: CancellationToken): void {
|
||||
/* noop */
|
||||
}
|
||||
|
||||
protected reportIndex(port: MessagePort, result: { path: string; indexResults: IndexResults }) {
|
||||
port.postMessage({ requestType: 'indexResult', data: result });
|
||||
}
|
||||
|
||||
protected override shutdown() {
|
||||
this._program.dispose();
|
||||
Extensions.destroyProgramExtensions(this._program.id);
|
||||
super.shutdown();
|
||||
}
|
||||
|
||||
private _reportDiagnostics(diagnostics: FileDiagnostics[], filesLeftToAnalyze: number, elapsedTime: number) {
|
||||
if (parentPort) {
|
||||
this._onAnalysisCompletion(parentPort, {
|
||||
@ -580,10 +584,6 @@ export abstract class BackgroundAnalysisRunnerBase extends BackgroundThreadBase
|
||||
private _analysisPaused(port: MessagePort, cancellationId: string) {
|
||||
port.postMessage({ requestType: 'analysisPaused', data: cancellationId });
|
||||
}
|
||||
|
||||
protected analysisDone(port: MessagePort, cancellationId: string) {
|
||||
port.postMessage({ requestType: 'analysisDone', data: cancellationId });
|
||||
}
|
||||
}
|
||||
|
||||
function convertAnalysisResults(result: AnalysisResults): AnalysisResults {
|
||||
|
@ -26,7 +26,7 @@ import {
|
||||
} from '../analyzer/types';
|
||||
import { throwIfCancellationRequested } from '../common/cancellationUtils';
|
||||
import { isNumber, isString } from '../common/core';
|
||||
import { convertOffsetsToRange, convertOffsetToPosition } from '../common/positionUtils';
|
||||
import { convertOffsetToPosition, convertOffsetsToRange } from '../common/positionUtils';
|
||||
import { TextRange } from '../common/textRange';
|
||||
import { TextRangeCollection } from '../common/textRangeCollection';
|
||||
import { LanguageServerInterface } from '../languageServerBase';
|
||||
@ -55,8 +55,8 @@ import {
|
||||
ErrorNode,
|
||||
ExceptNode,
|
||||
ExpressionNode,
|
||||
FormatStringNode,
|
||||
ForNode,
|
||||
FormatStringNode,
|
||||
FunctionAnnotationNode,
|
||||
FunctionNode,
|
||||
GlobalNode,
|
||||
@ -66,7 +66,6 @@ import {
|
||||
ImportFromNode,
|
||||
ImportNode,
|
||||
IndexNode,
|
||||
isExpressionNode,
|
||||
LambdaNode,
|
||||
ListComprehensionForNode,
|
||||
ListComprehensionIfNode,
|
||||
@ -117,6 +116,7 @@ import {
|
||||
WithNode,
|
||||
YieldFromNode,
|
||||
YieldNode,
|
||||
isExpressionNode,
|
||||
} from '../parser/parseNodes';
|
||||
import { ParseResults } from '../parser/parser';
|
||||
import { KeywordType, NewLineType, OperatorType, StringTokenFlags, Token, TokenType } from '../parser/tokenizerTypes';
|
||||
@ -570,18 +570,6 @@ class TreeDumper extends ParseTreeWalker {
|
||||
}
|
||||
}
|
||||
|
||||
private _log(value: string) {
|
||||
this._output += `${this._indentation}${value}\r\n`;
|
||||
}
|
||||
|
||||
private _getPrefix(node: ParseNode) {
|
||||
const pos = convertOffsetToPosition(node.start, this._lines);
|
||||
// VS code's output window expects 1 based values, print the line/char with 1 based.
|
||||
return `[${node.id}] '${this._file}:${pos.line + 1}:${pos.character + 1}' => ${printParseNodeType(
|
||||
node.nodeType
|
||||
)} ${getTextSpanString(node, this._lines)} =>`;
|
||||
}
|
||||
|
||||
reset() {
|
||||
this._indentation = '';
|
||||
this._output = '';
|
||||
@ -1004,6 +992,18 @@ class TreeDumper extends ParseTreeWalker {
|
||||
this._log(`${this._getPrefix(node)}`);
|
||||
return true;
|
||||
}
|
||||
|
||||
private _log(value: string) {
|
||||
this._output += `${this._indentation}${value}\r\n`;
|
||||
}
|
||||
|
||||
private _getPrefix(node: ParseNode) {
|
||||
const pos = convertOffsetToPosition(node.start, this._lines);
|
||||
// VS code's output window expects 1 based values, print the line/char with 1 based.
|
||||
return `[${node.id}] '${this._file}:${pos.line + 1}:${pos.character + 1}' => ${printParseNodeType(
|
||||
node.nodeType
|
||||
)} ${getTextSpanString(node, this._lines)} =>`;
|
||||
}
|
||||
}
|
||||
|
||||
function getTypeParameterCategoryString(type: TypeParameterCategory) {
|
||||
|
@ -82,16 +82,6 @@ export class FileBasedToken implements CancellationToken {
|
||||
// empty
|
||||
}
|
||||
|
||||
cancel() {
|
||||
if (!this.isCancelled) {
|
||||
this.isCancelled = true;
|
||||
if (this._emitter) {
|
||||
this._emitter.fire(undefined);
|
||||
this._disposeEmitter();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
get isCancellationRequested(): boolean {
|
||||
if (this.isCancelled) {
|
||||
return true;
|
||||
@ -115,6 +105,16 @@ export class FileBasedToken implements CancellationToken {
|
||||
return this._emitter.event;
|
||||
}
|
||||
|
||||
cancel() {
|
||||
if (!this.isCancelled) {
|
||||
this.isCancelled = true;
|
||||
if (this._emitter) {
|
||||
this._emitter.fire(undefined);
|
||||
this._disposeEmitter();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
dispose(): void {
|
||||
this._disposeEmitter();
|
||||
}
|
||||
|
@ -35,11 +35,6 @@ export type DiagnosticSeverityOverridesMap = { [ruleName: string]: DiagnosticSev
|
||||
// that provided through a language client like the VS Code editor. These options
|
||||
// are later combined with those from the config file to produce the final configuration.
|
||||
export class CommandLineOptions {
|
||||
constructor(executionRoot: string, fromVsCodeExtension: boolean) {
|
||||
this.executionRoot = executionRoot;
|
||||
this.fromVsCodeExtension = fromVsCodeExtension;
|
||||
}
|
||||
|
||||
// A list of file specs to include in the analysis. Can contain
|
||||
// directories, in which case all "*.py" files within those directories
|
||||
// are included.
|
||||
@ -142,4 +137,9 @@ export class CommandLineOptions {
|
||||
|
||||
// Analyze functions and methods that have no type annotations?
|
||||
analyzeUnannotatedFunctions?: boolean;
|
||||
|
||||
constructor(executionRoot: string, fromVsCodeExtension: boolean) {
|
||||
this.executionRoot = executionRoot;
|
||||
this.fromVsCodeExtension = fromVsCodeExtension;
|
||||
}
|
||||
}
|
||||
|
@ -36,19 +36,6 @@ export enum PythonPlatform {
|
||||
}
|
||||
|
||||
export class ExecutionEnvironment {
|
||||
// Default to "." which indicates every file in the project.
|
||||
constructor(
|
||||
root: string,
|
||||
defaultPythonVersion: PythonVersion | undefined,
|
||||
defaultPythonPlatform: string | undefined,
|
||||
defaultExtraPaths: string[] | undefined
|
||||
) {
|
||||
this.root = root || undefined;
|
||||
this.pythonVersion = defaultPythonVersion || latestStablePythonVersion;
|
||||
this.pythonPlatform = defaultPythonPlatform;
|
||||
this.extraPaths = [...(defaultExtraPaths ?? [])];
|
||||
}
|
||||
|
||||
// Root directory for execution - absolute or relative to the
|
||||
// project root.
|
||||
// Undefined if this is a rootless environment (e.g., open file mode).
|
||||
@ -62,6 +49,19 @@ export class ExecutionEnvironment {
|
||||
|
||||
// Default to no extra paths.
|
||||
extraPaths: string[] = [];
|
||||
|
||||
// Default to "." which indicates every file in the project.
|
||||
constructor(
|
||||
root: string,
|
||||
defaultPythonVersion: PythonVersion | undefined,
|
||||
defaultPythonPlatform: string | undefined,
|
||||
defaultExtraPaths: string[] | undefined
|
||||
) {
|
||||
this.root = root || undefined;
|
||||
this.pythonVersion = defaultPythonVersion || latestStablePythonVersion;
|
||||
this.pythonPlatform = defaultPythonPlatform;
|
||||
this.extraPaths = [...(defaultExtraPaths ?? [])];
|
||||
}
|
||||
}
|
||||
|
||||
export type DiagnosticLevel = 'none' | 'information' | 'warning' | 'error';
|
||||
@ -677,13 +677,6 @@ export function matchFileSpecs(configOptions: ConfigOptions, filePath: string, i
|
||||
// Internal configuration options. These are derived from a combination
|
||||
// of the command line and from a JSON-based config file.
|
||||
export class ConfigOptions {
|
||||
constructor(projectRoot: string, typeCheckingMode?: string) {
|
||||
this.projectRoot = projectRoot;
|
||||
this.typeCheckingMode = typeCheckingMode;
|
||||
this.diagnosticRuleSet = ConfigOptions.getDiagnosticRuleSet(typeCheckingMode);
|
||||
this.functionSignatureDisplay = SignatureDisplayType.formatted;
|
||||
}
|
||||
|
||||
// Absolute directory of project. All relative paths in the config
|
||||
// are based on this path.
|
||||
projectRoot: string;
|
||||
@ -805,6 +798,13 @@ export class ConfigOptions {
|
||||
// Controls how hover and completion function signatures are displayed.
|
||||
functionSignatureDisplay: SignatureDisplayType;
|
||||
|
||||
constructor(projectRoot: string, typeCheckingMode?: string) {
|
||||
this.projectRoot = projectRoot;
|
||||
this.typeCheckingMode = typeCheckingMode;
|
||||
this.diagnosticRuleSet = ConfigOptions.getDiagnosticRuleSet(typeCheckingMode);
|
||||
this.functionSignatureDisplay = SignatureDisplayType.formatted;
|
||||
}
|
||||
|
||||
static getDiagnosticRuleSet(typeCheckingMode?: string): DiagnosticRuleSet {
|
||||
if (typeCheckingMode === 'strict') {
|
||||
return getStrictDiagnosticRuleSet();
|
||||
|
@ -29,18 +29,6 @@ class DeferredImpl<T> implements Deferred<T> {
|
||||
});
|
||||
}
|
||||
|
||||
public resolve(_value?: T | PromiseLike<T>) {
|
||||
// eslint-disable-next-line prefer-rest-params
|
||||
this._resolve.apply(this._scope ? this._scope : this, arguments as any);
|
||||
this._resolved = true;
|
||||
}
|
||||
|
||||
public reject(_reason?: any) {
|
||||
// eslint-disable-next-line prefer-rest-params
|
||||
this._reject.apply(this._scope ? this._scope : this, arguments as any);
|
||||
this._rejected = true;
|
||||
}
|
||||
|
||||
get promise(): Promise<T> {
|
||||
return this._promise;
|
||||
}
|
||||
@ -56,6 +44,18 @@ class DeferredImpl<T> implements Deferred<T> {
|
||||
get completed(): boolean {
|
||||
return this._rejected || this._resolved;
|
||||
}
|
||||
|
||||
resolve(_value?: T | PromiseLike<T>) {
|
||||
// eslint-disable-next-line prefer-rest-params
|
||||
this._resolve.apply(this._scope ? this._scope : this, arguments as any);
|
||||
this._resolved = true;
|
||||
}
|
||||
|
||||
reject(_reason?: any) {
|
||||
// eslint-disable-next-line prefer-rest-params
|
||||
this._reject.apply(this._scope ? this._scope : this, arguments as any);
|
||||
this._rejected = true;
|
||||
}
|
||||
}
|
||||
|
||||
export function createDeferred<T>(scope: any = null): Deferred<T> {
|
||||
|
@ -32,6 +32,12 @@ class OwningFileToken extends FileBasedToken {
|
||||
super(cancellationFilePath, fs);
|
||||
}
|
||||
|
||||
override get isCancellationRequested(): boolean {
|
||||
// Since this object owns the file and it gets created when the
|
||||
// token is cancelled, there's no point in checking the pipe.
|
||||
return this.isCancelled;
|
||||
}
|
||||
|
||||
override cancel() {
|
||||
if (!this._disposed && !this.isCancelled) {
|
||||
this._createPipe();
|
||||
@ -39,12 +45,6 @@ class OwningFileToken extends FileBasedToken {
|
||||
}
|
||||
}
|
||||
|
||||
override get isCancellationRequested(): boolean {
|
||||
// Since this object owns the file and it gets created when the
|
||||
// token is cancelled, there's no point in checking the pipe.
|
||||
return this.isCancelled;
|
||||
}
|
||||
|
||||
override dispose(): void {
|
||||
this._disposed = true;
|
||||
|
||||
|
@ -60,6 +60,14 @@ export class LimitedAccessHost extends NoAccessHost {
|
||||
}
|
||||
|
||||
export class FullAccessHost extends LimitedAccessHost {
|
||||
constructor(protected _fs: FileSystem) {
|
||||
super();
|
||||
}
|
||||
|
||||
override get kind(): HostKind {
|
||||
return HostKind.FullAccess;
|
||||
}
|
||||
|
||||
static createHost(kind: HostKind, fs: FileSystem) {
|
||||
switch (kind) {
|
||||
case HostKind.NoAccess:
|
||||
@ -73,14 +81,6 @@ export class FullAccessHost extends LimitedAccessHost {
|
||||
}
|
||||
}
|
||||
|
||||
constructor(protected _fs: FileSystem) {
|
||||
super();
|
||||
}
|
||||
|
||||
override get kind(): HostKind {
|
||||
return HostKind.FullAccess;
|
||||
}
|
||||
|
||||
override getPythonSearchPaths(pythonPath?: string, logInfo?: string[]): PythonPathResult {
|
||||
const importFailureInfo = logInfo ?? [];
|
||||
let result = this._executePythonInterpreter(pythonPath, (p) =>
|
||||
|
@ -116,13 +116,6 @@ class EggZipOpenFS extends ZipOpenFS {
|
||||
private override isZip!: Set<PortablePath>;
|
||||
private override notZip!: Set<PortablePath>;
|
||||
|
||||
// Hack to provide typed access to this private method.
|
||||
private override getZipSync<T>(p: PortablePath, accept: (zipFs: ZipFS) => T): T {
|
||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||
//@ts-expect-error
|
||||
return super.getZipSync(p, accept);
|
||||
}
|
||||
|
||||
override findZip(p: PortablePath) {
|
||||
if (this.filter && !this.filter.test(p)) return null;
|
||||
|
||||
@ -173,6 +166,13 @@ class EggZipOpenFS extends ZipOpenFS {
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
// Hack to provide typed access to this private method.
|
||||
private override getZipSync<T>(p: PortablePath, accept: (zipFs: ZipFS) => T): T {
|
||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||
//@ts-expect-error
|
||||
return super.getZipSync(p, accept);
|
||||
}
|
||||
}
|
||||
|
||||
/* eslint-enable @typescript-eslint/naming-convention */
|
||||
|
@ -168,17 +168,17 @@ export interface WindowInterface {
|
||||
}
|
||||
|
||||
export interface LanguageServerInterface {
|
||||
readonly rootPath: string;
|
||||
readonly console: ConsoleInterface;
|
||||
readonly window: WindowInterface;
|
||||
readonly supportAdvancedEdits: boolean;
|
||||
|
||||
getWorkspaceForFile(filePath: string): Promise<Workspace>;
|
||||
getSettings(workspace: Workspace): Promise<ServerSettings>;
|
||||
createBackgroundAnalysis(serviceId: string): BackgroundAnalysisBase | undefined;
|
||||
reanalyze(): void;
|
||||
restart(): void;
|
||||
decodeTextDocumentUri(uriString: string): string;
|
||||
|
||||
readonly rootPath: string;
|
||||
readonly console: ConsoleInterface;
|
||||
readonly window: WindowInterface;
|
||||
readonly supportAdvancedEdits: boolean;
|
||||
}
|
||||
|
||||
export interface ServerOptions {
|
||||
@ -394,99 +394,6 @@ export abstract class LanguageServerBase implements LanguageServerInterface {
|
||||
Extensions.createLanguageServiceExtensions(this);
|
||||
}
|
||||
|
||||
// Convert uri to path
|
||||
decodeTextDocumentUri(uriString: string): string {
|
||||
return this._uriParser.decodeTextDocumentUri(uriString);
|
||||
}
|
||||
|
||||
abstract createBackgroundAnalysis(serviceId: string): BackgroundAnalysisBase | undefined;
|
||||
|
||||
protected abstract executeCommand(params: ExecuteCommandParams, token: CancellationToken): Promise<any>;
|
||||
|
||||
protected abstract isLongRunningCommand(command: string): boolean;
|
||||
|
||||
protected abstract executeCodeAction(
|
||||
params: CodeActionParams,
|
||||
token: CancellationToken
|
||||
): Promise<(Command | CodeAction)[] | undefined | null>;
|
||||
|
||||
abstract getSettings(workspace: Workspace): Promise<ServerSettings>;
|
||||
|
||||
protected isPythonPathImmutable(filePath: string): boolean {
|
||||
// This function is called to determine if the file is using
|
||||
// a special pythonPath separate from a workspace or not.
|
||||
// The default is no.
|
||||
return false;
|
||||
}
|
||||
|
||||
protected async getConfiguration(scopeUri: string | undefined, section: string) {
|
||||
if (this.client.hasConfigurationCapability) {
|
||||
const item: ConfigurationItem = {};
|
||||
if (scopeUri !== undefined) {
|
||||
item.scopeUri = scopeUri;
|
||||
}
|
||||
if (section !== undefined) {
|
||||
item.section = section;
|
||||
}
|
||||
return this._connection.workspace.getConfiguration(item);
|
||||
}
|
||||
|
||||
if (this._defaultClientConfig) {
|
||||
return getNestedProperty(this._defaultClientConfig, section);
|
||||
}
|
||||
|
||||
return undefined;
|
||||
}
|
||||
|
||||
protected isOpenFilesOnly(diagnosticMode: string): boolean {
|
||||
return diagnosticMode !== 'workspace';
|
||||
}
|
||||
|
||||
protected get allowModuleRename() {
|
||||
return false;
|
||||
}
|
||||
|
||||
protected getSeverityOverrides(value: string): DiagnosticSeverityOverrides | undefined {
|
||||
const enumValue = value as DiagnosticSeverityOverrides;
|
||||
if (getDiagnosticSeverityOverrides().includes(enumValue)) {
|
||||
return enumValue;
|
||||
}
|
||||
|
||||
return undefined;
|
||||
}
|
||||
|
||||
protected getDiagnosticRuleName(value: string): DiagnosticRule | undefined {
|
||||
const enumValue = value as DiagnosticRule;
|
||||
if (getDiagLevelDiagnosticRules().includes(enumValue)) {
|
||||
return enumValue;
|
||||
}
|
||||
|
||||
return undefined;
|
||||
}
|
||||
|
||||
protected abstract createHost(): Host;
|
||||
protected abstract createImportResolver(fs: FileSystem, options: ConfigOptions, host: Host): ImportResolver;
|
||||
|
||||
protected createBackgroundAnalysisProgram(
|
||||
serviceId: string,
|
||||
console: ConsoleInterface,
|
||||
configOptions: ConfigOptions,
|
||||
importResolver: ImportResolver,
|
||||
backgroundAnalysis?: BackgroundAnalysisBase,
|
||||
maxAnalysisTime?: MaxAnalysisTime,
|
||||
cacheManager?: CacheManager
|
||||
): BackgroundAnalysisProgram {
|
||||
return new BackgroundAnalysisProgram(
|
||||
console,
|
||||
configOptions,
|
||||
importResolver,
|
||||
backgroundAnalysis,
|
||||
maxAnalysisTime,
|
||||
/* disableChecker */ undefined,
|
||||
cacheManager
|
||||
);
|
||||
}
|
||||
|
||||
// Provides access to the client's window.
|
||||
get window(): RemoteWindow {
|
||||
return this._connection.window;
|
||||
@ -496,6 +403,15 @@ export abstract class LanguageServerBase implements LanguageServerInterface {
|
||||
return this.client.hasDocumentChangeCapability && this.client.hasDocumentAnnotationCapability;
|
||||
}
|
||||
|
||||
// Convert uri to path
|
||||
decodeTextDocumentUri(uriString: string): string {
|
||||
return this._uriParser.decodeTextDocumentUri(uriString);
|
||||
}
|
||||
|
||||
abstract createBackgroundAnalysis(serviceId: string): BackgroundAnalysisBase | undefined;
|
||||
|
||||
abstract getSettings(workspace: Workspace): Promise<ServerSettings>;
|
||||
|
||||
// Creates a service instance that's used for analyzing a
|
||||
// program within a workspace.
|
||||
createAnalyzerService(
|
||||
@ -552,6 +468,142 @@ export abstract class LanguageServerBase implements LanguageServerInterface {
|
||||
});
|
||||
}
|
||||
|
||||
updateSettingsForAllWorkspaces(): void {
|
||||
const tasks: Promise<void>[] = [];
|
||||
this._workspaceFactory.items().forEach((workspace) => {
|
||||
// Updating settings can change workspace's file ownership. Make workspace uninitialized so that
|
||||
// features can wait until workspace gets new settings.
|
||||
// the file's ownership can also changed by `pyrightconfig.json` changes, but those are synchronous
|
||||
// operation, so it won't affect this.
|
||||
workspace.isInitialized = workspace.isInitialized.reset();
|
||||
tasks.push(this.updateSettingsForWorkspace(workspace, workspace.isInitialized));
|
||||
});
|
||||
|
||||
Promise.all(tasks).then(() => {
|
||||
this._setupFileWatcher();
|
||||
});
|
||||
}
|
||||
|
||||
async updateSettingsForWorkspace(
|
||||
workspace: Workspace,
|
||||
status: InitStatus | undefined,
|
||||
serverSettings?: ServerSettings
|
||||
): Promise<void> {
|
||||
status?.markCalled();
|
||||
|
||||
serverSettings = serverSettings ?? (await this.getSettings(workspace));
|
||||
|
||||
// Set logging level first.
|
||||
(this.console as ConsoleWithLogLevel).level = serverSettings.logLevel ?? LogLevel.Info;
|
||||
|
||||
// Apply the new path to the workspace (before restarting the service).
|
||||
serverSettings.pythonPath = this._workspaceFactory.applyPythonPath(workspace, serverSettings.pythonPath);
|
||||
|
||||
// Then use the updated settings to restart the service.
|
||||
this.updateOptionsAndRestartService(workspace, serverSettings);
|
||||
|
||||
workspace.disableLanguageServices = !!serverSettings.disableLanguageServices;
|
||||
workspace.disableOrganizeImports = !!serverSettings.disableOrganizeImports;
|
||||
|
||||
// Don't use workspace.isInitialized directly since it might have been
|
||||
// reset due to pending config change event.
|
||||
// The workspace is now open for business.
|
||||
status?.resolve();
|
||||
}
|
||||
|
||||
updateOptionsAndRestartService(
|
||||
workspace: Workspace,
|
||||
serverSettings: ServerSettings,
|
||||
typeStubTargetImportName?: string
|
||||
) {
|
||||
AnalyzerServiceExecutor.runWithOptions(this.rootPath, workspace, serverSettings, typeStubTargetImportName);
|
||||
workspace.searchPathsToWatch = workspace.service.librarySearchPathsToWatch ?? [];
|
||||
}
|
||||
|
||||
protected get allowModuleRename() {
|
||||
return false;
|
||||
}
|
||||
|
||||
protected abstract executeCommand(params: ExecuteCommandParams, token: CancellationToken): Promise<any>;
|
||||
|
||||
protected abstract isLongRunningCommand(command: string): boolean;
|
||||
|
||||
protected abstract executeCodeAction(
|
||||
params: CodeActionParams,
|
||||
token: CancellationToken
|
||||
): Promise<(Command | CodeAction)[] | undefined | null>;
|
||||
|
||||
protected isPythonPathImmutable(filePath: string): boolean {
|
||||
// This function is called to determine if the file is using
|
||||
// a special pythonPath separate from a workspace or not.
|
||||
// The default is no.
|
||||
return false;
|
||||
}
|
||||
|
||||
protected async getConfiguration(scopeUri: string | undefined, section: string) {
|
||||
if (this.client.hasConfigurationCapability) {
|
||||
const item: ConfigurationItem = {};
|
||||
if (scopeUri !== undefined) {
|
||||
item.scopeUri = scopeUri;
|
||||
}
|
||||
if (section !== undefined) {
|
||||
item.section = section;
|
||||
}
|
||||
return this._connection.workspace.getConfiguration(item);
|
||||
}
|
||||
|
||||
if (this._defaultClientConfig) {
|
||||
return getNestedProperty(this._defaultClientConfig, section);
|
||||
}
|
||||
|
||||
return undefined;
|
||||
}
|
||||
|
||||
protected isOpenFilesOnly(diagnosticMode: string): boolean {
|
||||
return diagnosticMode !== 'workspace';
|
||||
}
|
||||
|
||||
protected getSeverityOverrides(value: string): DiagnosticSeverityOverrides | undefined {
|
||||
const enumValue = value as DiagnosticSeverityOverrides;
|
||||
if (getDiagnosticSeverityOverrides().includes(enumValue)) {
|
||||
return enumValue;
|
||||
}
|
||||
|
||||
return undefined;
|
||||
}
|
||||
|
||||
protected getDiagnosticRuleName(value: string): DiagnosticRule | undefined {
|
||||
const enumValue = value as DiagnosticRule;
|
||||
if (getDiagLevelDiagnosticRules().includes(enumValue)) {
|
||||
return enumValue;
|
||||
}
|
||||
|
||||
return undefined;
|
||||
}
|
||||
|
||||
protected abstract createHost(): Host;
|
||||
protected abstract createImportResolver(fs: FileSystem, options: ConfigOptions, host: Host): ImportResolver;
|
||||
|
||||
protected createBackgroundAnalysisProgram(
|
||||
serviceId: string,
|
||||
console: ConsoleInterface,
|
||||
configOptions: ConfigOptions,
|
||||
importResolver: ImportResolver,
|
||||
backgroundAnalysis?: BackgroundAnalysisBase,
|
||||
maxAnalysisTime?: MaxAnalysisTime,
|
||||
cacheManager?: CacheManager
|
||||
): BackgroundAnalysisProgram {
|
||||
return new BackgroundAnalysisProgram(
|
||||
console,
|
||||
configOptions,
|
||||
importResolver,
|
||||
backgroundAnalysis,
|
||||
maxAnalysisTime,
|
||||
/* disableChecker */ undefined,
|
||||
cacheManager
|
||||
);
|
||||
}
|
||||
|
||||
protected setupConnection(supportedCommands: string[], supportedCodeActions: string[]): void {
|
||||
// After the server has started the client sends an initialize request. The server receives
|
||||
// in the passed params the rootPath of the workspace plus the client capabilities.
|
||||
@ -730,49 +782,6 @@ export abstract class LanguageServerBase implements LanguageServerInterface {
|
||||
this._setupFileWatcher();
|
||||
}
|
||||
|
||||
private _setupFileWatcher() {
|
||||
if (!this.client.hasWatchFileCapability) {
|
||||
return;
|
||||
}
|
||||
|
||||
const watchKind = WatchKind.Create | WatchKind.Change | WatchKind.Delete;
|
||||
|
||||
// Set default (config files and all workspace files) first.
|
||||
const watchers: FileSystemWatcher[] = [
|
||||
...configFileNames.map((fileName) => ({ globPattern: `**/${fileName}`, kind: watchKind })),
|
||||
{ globPattern: '**', kind: watchKind },
|
||||
];
|
||||
|
||||
// Add all python search paths to watch list
|
||||
if (this.client.hasWatchFileRelativePathCapability) {
|
||||
// Dedup search paths from all workspaces.
|
||||
// Get rid of any search path under workspace root since it is already watched by
|
||||
// "**" above.
|
||||
const foldersToWatch = deduplicateFolders(
|
||||
this._workspaceFactory
|
||||
.getNonDefaultWorkspaces()
|
||||
.map((w) => w.searchPathsToWatch.filter((p) => !p.startsWith(w.rootPath)))
|
||||
);
|
||||
|
||||
foldersToWatch.forEach((p) => {
|
||||
const globPattern = isFile(this._serviceFS, p, /* treatZipDirectoryAsFile */ true)
|
||||
? { baseUri: convertPathToUri(this._serviceFS, getDirectoryPath(p)), pattern: getFileName(p) }
|
||||
: { baseUri: convertPathToUri(this._serviceFS, p), pattern: '**' };
|
||||
|
||||
watchers.push({ globPattern, kind: watchKind });
|
||||
});
|
||||
}
|
||||
|
||||
// File watcher is pylance wide service. Dispose all existing file watchers and create new ones.
|
||||
this._connection.client.register(DidChangeWatchedFilesNotification.type, { watchers }).then((d) => {
|
||||
if (this._lastFileWatcherRegistration) {
|
||||
this._lastFileWatcherRegistration.dispose();
|
||||
}
|
||||
|
||||
this._lastFileWatcherRegistration = d;
|
||||
});
|
||||
}
|
||||
|
||||
protected onDidChangeConfiguration(params: DidChangeConfigurationParams) {
|
||||
this.console.log(`Received updated settings`);
|
||||
if (params?.settings) {
|
||||
@ -1312,22 +1321,6 @@ export abstract class LanguageServerBase implements LanguageServerInterface {
|
||||
);
|
||||
}
|
||||
|
||||
updateSettingsForAllWorkspaces(): void {
|
||||
const tasks: Promise<void>[] = [];
|
||||
this._workspaceFactory.items().forEach((workspace) => {
|
||||
// Updating settings can change workspace's file ownership. Make workspace uninitialized so that
|
||||
// features can wait until workspace gets new settings.
|
||||
// the file's ownership can also changed by `pyrightconfig.json` changes, but those are synchronous
|
||||
// operation, so it won't affect this.
|
||||
workspace.isInitialized = workspace.isInitialized.reset();
|
||||
tasks.push(this.updateSettingsForWorkspace(workspace, workspace.isInitialized));
|
||||
});
|
||||
|
||||
Promise.all(tasks).then(() => {
|
||||
this._setupFileWatcher();
|
||||
});
|
||||
}
|
||||
|
||||
protected getCompletionOptions(workspace: Workspace, params?: CompletionParams): CompletionOptions {
|
||||
return {
|
||||
format: this.client.completionDocFormat,
|
||||
@ -1387,42 +1380,6 @@ export abstract class LanguageServerBase implements LanguageServerInterface {
|
||||
}
|
||||
}
|
||||
|
||||
async updateSettingsForWorkspace(
|
||||
workspace: Workspace,
|
||||
status: InitStatus | undefined,
|
||||
serverSettings?: ServerSettings
|
||||
): Promise<void> {
|
||||
status?.markCalled();
|
||||
|
||||
serverSettings = serverSettings ?? (await this.getSettings(workspace));
|
||||
|
||||
// Set logging level first.
|
||||
(this.console as ConsoleWithLogLevel).level = serverSettings.logLevel ?? LogLevel.Info;
|
||||
|
||||
// Apply the new path to the workspace (before restarting the service).
|
||||
serverSettings.pythonPath = this._workspaceFactory.applyPythonPath(workspace, serverSettings.pythonPath);
|
||||
|
||||
// Then use the updated settings to restart the service.
|
||||
this.updateOptionsAndRestartService(workspace, serverSettings);
|
||||
|
||||
workspace.disableLanguageServices = !!serverSettings.disableLanguageServices;
|
||||
workspace.disableOrganizeImports = !!serverSettings.disableOrganizeImports;
|
||||
|
||||
// Don't use workspace.isInitialized directly since it might have been
|
||||
// reset due to pending config change event.
|
||||
// The workspace is now open for business.
|
||||
status?.resolve();
|
||||
}
|
||||
|
||||
updateOptionsAndRestartService(
|
||||
workspace: Workspace,
|
||||
serverSettings: ServerSettings,
|
||||
typeStubTargetImportName?: string
|
||||
) {
|
||||
AnalyzerServiceExecutor.runWithOptions(this.rootPath, workspace, serverSettings, typeStubTargetImportName);
|
||||
workspace.searchPathsToWatch = workspace.service.librarySearchPathsToWatch ?? [];
|
||||
}
|
||||
|
||||
protected onWorkspaceCreated(workspace: Workspace) {
|
||||
// Update settings on this workspace (but only if initialize has happened)
|
||||
if (this._initialized) {
|
||||
@ -1456,6 +1413,69 @@ export abstract class LanguageServerBase implements LanguageServerInterface {
|
||||
return this.createAnalyzerService(name, services, libraryReanalysisTimeProvider);
|
||||
}
|
||||
|
||||
protected recordUserInteractionTime() {
|
||||
// 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._workspaceFactory.items().forEach((workspace: { service: { recordUserInteractionTime: () => void } }) => {
|
||||
workspace.service.recordUserInteractionTime();
|
||||
});
|
||||
}
|
||||
|
||||
protected getDocumentationUrlForDiagnosticRule(rule: string): string | undefined {
|
||||
// Configuration.md is configured to have a link for every rule name.
|
||||
return `https://github.com/microsoft/pyright/blob/main/docs/configuration.md#${rule}`;
|
||||
}
|
||||
|
||||
protected abstract createProgressReporter(): ProgressReporter;
|
||||
|
||||
protected canNavigateToFile(path: string, fs: FileSystem): boolean {
|
||||
return canNavigateToFile(fs, path);
|
||||
}
|
||||
|
||||
private _setupFileWatcher() {
|
||||
if (!this.client.hasWatchFileCapability) {
|
||||
return;
|
||||
}
|
||||
|
||||
const watchKind = WatchKind.Create | WatchKind.Change | WatchKind.Delete;
|
||||
|
||||
// Set default (config files and all workspace files) first.
|
||||
const watchers: FileSystemWatcher[] = [
|
||||
...configFileNames.map((fileName) => ({ globPattern: `**/${fileName}`, kind: watchKind })),
|
||||
{ globPattern: '**', kind: watchKind },
|
||||
];
|
||||
|
||||
// Add all python search paths to watch list
|
||||
if (this.client.hasWatchFileRelativePathCapability) {
|
||||
// Dedup search paths from all workspaces.
|
||||
// Get rid of any search path under workspace root since it is already watched by
|
||||
// "**" above.
|
||||
const foldersToWatch = deduplicateFolders(
|
||||
this._workspaceFactory
|
||||
.getNonDefaultWorkspaces()
|
||||
.map((w) => w.searchPathsToWatch.filter((p) => !p.startsWith(w.rootPath)))
|
||||
);
|
||||
|
||||
foldersToWatch.forEach((p) => {
|
||||
const globPattern = isFile(this._serviceFS, p, /* treatZipDirectoryAsFile */ true)
|
||||
? { baseUri: convertPathToUri(this._serviceFS, getDirectoryPath(p)), pattern: getFileName(p) }
|
||||
: { baseUri: convertPathToUri(this._serviceFS, p), pattern: '**' };
|
||||
|
||||
watchers.push({ globPattern, kind: watchKind });
|
||||
});
|
||||
}
|
||||
|
||||
// File watcher is pylance wide service. Dispose all existing file watchers and create new ones.
|
||||
this._connection.client.register(DidChangeWatchedFilesNotification.type, { watchers }).then((d) => {
|
||||
if (this._lastFileWatcherRegistration) {
|
||||
this._lastFileWatcherRegistration.dispose();
|
||||
}
|
||||
|
||||
this._lastFileWatcherRegistration = d;
|
||||
});
|
||||
}
|
||||
|
||||
private _sendDiagnostics(params: PublishDiagnosticsParams[]) {
|
||||
for (const param of params) {
|
||||
this._connection.sendDiagnostics(param);
|
||||
@ -1596,24 +1616,4 @@ export abstract class LanguageServerBase implements LanguageServerInterface {
|
||||
|
||||
return convertedDiags;
|
||||
}
|
||||
|
||||
protected recordUserInteractionTime() {
|
||||
// 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._workspaceFactory.items().forEach((workspace: { service: { recordUserInteractionTime: () => void } }) => {
|
||||
workspace.service.recordUserInteractionTime();
|
||||
});
|
||||
}
|
||||
|
||||
protected getDocumentationUrlForDiagnosticRule(rule: string): string | undefined {
|
||||
// Configuration.md is configured to have a link for every rule name.
|
||||
return `https://github.com/microsoft/pyright/blob/main/docs/configuration.md#${rule}`;
|
||||
}
|
||||
|
||||
protected abstract createProgressReporter(): ProgressReporter;
|
||||
|
||||
protected canNavigateToFile(path: string, fs: FileSystem): boolean {
|
||||
return canNavigateToFile(fs, path);
|
||||
}
|
||||
}
|
||||
|
@ -229,6 +229,10 @@ export class CallHierarchyProvider {
|
||||
return callItems;
|
||||
}
|
||||
|
||||
private get _evaluator(): TypeEvaluator {
|
||||
return this._program.evaluator!;
|
||||
}
|
||||
|
||||
private _getTargetDeclaration(referencesResult: ReferencesResult): {
|
||||
targetDecl: Declaration;
|
||||
callItemUri: string;
|
||||
@ -271,10 +275,6 @@ export class CallHierarchyProvider {
|
||||
return { targetDecl, callItemUri, symbolName };
|
||||
}
|
||||
|
||||
private get _evaluator(): TypeEvaluator {
|
||||
return this._program.evaluator!;
|
||||
}
|
||||
|
||||
private _getIncomingCallsForDeclaration(
|
||||
parseResults: ParseResults,
|
||||
filePath: string,
|
||||
|
@ -24,8 +24,8 @@ import {
|
||||
createSynthesizedAliasDeclaration,
|
||||
getDeclarationsWithUsesLocalNameRemoved,
|
||||
} from '../analyzer/declarationUtils';
|
||||
import { getModuleNode, getStringNodeValueRange } from '../analyzer/parseTreeUtils';
|
||||
import * as ParseTreeUtils from '../analyzer/parseTreeUtils';
|
||||
import { getModuleNode, getStringNodeValueRange } from '../analyzer/parseTreeUtils';
|
||||
import { ParseTreeWalker } from '../analyzer/parseTreeWalker';
|
||||
import { ScopeType } from '../analyzer/scope';
|
||||
import * as ScopeUtils from '../analyzer/scopeUtils';
|
||||
@ -63,6 +63,45 @@ export enum DocumentSymbolCollectorUseCase {
|
||||
// This walker looks for symbols that are semantically equivalent
|
||||
// to the requested symbol.
|
||||
export class DocumentSymbolCollector extends ParseTreeWalker {
|
||||
private _results: CollectionResult[] = [];
|
||||
private _dunderAllNameNodes = new Set<StringNode>();
|
||||
private _initFunction: FunctionNode | undefined;
|
||||
private _symbolNames: Set<string> = new Set<string>();
|
||||
|
||||
constructor(
|
||||
symbolNames: string[],
|
||||
private _declarations: Declaration[],
|
||||
private _evaluator: TypeEvaluator,
|
||||
private _cancellationToken: CancellationToken,
|
||||
private _startingNode: ParseNode,
|
||||
private _treatModuleInImportAndFromImportSame = false,
|
||||
private _skipUnreachableCode = true,
|
||||
private _useCase = DocumentSymbolCollectorUseCase.Reference
|
||||
) {
|
||||
super();
|
||||
|
||||
// Start with the symbols passed in
|
||||
symbolNames.forEach((s) => this._symbolNames.add(s));
|
||||
|
||||
// Don't report strings in __all__ right away, that will
|
||||
// break the assumption on the result ordering.
|
||||
this._setDunderAllNodes(this._startingNode);
|
||||
|
||||
// Check if one of our symbols is __init__ and we
|
||||
// have a class declaration in the list and we are
|
||||
// computing symbols for references and not rename.
|
||||
const initDeclaration = _declarations.find(
|
||||
(d) => d.type === DeclarationType.Function && d.node.name.value === '__init__'
|
||||
);
|
||||
if (initDeclaration && _useCase === DocumentSymbolCollectorUseCase.Reference) {
|
||||
const classDeclaration = _declarations.find((d) => d.type === DeclarationType.Class);
|
||||
if (classDeclaration) {
|
||||
this._initFunction = initDeclaration.node as FunctionNode;
|
||||
this._symbolNames.add((classDeclaration.node as ClassNode).name.value);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static collectFromNode(
|
||||
node: NameNode,
|
||||
evaluator: TypeEvaluator,
|
||||
@ -161,45 +200,6 @@ export class DocumentSymbolCollector extends ParseTreeWalker {
|
||||
return resolvedDeclarations;
|
||||
}
|
||||
|
||||
private _results: CollectionResult[] = [];
|
||||
private _dunderAllNameNodes = new Set<StringNode>();
|
||||
private _initFunction: FunctionNode | undefined;
|
||||
private _symbolNames: Set<string> = new Set<string>();
|
||||
|
||||
constructor(
|
||||
symbolNames: string[],
|
||||
private _declarations: Declaration[],
|
||||
private _evaluator: TypeEvaluator,
|
||||
private _cancellationToken: CancellationToken,
|
||||
private _startingNode: ParseNode,
|
||||
private _treatModuleInImportAndFromImportSame = false,
|
||||
private _skipUnreachableCode = true,
|
||||
private _useCase = DocumentSymbolCollectorUseCase.Reference
|
||||
) {
|
||||
super();
|
||||
|
||||
// Start with the symbols passed in
|
||||
symbolNames.forEach((s) => this._symbolNames.add(s));
|
||||
|
||||
// Don't report strings in __all__ right away, that will
|
||||
// break the assumption on the result ordering.
|
||||
this._setDunderAllNodes(this._startingNode);
|
||||
|
||||
// Check if one of our symbols is __init__ and we
|
||||
// have a class declaration in the list and we are
|
||||
// computing symbols for references and not rename.
|
||||
const initDeclaration = _declarations.find(
|
||||
(d) => d.type === DeclarationType.Function && d.node.name.value === '__init__'
|
||||
);
|
||||
if (initDeclaration && _useCase === DocumentSymbolCollectorUseCase.Reference) {
|
||||
const classDeclaration = _declarations.find((d) => d.type === DeclarationType.Class);
|
||||
if (classDeclaration) {
|
||||
this._initFunction = initDeclaration.node as FunctionNode;
|
||||
this._symbolNames.add((classDeclaration.node as ClassNode).name.value);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
collect() {
|
||||
this.walk(this._startingNode);
|
||||
return this._results;
|
||||
@ -283,7 +283,8 @@ export class DocumentSymbolCollector extends ParseTreeWalker {
|
||||
)
|
||||
);
|
||||
if (match) {
|
||||
// Special case for __init__ being one of our symbol names and we have a classname as the other.
|
||||
// Special case for __init__ being one of our symbol names and we
|
||||
// have a class name as the other.
|
||||
if (this._initFunction) {
|
||||
// If this is a method, must be an __init__ reference.
|
||||
if (match.type === DeclarationType.Function) {
|
||||
|
@ -18,15 +18,15 @@ import {
|
||||
isVariableDeclaration,
|
||||
} from '../analyzer/declaration';
|
||||
import { createSynthesizedAliasDeclaration, getNameFromDeclaration } from '../analyzer/declarationUtils';
|
||||
import { createImportedModuleDescriptor, ImportResolver, ModuleNameAndType } from '../analyzer/importResolver';
|
||||
import { ImportResolver, ModuleNameAndType, createImportedModuleDescriptor } from '../analyzer/importResolver';
|
||||
import {
|
||||
ImportStatement,
|
||||
ImportStatements,
|
||||
getDirectoryLeadingDotsPointsTo,
|
||||
getImportGroupFromModuleNameAndType,
|
||||
getRelativeModuleName,
|
||||
getTopLevelImports,
|
||||
haveSameParentModule,
|
||||
ImportStatement,
|
||||
ImportStatements,
|
||||
} from '../analyzer/importStatementUtils';
|
||||
import {
|
||||
getDottedNameWithGivenNodeAsLastName,
|
||||
@ -69,13 +69,13 @@ import {
|
||||
ImportAsNode,
|
||||
ImportFromAsNode,
|
||||
ImportFromNode,
|
||||
isExpressionNode,
|
||||
MemberAccessNode,
|
||||
ModuleNameNode,
|
||||
ModuleNode,
|
||||
NameNode,
|
||||
ParseNode,
|
||||
ParseNodeType,
|
||||
isExpressionNode,
|
||||
} from '../parser/parseNodes';
|
||||
import { ParseResults } from '../parser/parser';
|
||||
import { CollectionResult, DocumentSymbolCollector, DocumentSymbolCollectorUseCase } from './documentSymbolCollector';
|
||||
@ -87,6 +87,42 @@ enum UpdateType {
|
||||
}
|
||||
|
||||
export class RenameModuleProvider {
|
||||
private readonly _newModuleFilePath: string;
|
||||
private readonly _moduleNames: string[];
|
||||
private readonly _newModuleNames: string[];
|
||||
private readonly _onlyNameChanged: boolean;
|
||||
private readonly _aliasIntroduced = new Set<ImportAsNode>();
|
||||
private readonly _textEditTracker = new TextEditTracker();
|
||||
|
||||
private constructor(
|
||||
private _fs: FileSystem,
|
||||
private _evaluator: TypeEvaluator,
|
||||
private _moduleFilePath: string,
|
||||
newModuleFilePath: string,
|
||||
private _moduleNameAndType: ModuleNameAndType,
|
||||
private _newModuleNameAndType: ModuleNameAndType,
|
||||
private _type: UpdateType,
|
||||
public declarations: Declaration[],
|
||||
private _token: CancellationToken
|
||||
) {
|
||||
// moduleName and newModuleName are always in the absolute path form.
|
||||
this._newModuleFilePath = resolvePaths(newModuleFilePath);
|
||||
|
||||
this._moduleNames = this._moduleName.split('.');
|
||||
this._newModuleNames = this._newModuleName.split('.');
|
||||
|
||||
this._onlyNameChanged = haveSameParentModule(this._moduleNames, this._newModuleNames);
|
||||
assert(this._type !== UpdateType.Folder || this._onlyNameChanged, 'We only support simple rename for folder');
|
||||
}
|
||||
|
||||
get lastModuleName() {
|
||||
return this._moduleNames[this._moduleNames.length - 1];
|
||||
}
|
||||
|
||||
get textEditTracker(): TextEditTracker {
|
||||
return this._textEditTracker;
|
||||
}
|
||||
|
||||
static createForModule(
|
||||
importResolver: ImportResolver,
|
||||
configOptions: ConfigOptions,
|
||||
@ -302,6 +338,65 @@ export class RenameModuleProvider {
|
||||
}
|
||||
}
|
||||
|
||||
getEdits(): FileEditAction[] {
|
||||
return this._textEditTracker.getEdits(this._token);
|
||||
}
|
||||
|
||||
renameReferences(parseResults: ParseResults) {
|
||||
switch (this._type) {
|
||||
case UpdateType.Folder:
|
||||
return this._renameFolderReferences(parseResults);
|
||||
case UpdateType.File:
|
||||
return this._renameModuleReferences(parseResults);
|
||||
case UpdateType.Symbol:
|
||||
return this._updateSymbolReferences(parseResults);
|
||||
default:
|
||||
return assertNever(this._type, `${this._type} is unknown`);
|
||||
}
|
||||
}
|
||||
|
||||
tryGetFirstSymbolUsage(parseResults: ParseResults, symbol?: { name: string; decls: Declaration[] }) {
|
||||
const name = symbol?.name ?? getNameFromDeclaration(this.declarations[0]) ?? '';
|
||||
const collector = new DocumentSymbolCollector(
|
||||
[name],
|
||||
symbol?.decls ?? this.declarations,
|
||||
this._evaluator!,
|
||||
this._token,
|
||||
parseResults.parseTree,
|
||||
/* treatModuleImportAndFromImportSame */ true,
|
||||
/* skipUnreachableCode */ false
|
||||
);
|
||||
|
||||
for (const result of collector.collect().sort((r1, r2) => r1.range.start - r2.range.start)) {
|
||||
// We only care about symbol usages, not alias decl of the symbol.
|
||||
if (
|
||||
isImportModuleName(result.node) ||
|
||||
isImportAlias(result.node) ||
|
||||
isFromImportModuleName(result.node) ||
|
||||
isFromImportName(result.node) ||
|
||||
isFromImportAlias(result.node)
|
||||
) {
|
||||
continue;
|
||||
}
|
||||
|
||||
return result.range.start;
|
||||
}
|
||||
|
||||
return undefined;
|
||||
}
|
||||
|
||||
private get _moduleName() {
|
||||
return this._moduleNameAndType.moduleName;
|
||||
}
|
||||
|
||||
private get _newLastModuleName() {
|
||||
return this._newModuleNames[this._newModuleNames.length - 1];
|
||||
}
|
||||
|
||||
private get _newModuleName() {
|
||||
return this._newModuleNameAndType.moduleName;
|
||||
}
|
||||
|
||||
private static _create(
|
||||
importResolver: ImportResolver,
|
||||
configOptions: ConfigOptions,
|
||||
@ -356,89 +451,6 @@ export class RenameModuleProvider {
|
||||
);
|
||||
}
|
||||
|
||||
private readonly _newModuleFilePath: string;
|
||||
private readonly _moduleNames: string[];
|
||||
private readonly _newModuleNames: string[];
|
||||
private readonly _onlyNameChanged: boolean;
|
||||
private readonly _aliasIntroduced = new Set<ImportAsNode>();
|
||||
private readonly _textEditTracker = new TextEditTracker();
|
||||
|
||||
private constructor(
|
||||
private _fs: FileSystem,
|
||||
private _evaluator: TypeEvaluator,
|
||||
private _moduleFilePath: string,
|
||||
newModuleFilePath: string,
|
||||
private _moduleNameAndType: ModuleNameAndType,
|
||||
private _newModuleNameAndType: ModuleNameAndType,
|
||||
private _type: UpdateType,
|
||||
public declarations: Declaration[],
|
||||
private _token: CancellationToken
|
||||
) {
|
||||
// moduleName and newModuleName are always in the absolute path form.
|
||||
this._newModuleFilePath = resolvePaths(newModuleFilePath);
|
||||
|
||||
this._moduleNames = this._moduleName.split('.');
|
||||
this._newModuleNames = this._newModuleName.split('.');
|
||||
|
||||
this._onlyNameChanged = haveSameParentModule(this._moduleNames, this._newModuleNames);
|
||||
assert(this._type !== UpdateType.Folder || this._onlyNameChanged, 'We only support simple rename for folder');
|
||||
}
|
||||
|
||||
get lastModuleName() {
|
||||
return this._moduleNames[this._moduleNames.length - 1];
|
||||
}
|
||||
|
||||
get textEditTracker(): TextEditTracker {
|
||||
return this._textEditTracker;
|
||||
}
|
||||
|
||||
getEdits(): FileEditAction[] {
|
||||
return this._textEditTracker.getEdits(this._token);
|
||||
}
|
||||
|
||||
renameReferences(parseResults: ParseResults) {
|
||||
switch (this._type) {
|
||||
case UpdateType.Folder:
|
||||
return this._renameFolderReferences(parseResults);
|
||||
case UpdateType.File:
|
||||
return this._renameModuleReferences(parseResults);
|
||||
case UpdateType.Symbol:
|
||||
return this._updateSymbolReferences(parseResults);
|
||||
default:
|
||||
return assertNever(this._type, `${this._type} is unknown`);
|
||||
}
|
||||
}
|
||||
|
||||
tryGetFirstSymbolUsage(parseResults: ParseResults, symbol?: { name: string; decls: Declaration[] }) {
|
||||
const name = symbol?.name ?? getNameFromDeclaration(this.declarations[0]) ?? '';
|
||||
const collector = new DocumentSymbolCollector(
|
||||
[name],
|
||||
symbol?.decls ?? this.declarations,
|
||||
this._evaluator!,
|
||||
this._token,
|
||||
parseResults.parseTree,
|
||||
/* treatModuleImportAndFromImportSame */ true,
|
||||
/* skipUnreachableCode */ false
|
||||
);
|
||||
|
||||
for (const result of collector.collect().sort((r1, r2) => r1.range.start - r2.range.start)) {
|
||||
// We only care about symbol usages, not alias decl of the symbol.
|
||||
if (
|
||||
isImportModuleName(result.node) ||
|
||||
isImportAlias(result.node) ||
|
||||
isFromImportModuleName(result.node) ||
|
||||
isFromImportName(result.node) ||
|
||||
isFromImportAlias(result.node)
|
||||
) {
|
||||
continue;
|
||||
}
|
||||
|
||||
return result.range.start;
|
||||
}
|
||||
|
||||
return undefined;
|
||||
}
|
||||
|
||||
private _updateSymbolReferences(parseResults: ParseResults) {
|
||||
const filePath = getFileInfo(parseResults.parseTree).filePath;
|
||||
const isSource = filePath === this._moduleFilePath;
|
||||
@ -1366,18 +1378,6 @@ export class RenameModuleProvider {
|
||||
// ex) x.y.z used in "from x.y.z import ..."
|
||||
return moduleName;
|
||||
}
|
||||
|
||||
private get _moduleName() {
|
||||
return this._moduleNameAndType.moduleName;
|
||||
}
|
||||
|
||||
private get _newLastModuleName() {
|
||||
return this._newModuleNames[this._newModuleNames.length - 1];
|
||||
}
|
||||
|
||||
private get _newModuleName() {
|
||||
return this._newModuleNameAndType.moduleName;
|
||||
}
|
||||
}
|
||||
|
||||
class ModuleNameCollector extends ParseTreeWalker {
|
||||
|
@ -59,6 +59,10 @@ export class SignatureHelpProvider {
|
||||
return this._convert(this._getSignatureHelp());
|
||||
}
|
||||
|
||||
private get _evaluator(): TypeEvaluator {
|
||||
return this._program.evaluator!;
|
||||
}
|
||||
|
||||
private _getSignatureHelp(): SignatureHelpResults | undefined {
|
||||
throwIfCancellationRequested(this._token);
|
||||
if (!this._parseResults) {
|
||||
@ -214,10 +218,6 @@ export class SignatureHelpProvider {
|
||||
return { signatures, activeSignature, activeParameter };
|
||||
}
|
||||
|
||||
private get _evaluator(): TypeEvaluator {
|
||||
return this._program.evaluator!;
|
||||
}
|
||||
|
||||
private _makeSignature(callNode: CallNode, signature: CallSignature): SignatureInfo {
|
||||
const functionType = signature.type;
|
||||
const stringParts = this._evaluator.printFunctionParts(functionType, PrintTypeFlags.ExpandTypedDictArgs);
|
||||
|
@ -27,10 +27,6 @@ export class CharacterStream {
|
||||
this._isEndOfStream = text.length === 0;
|
||||
}
|
||||
|
||||
getText(): string {
|
||||
return this._text;
|
||||
}
|
||||
|
||||
get position(): number {
|
||||
return this._position;
|
||||
}
|
||||
@ -44,6 +40,22 @@ export class CharacterStream {
|
||||
return this._currentChar;
|
||||
}
|
||||
|
||||
get nextChar(): number {
|
||||
return this.position + 1 < this._text.length ? this._text.charCodeAt(this.position + 1) : 0;
|
||||
}
|
||||
|
||||
get prevChar(): number {
|
||||
return this.position - 1 >= 0 ? this._text.charCodeAt(this.position - 1) : 0;
|
||||
}
|
||||
|
||||
get length(): number {
|
||||
return this._text.length;
|
||||
}
|
||||
|
||||
getText(): string {
|
||||
return this._text;
|
||||
}
|
||||
|
||||
// We also expose a (non-property) method that is
|
||||
// the equivalent of currentChar above. This allows
|
||||
// us to work around assumptions in the TypeScript
|
||||
@ -53,14 +65,6 @@ export class CharacterStream {
|
||||
return this._currentChar;
|
||||
}
|
||||
|
||||
get nextChar(): number {
|
||||
return this.position + 1 < this._text.length ? this._text.charCodeAt(this.position + 1) : 0;
|
||||
}
|
||||
|
||||
get prevChar(): number {
|
||||
return this.position - 1 >= 0 ? this._text.charCodeAt(this.position - 1) : 0;
|
||||
}
|
||||
|
||||
isEndOfStream(): boolean {
|
||||
return this._isEndOfStream;
|
||||
}
|
||||
@ -126,10 +130,6 @@ export class CharacterStream {
|
||||
return this._text.charCodeAt(index);
|
||||
}
|
||||
|
||||
get length(): number {
|
||||
return this._text.length;
|
||||
}
|
||||
|
||||
private _checkBounds(): void {
|
||||
if (this._position < 0) {
|
||||
this._position = 0;
|
||||
|
@ -19,7 +19,7 @@ import { assert } from '../common/debug';
|
||||
import { Diagnostic, DiagnosticAddendum } from '../common/diagnostic';
|
||||
import { DiagnosticSink } from '../common/diagnosticSink';
|
||||
import { convertOffsetsToRange, convertPositionToOffset } from '../common/positionUtils';
|
||||
import { latestStablePythonVersion, PythonVersion } from '../common/pythonVersion';
|
||||
import { PythonVersion, latestStablePythonVersion } from '../common/pythonVersion';
|
||||
import { TextRange } from '../common/textRange';
|
||||
import { TextRangeCollection } from '../common/textRangeCollection';
|
||||
import { timingStats } from '../common/timing';
|
||||
@ -50,12 +50,10 @@ import {
|
||||
ErrorNode,
|
||||
ExceptNode,
|
||||
ExpressionNode,
|
||||
extendRange,
|
||||
FormatStringNode,
|
||||
ForNode,
|
||||
FormatStringNode,
|
||||
FunctionAnnotationNode,
|
||||
FunctionNode,
|
||||
getNextNodeId,
|
||||
GlobalNode,
|
||||
IfNode,
|
||||
ImportAsNode,
|
||||
@ -117,6 +115,8 @@ import {
|
||||
WithNode,
|
||||
YieldFromNode,
|
||||
YieldNode,
|
||||
extendRange,
|
||||
getNextNodeId,
|
||||
} from './parseNodes';
|
||||
import * as StringTokenUtils from './stringTokenUtils';
|
||||
import { Tokenizer, TokenizerOutput } from './tokenizer';
|
||||
@ -129,11 +129,11 @@ import {
|
||||
NumberToken,
|
||||
OperatorToken,
|
||||
OperatorType,
|
||||
softKeywords,
|
||||
StringToken,
|
||||
StringTokenFlags,
|
||||
Token,
|
||||
TokenType,
|
||||
softKeywords,
|
||||
} from './tokenizerTypes';
|
||||
|
||||
interface ListResult<T> {
|
||||
@ -148,6 +148,13 @@ interface SubscriptListResult {
|
||||
}
|
||||
|
||||
export class ParseOptions {
|
||||
isStubFile: boolean;
|
||||
pythonVersion: PythonVersion;
|
||||
reportInvalidStringEscapeSequence: boolean;
|
||||
skipFunctionAndClassBody: boolean;
|
||||
ipythonMode: IPythonMode;
|
||||
reportErrorsForParsedStringContents: boolean;
|
||||
|
||||
constructor() {
|
||||
this.isStubFile = false;
|
||||
this.pythonVersion = latestStablePythonVersion;
|
||||
@ -156,13 +163,6 @@ export class ParseOptions {
|
||||
this.ipythonMode = IPythonMode.None;
|
||||
this.reportErrorsForParsedStringContents = false;
|
||||
}
|
||||
|
||||
isStubFile: boolean;
|
||||
pythonVersion: PythonVersion;
|
||||
reportInvalidStringEscapeSequence: boolean;
|
||||
skipFunctionAndClassBody: boolean;
|
||||
ipythonMode: IPythonMode;
|
||||
reportErrorsForParsedStringContents: boolean;
|
||||
}
|
||||
|
||||
export interface ParseResults {
|
||||
|
@ -245,6 +245,10 @@ export class PyrightFileSystem
|
||||
this._partialStubPackagePaths.clear();
|
||||
}
|
||||
|
||||
protected override _isMovedEntry(path: string) {
|
||||
return this._partialStubPackagePaths.has(path) || super._isMovedEntry(path);
|
||||
}
|
||||
|
||||
private _getRelativePathPartialStubs(path: string) {
|
||||
const paths: string[] = [];
|
||||
|
||||
@ -279,8 +283,4 @@ export class PyrightFileSystem
|
||||
searchAllStubs(path);
|
||||
return paths;
|
||||
}
|
||||
|
||||
protected override _isMovedEntry(path: string) {
|
||||
return this._partialStubPackagePaths.has(path) || super._isMovedEntry(path);
|
||||
}
|
||||
}
|
||||
|
@ -28,10 +28,10 @@ import { UriParser } from '../../../common/uriParser';
|
||||
import { LanguageServerInterface, MessageAction, ServerSettings, WindowInterface } from '../../../languageServerBase';
|
||||
import { CodeActionProvider } from '../../../languageService/codeActionProvider';
|
||||
import {
|
||||
createInitStatus,
|
||||
WellKnownWorkspaceKinds,
|
||||
Workspace,
|
||||
WorkspacePythonPathKind,
|
||||
createInitStatus,
|
||||
} from '../../../workspaceFactory';
|
||||
import { TestAccessHost } from '../testAccessHost';
|
||||
import { HostSpecificFeatures } from './testState';
|
||||
@ -76,6 +76,10 @@ export class TestFeatures implements HostSpecificFeatures {
|
||||
}
|
||||
|
||||
export class TestLanguageService implements LanguageServerInterface {
|
||||
readonly rootPath = path.sep;
|
||||
readonly window = new TestWindow();
|
||||
readonly supportAdvancedEdits = true;
|
||||
|
||||
private readonly _workspace: Workspace;
|
||||
private readonly _defaultWorkspace: Workspace;
|
||||
private readonly _uriParser: UriParser;
|
||||
@ -143,10 +147,6 @@ export class TestLanguageService implements LanguageServerInterface {
|
||||
restart(): void {
|
||||
// Don't do anything
|
||||
}
|
||||
|
||||
readonly rootPath = path.sep;
|
||||
readonly window = new TestWindow();
|
||||
readonly supportAdvancedEdits = true;
|
||||
}
|
||||
|
||||
class TestWindow implements WindowInterface {
|
||||
|
@ -1463,55 +1463,6 @@ export class TestState {
|
||||
this._cancellationToken.resetCancelled();
|
||||
}
|
||||
|
||||
private _convertGlobalOptionsToConfigOptions(projectRoot: string, mountPaths?: Map<string, string>): ConfigOptions {
|
||||
const configOptions = new ConfigOptions(projectRoot);
|
||||
|
||||
// add more global options as we need them
|
||||
const newConfigOptions = this._applyTestConfigOptions(configOptions, mountPaths);
|
||||
|
||||
// default tests to run use compact signatures.
|
||||
newConfigOptions.functionSignatureDisplay = SignatureDisplayType.compact;
|
||||
|
||||
return newConfigOptions;
|
||||
}
|
||||
|
||||
private _applyTestConfigOptions(configOptions: ConfigOptions, mountPaths?: Map<string, string>) {
|
||||
// Always enable "test mode".
|
||||
configOptions.internalTestMode = true;
|
||||
|
||||
// Always analyze all files
|
||||
configOptions.checkOnlyOpenFiles = false;
|
||||
|
||||
// make sure we set typing path
|
||||
if (configOptions.stubPath === undefined) {
|
||||
configOptions.stubPath = normalizePath(combinePaths(vfs.MODULE_PATH, 'typings'));
|
||||
}
|
||||
|
||||
configOptions.include.push(getFileSpec(this.fs, configOptions.projectRoot, '.'));
|
||||
configOptions.exclude.push(getFileSpec(this.fs, configOptions.projectRoot, typeshedFolder));
|
||||
configOptions.exclude.push(getFileSpec(this.fs, configOptions.projectRoot, distlibFolder));
|
||||
configOptions.exclude.push(getFileSpec(this.fs, configOptions.projectRoot, libFolder));
|
||||
|
||||
if (mountPaths) {
|
||||
for (const mountPath of mountPaths.keys()) {
|
||||
configOptions.exclude.push(getFileSpec(this.fs, configOptions.projectRoot, mountPath));
|
||||
}
|
||||
}
|
||||
|
||||
if (configOptions.functionSignatureDisplay === undefined) {
|
||||
configOptions.functionSignatureDisplay === SignatureDisplayType.compact;
|
||||
}
|
||||
|
||||
return configOptions;
|
||||
}
|
||||
|
||||
protected getFileContent(fileName: string): string {
|
||||
const files = this.testData.files.filter(
|
||||
(f) => comparePaths(f.fileName, fileName, this.testFS.ignoreCase) === Comparison.EqualTo
|
||||
);
|
||||
return files[0].content;
|
||||
}
|
||||
|
||||
convertPositionToOffset(fileName: string, position: Position): number {
|
||||
const lines = this._getTextRangeCollection(fileName);
|
||||
return convertPositionToOffset(position, lines)!;
|
||||
@ -1523,6 +1474,20 @@ export class TestState {
|
||||
return convertOffsetToPosition(offset, lines);
|
||||
}
|
||||
|
||||
analyze() {
|
||||
while (this.program.analyze()) {
|
||||
// Continue to call analyze until it completes. Since we're not
|
||||
// specifying a timeout, it should complete the first time.
|
||||
}
|
||||
}
|
||||
|
||||
protected getFileContent(fileName: string): string {
|
||||
const files = this.testData.files.filter(
|
||||
(f) => comparePaths(f.fileName, fileName, this.testFS.ignoreCase) === Comparison.EqualTo
|
||||
);
|
||||
return files[0].content;
|
||||
}
|
||||
|
||||
protected convertOffsetsToRange(fileName: string, startOffset: number, endOffset: number): PositionRange {
|
||||
const lines = this._getTextRangeCollection(fileName);
|
||||
|
||||
@ -1532,58 +1497,10 @@ export class TestState {
|
||||
};
|
||||
}
|
||||
|
||||
private _getParseResult(fileName: string) {
|
||||
const file = this.program.getBoundSourceFile(fileName)!;
|
||||
return file.getParseResults()!;
|
||||
}
|
||||
|
||||
private _getTextRangeCollection(fileName: string): TextRangeCollection<TextRange> {
|
||||
if (fileName in this._files) {
|
||||
return this._getParseResult(fileName).tokenizerOutput.lines;
|
||||
}
|
||||
|
||||
// slow path
|
||||
const fileContents = this.fs.readFileSync(fileName, 'utf8');
|
||||
const tokenizer = new Tokenizer();
|
||||
return tokenizer.tokenize(fileContents).lines;
|
||||
}
|
||||
|
||||
protected raiseError(message: string): never {
|
||||
throw new Error(this._messageAtLastKnownMarker(message));
|
||||
}
|
||||
|
||||
private _messageAtLastKnownMarker(message: string) {
|
||||
const locationDescription = this.lastKnownMarker
|
||||
? this.lastKnownMarker
|
||||
: this._getLineColStringAtPosition(this.currentCaretPosition);
|
||||
return `At ${locationDescription}: ${message}`;
|
||||
}
|
||||
|
||||
private _checkPostEditInvariants() {
|
||||
// blank for now
|
||||
}
|
||||
|
||||
private _editScriptAndUpdateMarkers(fileName: string, editStart: number, editEnd: number, newText: string) {
|
||||
// this.languageServiceAdapterHost.editScript(fileName, editStart, editEnd, newText);
|
||||
for (const marker of this.testData.markers) {
|
||||
if (marker.fileName === fileName) {
|
||||
marker.position = this._updatePosition(marker.position, editStart, editEnd, newText);
|
||||
}
|
||||
}
|
||||
|
||||
for (const range of this.testData.ranges) {
|
||||
if (range.fileName === fileName) {
|
||||
range.pos = this._updatePosition(range.pos, editStart, editEnd, newText);
|
||||
range.end = this._updatePosition(range.end, editStart, editEnd, newText);
|
||||
}
|
||||
}
|
||||
this.testData.rangesByText = undefined;
|
||||
}
|
||||
|
||||
private _removeWhitespace(text: string): string {
|
||||
return text.replace(/\s/g, '');
|
||||
}
|
||||
|
||||
protected createMultiMap<T>(values?: T[], getKey?: (t: T) => string): MultiMap<T> {
|
||||
const map = new Map<string, T[]>() as MultiMap<T>;
|
||||
map.add = multiMapAdd;
|
||||
@ -1626,6 +1543,114 @@ export class TestState {
|
||||
return this.getFileContent(fileName).slice(pos, end);
|
||||
}
|
||||
|
||||
protected verifyCompletionItem(expected: _.FourSlashCompletionItem, actual: CompletionItem) {
|
||||
assert.strictEqual(actual.label, expected.label);
|
||||
assert.strictEqual(actual.detail, expected.detail);
|
||||
assert.strictEqual(actual.kind, expected.kind);
|
||||
|
||||
assert.strictEqual(actual.insertText, expected.insertionText);
|
||||
this._verifyEdit(actual.textEdit as TextEdit, expected.textEdit);
|
||||
this._verifyEdits(actual.additionalTextEdits, expected.additionalTextEdits);
|
||||
|
||||
if (expected.detailDescription !== undefined) {
|
||||
assert.strictEqual(actual.labelDetails?.description, expected.detailDescription);
|
||||
}
|
||||
|
||||
if (expected.commitCharacters !== undefined) {
|
||||
expect(expected.commitCharacters.sort()).toEqual(actual.commitCharacters?.sort() ?? []);
|
||||
}
|
||||
}
|
||||
|
||||
private _convertGlobalOptionsToConfigOptions(projectRoot: string, mountPaths?: Map<string, string>): ConfigOptions {
|
||||
const configOptions = new ConfigOptions(projectRoot);
|
||||
|
||||
// add more global options as we need them
|
||||
const newConfigOptions = this._applyTestConfigOptions(configOptions, mountPaths);
|
||||
|
||||
// default tests to run use compact signatures.
|
||||
newConfigOptions.functionSignatureDisplay = SignatureDisplayType.compact;
|
||||
|
||||
return newConfigOptions;
|
||||
}
|
||||
|
||||
private _applyTestConfigOptions(configOptions: ConfigOptions, mountPaths?: Map<string, string>) {
|
||||
// Always enable "test mode".
|
||||
configOptions.internalTestMode = true;
|
||||
|
||||
// Always analyze all files
|
||||
configOptions.checkOnlyOpenFiles = false;
|
||||
|
||||
// make sure we set typing path
|
||||
if (configOptions.stubPath === undefined) {
|
||||
configOptions.stubPath = normalizePath(combinePaths(vfs.MODULE_PATH, 'typings'));
|
||||
}
|
||||
|
||||
configOptions.include.push(getFileSpec(this.fs, configOptions.projectRoot, '.'));
|
||||
configOptions.exclude.push(getFileSpec(this.fs, configOptions.projectRoot, typeshedFolder));
|
||||
configOptions.exclude.push(getFileSpec(this.fs, configOptions.projectRoot, distlibFolder));
|
||||
configOptions.exclude.push(getFileSpec(this.fs, configOptions.projectRoot, libFolder));
|
||||
|
||||
if (mountPaths) {
|
||||
for (const mountPath of mountPaths.keys()) {
|
||||
configOptions.exclude.push(getFileSpec(this.fs, configOptions.projectRoot, mountPath));
|
||||
}
|
||||
}
|
||||
|
||||
if (configOptions.functionSignatureDisplay === undefined) {
|
||||
configOptions.functionSignatureDisplay === SignatureDisplayType.compact;
|
||||
}
|
||||
|
||||
return configOptions;
|
||||
}
|
||||
|
||||
private _getParseResult(fileName: string) {
|
||||
const file = this.program.getBoundSourceFile(fileName)!;
|
||||
return file.getParseResults()!;
|
||||
}
|
||||
|
||||
private _getTextRangeCollection(fileName: string): TextRangeCollection<TextRange> {
|
||||
if (fileName in this._files) {
|
||||
return this._getParseResult(fileName).tokenizerOutput.lines;
|
||||
}
|
||||
|
||||
// slow path
|
||||
const fileContents = this.fs.readFileSync(fileName, 'utf8');
|
||||
const tokenizer = new Tokenizer();
|
||||
return tokenizer.tokenize(fileContents).lines;
|
||||
}
|
||||
|
||||
private _messageAtLastKnownMarker(message: string) {
|
||||
const locationDescription = this.lastKnownMarker
|
||||
? this.lastKnownMarker
|
||||
: this._getLineColStringAtPosition(this.currentCaretPosition);
|
||||
return `At ${locationDescription}: ${message}`;
|
||||
}
|
||||
|
||||
private _checkPostEditInvariants() {
|
||||
// blank for now
|
||||
}
|
||||
|
||||
private _editScriptAndUpdateMarkers(fileName: string, editStart: number, editEnd: number, newText: string) {
|
||||
// this.languageServiceAdapterHost.editScript(fileName, editStart, editEnd, newText);
|
||||
for (const marker of this.testData.markers) {
|
||||
if (marker.fileName === fileName) {
|
||||
marker.position = this._updatePosition(marker.position, editStart, editEnd, newText);
|
||||
}
|
||||
}
|
||||
|
||||
for (const range of this.testData.ranges) {
|
||||
if (range.fileName === fileName) {
|
||||
range.pos = this._updatePosition(range.pos, editStart, editEnd, newText);
|
||||
range.end = this._updatePosition(range.end, editStart, editEnd, newText);
|
||||
}
|
||||
}
|
||||
this.testData.rangesByText = undefined;
|
||||
}
|
||||
|
||||
private _removeWhitespace(text: string): string {
|
||||
return text.replace(/\s/g, '');
|
||||
}
|
||||
|
||||
private _getOnlyRange() {
|
||||
const ranges = this.getRanges();
|
||||
if (ranges.length !== 1) {
|
||||
@ -1778,13 +1803,6 @@ export class TestState {
|
||||
return position <= editStart ? position : position < editEnd ? -1 : position + length - +(editEnd - editStart);
|
||||
}
|
||||
|
||||
public analyze() {
|
||||
while (this.program.analyze()) {
|
||||
// Continue to call analyze until it completes. Since we're not
|
||||
// specifying a timeout, it should complete the first time.
|
||||
}
|
||||
}
|
||||
|
||||
private _getDiagnosticsPerFile() {
|
||||
const sourceFiles = this._files.map((f) => this.program.getSourceFile(f));
|
||||
const results = sourceFiles.map((sourceFile, index) => {
|
||||
@ -1927,24 +1945,6 @@ export class TestState {
|
||||
this.raiseError(`doesn't contain expected result: ${stringify(extra)}, actual: ${stringify(left)}`);
|
||||
}
|
||||
}
|
||||
|
||||
protected verifyCompletionItem(expected: _.FourSlashCompletionItem, actual: CompletionItem) {
|
||||
assert.strictEqual(actual.label, expected.label);
|
||||
assert.strictEqual(actual.detail, expected.detail);
|
||||
assert.strictEqual(actual.kind, expected.kind);
|
||||
|
||||
assert.strictEqual(actual.insertText, expected.insertionText);
|
||||
this._verifyEdit(actual.textEdit as TextEdit, expected.textEdit);
|
||||
this._verifyEdits(actual.additionalTextEdits, expected.additionalTextEdits);
|
||||
|
||||
if (expected.detailDescription !== undefined) {
|
||||
assert.strictEqual(actual.labelDetails?.description, expected.detailDescription);
|
||||
}
|
||||
|
||||
if (expected.commitCharacters !== undefined) {
|
||||
expect(expected.commitCharacters.sort()).toEqual(actual.commitCharacters?.sort() ?? []);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export function parseAndGetTestState(
|
||||
|
@ -19,8 +19,8 @@ import {
|
||||
} from '../../../common/fileSystem';
|
||||
import * as pathUtil from '../../../common/pathUtils';
|
||||
import { bufferFrom, createIOError } from '../utils';
|
||||
import { closeIterator, getIterator, Metadata, nextResult, SortedMap } from './../utils';
|
||||
import { validate, ValidationFlags } from './pathValidation';
|
||||
import { Metadata, SortedMap, closeIterator, getIterator, nextResult } from './../utils';
|
||||
import { ValidationFlags, validate } from './pathValidation';
|
||||
|
||||
export const MODULE_PATH = pathUtil.normalizeSlashes('/');
|
||||
|
||||
@ -111,6 +111,13 @@ export class TestFileSystem implements FileSystem {
|
||||
return Object.isFrozen(this);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the file system shadowed by this file system.
|
||||
*/
|
||||
get shadowRoot() {
|
||||
return this._shadowRoot;
|
||||
}
|
||||
|
||||
/**
|
||||
* Makes the file system read-only.
|
||||
*/
|
||||
@ -119,13 +126,6 @@ export class TestFileSystem implements FileSystem {
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the file system shadowed by this file system.
|
||||
*/
|
||||
get shadowRoot() {
|
||||
return this._shadowRoot;
|
||||
}
|
||||
|
||||
/**
|
||||
* Snapshots the current file system, effectively shadowing itself. This is useful for
|
||||
* generating file system patches using `.diff()` from one snapshot to the next. Performs
|
||||
@ -201,14 +201,6 @@ export class TestFileSystem implements FileSystem {
|
||||
return this._filemeta(node);
|
||||
}
|
||||
|
||||
private _filemeta(node: Inode): Metadata {
|
||||
if (!node.meta) {
|
||||
const parentMeta = node.shadowRoot && this._shadowRoot && this._shadowRoot._filemeta(node.shadowRoot);
|
||||
node.meta = new Metadata(parentMeta);
|
||||
}
|
||||
return node.meta;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the pathname of the current working directory.
|
||||
*
|
||||
@ -371,40 +363,6 @@ export class TestFileSystem implements FileSystem {
|
||||
return URI.file(path).toString();
|
||||
}
|
||||
|
||||
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)) {
|
||||
results.push(path);
|
||||
}
|
||||
}
|
||||
if (axis === 'ancestors-or-self' || axis === 'ancestors') {
|
||||
const dirname = pathUtil.getDirectoryPath(path);
|
||||
if (dirname !== path) {
|
||||
try {
|
||||
const stats = this._stat(this._walk(dirname, noFollow));
|
||||
if (!traversal.traverse || traversal.traverse(dirname, stats)) {
|
||||
this._scan(dirname, stats, 'ancestors-or-self', traversal, noFollow, results);
|
||||
}
|
||||
} catch {
|
||||
/* ignored */
|
||||
}
|
||||
}
|
||||
}
|
||||
if (axis === 'descendants-or-self' || axis === 'descendants') {
|
||||
if (stats.isDirectory() && (!traversal.traverse || traversal.traverse(path, stats))) {
|
||||
for (const file of this.readdirSync(path)) {
|
||||
try {
|
||||
const childpath = pathUtil.combinePaths(path, file);
|
||||
const stats = this._stat(this._walk(childpath, noFollow));
|
||||
this._scan(childpath, stats, 'descendants-or-self', traversal, noFollow, results);
|
||||
} catch {
|
||||
/* ignored */
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Mounts a physical or virtual file system at a location in this virtual file system.
|
||||
*
|
||||
@ -562,27 +520,6 @@ export class TestFileSystem implements FileSystem {
|
||||
return this._stat(this._walk(this._resolve(path), /* noFollow */ true));
|
||||
}
|
||||
|
||||
private _stat(entry: WalkResult) {
|
||||
const node = entry.node;
|
||||
if (!node) {
|
||||
throw createIOError(`ENOENT`, entry.realpath);
|
||||
}
|
||||
return new Stats(
|
||||
node.dev,
|
||||
node.ino,
|
||||
node.mode,
|
||||
node.nlink,
|
||||
/* rdev */ 0,
|
||||
/* size */ isFile(node) ? this._getSize(node) : isSymlink(node) ? node.symlink.length : 0,
|
||||
/* blksize */ 4096,
|
||||
/* blocks */ 0,
|
||||
node.atimeMs,
|
||||
node.mtimeMs,
|
||||
node.ctimeMs,
|
||||
node.birthtimeMs
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Read a directory. If `path` is a symbolic link, it is dereferenced.
|
||||
*
|
||||
@ -640,15 +577,6 @@ export class TestFileSystem implements FileSystem {
|
||||
this._mkdir(this._walk(this._resolve(path), /* noFollow */ true));
|
||||
}
|
||||
|
||||
private _mkdir({ parent, links, node: existingNode, basename }: WalkResult) {
|
||||
if (existingNode) {
|
||||
throw createIOError('EEXIST');
|
||||
}
|
||||
const time = this.time();
|
||||
const node = this._mknod(parent ? parent.dev : ++devCount, S_IFDIR, /* mode */ 0o777, time);
|
||||
this._addLink(parent, links, basename, node, time);
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove a directory.
|
||||
*
|
||||
@ -949,6 +877,78 @@ export class TestFileSystem implements FileSystem {
|
||||
// Do Nothing
|
||||
}
|
||||
|
||||
private _mkdir({ parent, links, node: existingNode, basename }: WalkResult) {
|
||||
if (existingNode) {
|
||||
throw createIOError('EEXIST');
|
||||
}
|
||||
const time = this.time();
|
||||
const node = this._mknod(parent ? parent.dev : ++devCount, S_IFDIR, /* mode */ 0o777, time);
|
||||
this._addLink(parent, links, basename, node, time);
|
||||
}
|
||||
|
||||
private _filemeta(node: Inode): Metadata {
|
||||
if (!node.meta) {
|
||||
const parentMeta = node.shadowRoot && this._shadowRoot && this._shadowRoot._filemeta(node.shadowRoot);
|
||||
node.meta = new Metadata(parentMeta);
|
||||
}
|
||||
return node.meta;
|
||||
}
|
||||
|
||||
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)) {
|
||||
results.push(path);
|
||||
}
|
||||
}
|
||||
if (axis === 'ancestors-or-self' || axis === 'ancestors') {
|
||||
const dirname = pathUtil.getDirectoryPath(path);
|
||||
if (dirname !== path) {
|
||||
try {
|
||||
const stats = this._stat(this._walk(dirname, noFollow));
|
||||
if (!traversal.traverse || traversal.traverse(dirname, stats)) {
|
||||
this._scan(dirname, stats, 'ancestors-or-self', traversal, noFollow, results);
|
||||
}
|
||||
} catch {
|
||||
/* ignored */
|
||||
}
|
||||
}
|
||||
}
|
||||
if (axis === 'descendants-or-self' || axis === 'descendants') {
|
||||
if (stats.isDirectory() && (!traversal.traverse || traversal.traverse(path, stats))) {
|
||||
for (const file of this.readdirSync(path)) {
|
||||
try {
|
||||
const childpath = pathUtil.combinePaths(path, file);
|
||||
const stats = this._stat(this._walk(childpath, noFollow));
|
||||
this._scan(childpath, stats, 'descendants-or-self', traversal, noFollow, results);
|
||||
} catch {
|
||||
/* ignored */
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private _stat(entry: WalkResult) {
|
||||
const node = entry.node;
|
||||
if (!node) {
|
||||
throw createIOError(`ENOENT`, entry.realpath);
|
||||
}
|
||||
return new Stats(
|
||||
node.dev,
|
||||
node.ino,
|
||||
node.mode,
|
||||
node.nlink,
|
||||
/* rdev */ 0,
|
||||
/* size */ isFile(node) ? this._getSize(node) : isSymlink(node) ? node.symlink.length : 0,
|
||||
/* blksize */ 4096,
|
||||
/* blocks */ 0,
|
||||
node.atimeMs,
|
||||
node.mtimeMs,
|
||||
node.ctimeMs,
|
||||
node.birthtimeMs
|
||||
);
|
||||
}
|
||||
|
||||
private static _diffWorker(
|
||||
container: FileSet,
|
||||
changed: TestFileSystem,
|
||||
@ -1026,7 +1026,7 @@ export class TestFileSystem implements FileSystem {
|
||||
return false;
|
||||
}
|
||||
|
||||
// no difference if the root links are empty and unshadowed
|
||||
// no difference if the root links are empty and not shadowed
|
||||
if (!changed._lazy.links && !changed._shadowRoot && !base._lazy.links && !base._shadowRoot) {
|
||||
return false;
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user