diff --git a/docs/configuration.md b/docs/configuration.md
index 907e950f7..a7a1c2b4e 100644
--- a/docs/configuration.md
+++ b/docs/configuration.md
@@ -44,137 +44,137 @@ Relative paths specified within the config file are relative to the config file
## Type Check Diagnostics Settings
The following settings control pyright’s diagnostic output (warnings or errors). Unless otherwise specified, each diagnostic setting can specify a boolean value (`false` indicating that no error is generated and `true` indicating that an error is generated). Alternatively, a string value of `"none"`, `"warning"`, `"information"`, or `"error"` can be used to specify the diagnostic level.
-**strictListInference** [boolean]: When inferring the type of a list, use strict type assumptions. For example, the expression `[1, 'a', 3.4]` could be inferred to be of type `list[Any]` or `list[int | str | float]`. If this setting is true, it will use the latter (stricter) type. The default value for this setting is 'false'.
+ **strictListInference** [boolean]: When inferring the type of a list, use strict type assumptions. For example, the expression `[1, 'a', 3.4]` could be inferred to be of type `list[Any]` or `list[int | str | float]`. If this setting is true, it will use the latter (stricter) type. The default value for this setting is 'false'.
-**strictDictionaryInference** [boolean]: When inferring the type of a dictionary’s keys and values, use strict type assumptions. For example, the expression `{'a': 1, 'b': 'a'}` could be inferred to be of type `dict[str, Any]` or `dict[str, int | str]`. If this setting is true, it will use the latter (stricter) type. The default value for this setting is 'false'.
+ **strictDictionaryInference** [boolean]: When inferring the type of a dictionary’s keys and values, use strict type assumptions. For example, the expression `{'a': 1, 'b': 'a'}` could be inferred to be of type `dict[str, Any]` or `dict[str, int | str]`. If this setting is true, it will use the latter (stricter) type. The default value for this setting is 'false'.
-**strictSetInference** [boolean]: When inferring the type of a set, use strict type assumptions. For example, the expression `{1, 'a', 3.4}` could be inferred to be of type `set[Any]` or `set[int | str | float]`. If this setting is true, it will use the latter (stricter) type. The default value for this setting is 'false'.
+ **strictSetInference** [boolean]: When inferring the type of a set, use strict type assumptions. For example, the expression `{1, 'a', 3.4}` could be inferred to be of type `set[Any]` or `set[int | str | float]`. If this setting is true, it will use the latter (stricter) type. The default value for this setting is 'false'.
-**strictParameterNoneValue** [boolean]: PEP 484 indicates that when a function parameter is assigned a default value of None, its type should implicitly be Optional even if the explicit type is not. When enabled, this rule requires that parameter type annotations use Optional explicitly in this case. The default value for this setting is 'true'.
+ **strictParameterNoneValue** [boolean]: PEP 484 indicates that when a function parameter is assigned a default value of None, its type should implicitly be Optional even if the explicit type is not. When enabled, this rule requires that parameter type annotations use Optional explicitly in this case. The default value for this setting is 'true'.
-**enableTypeIgnoreComments** [boolean]: PEP 484 defines support for "# type: ignore" comments. This switch enables or disables support for these comments. The default value for this setting is 'true'. This does not affect "# pyright: ignore" comments.
+ **enableTypeIgnoreComments** [boolean]: PEP 484 defines support for "# type: ignore" comments. This switch enables or disables support for these comments. The default value for this setting is 'true'. This does not affect "# pyright: ignore" comments.
-**reportGeneralTypeIssues** [boolean or string, optional]: Generate or suppress diagnostics for general type inconsistencies, unsupported operations, argument/parameter mismatches, etc. This covers all of the basic type-checking rules not covered by other rules. It does not include syntax errors. The default value for this setting is 'error'.
+ **reportGeneralTypeIssues** [boolean or string, optional]: Generate or suppress diagnostics for general type inconsistencies, unsupported operations, argument/parameter mismatches, etc. This covers all of the basic type-checking rules not covered by other rules. It does not include syntax errors. The default value for this setting is 'error'.
-**reportPropertyTypeMismatch** [boolean or string, optional]: Generate or suppress diagnostics for properties where the type of the value passed to the setter is not assignable to the value returned by the getter. Such mismatches violate the intended use of properties, which are meant to act like variables. The default value for this setting is 'none'.
+ **reportPropertyTypeMismatch** [boolean or string, optional]: Generate or suppress diagnostics for properties where the type of the value passed to the setter is not assignable to the value returned by the getter. Such mismatches violate the intended use of properties, which are meant to act like variables. The default value for this setting is 'none'.
-**reportFunctionMemberAccess** [boolean or string, optional]: Generate or suppress diagnostics for non-standard member accesses for functions. The default value for this setting is 'none'.
+ **reportFunctionMemberAccess** [boolean or string, optional]: Generate or suppress diagnostics for non-standard member accesses for functions. The default value for this setting is 'none'.
-**reportMissingImports** [boolean or string, optional]: Generate or suppress diagnostics for imports that have no corresponding imported python file or type stub file. The default value for this setting is 'error'.
+ **reportMissingImports** [boolean or string, optional]: Generate or suppress diagnostics for imports that have no corresponding imported python file or type stub file. The default value for this setting is 'error'.
-**reportMissingModuleSource** [boolean or string, optional]: Generate or suppress diagnostics for imports that have no corresponding source file. This happens when a type stub is found, but the module source file was not found, indicating that the code may fail at runtime when using this execution environment. Type checking will be done using the type stub. The default value for this setting is 'warning'.
+ **reportMissingModuleSource** [boolean or string, optional]: Generate or suppress diagnostics for imports that have no corresponding source file. This happens when a type stub is found, but the module source file was not found, indicating that the code may fail at runtime when using this execution environment. Type checking will be done using the type stub. The default value for this setting is 'warning'.
-**reportMissingTypeStubs** [boolean or string, optional]: Generate or suppress diagnostics for imports that have no corresponding type stub file (either a typeshed file or a custom type stub). The type checker requires type stubs to do its best job at analysis. The default value for this setting is 'none'. Note that there is a corresponding quick fix for this diagnostics that let you generate custom type stub to improve editing experiences.
+ **reportMissingTypeStubs** [boolean or string, optional]: Generate or suppress diagnostics for imports that have no corresponding type stub file (either a typeshed file or a custom type stub). The type checker requires type stubs to do its best job at analysis. The default value for this setting is 'none'. Note that there is a corresponding quick fix for this diagnostics that let you generate custom type stub to improve editing experiences.
-**reportImportCycles** [boolean or string, optional]: Generate or suppress diagnostics for cyclical import chains. These are not errors in Python, but they do slow down type analysis and often hint at architectural layering issues. Generally, they should be avoided. The default value for this setting is 'none'. Note that there are import cycles in the typeshed stdlib typestub files that are ignored by this setting.
+ **reportImportCycles** [boolean or string, optional]: Generate or suppress diagnostics for cyclical import chains. These are not errors in Python, but they do slow down type analysis and often hint at architectural layering issues. Generally, they should be avoided. The default value for this setting is 'none'. Note that there are import cycles in the typeshed stdlib typestub files that are ignored by this setting.
-**reportUnusedImport** [boolean or string, optional]: Generate or suppress diagnostics for an imported symbol that is not referenced within that file. The default value for this setting is 'none'.
+ **reportUnusedImport** [boolean or string, optional]: Generate or suppress diagnostics for an imported symbol that is not referenced within that file. The default value for this setting is 'none'.
-**reportUnusedClass** [boolean or string, optional]: Generate or suppress diagnostics for a class with a private name (starting with an underscore) that is not accessed. The default value for this setting is 'none'.
+ **reportUnusedClass** [boolean or string, optional]: Generate or suppress diagnostics for a class with a private name (starting with an underscore) that is not accessed. The default value for this setting is 'none'.
-**reportUnusedFunction** [boolean or string, optional]: Generate or suppress diagnostics for a function or method with a private name (starting with an underscore) that is not accessed. The default value for this setting is 'none'.
+ **reportUnusedFunction** [boolean or string, optional]: Generate or suppress diagnostics for a function or method with a private name (starting with an underscore) that is not accessed. The default value for this setting is 'none'.
-**reportUnusedVariable** [boolean or string, optional]: Generate or suppress diagnostics for a variable that is not accessed. The default value for this setting is 'none'. Variables whose names begin with an underscore are exempt from this check.
+ **reportUnusedVariable** [boolean or string, optional]: Generate or suppress diagnostics for a variable that is not accessed. The default value for this setting is 'none'. Variables whose names begin with an underscore are exempt from this check.
-**reportDuplicateImport** [boolean or string, optional]: Generate or suppress diagnostics for an imported symbol or module that is imported more than once. The default value for this setting is 'none'.
+ **reportDuplicateImport** [boolean or string, optional]: Generate or suppress diagnostics for an imported symbol or module that is imported more than once. The default value for this setting is 'none'.
-**reportWildcardImportFromLibrary** [boolean or string, optional]: Generate or suppress diagnostics for a wildcard import from an external library. The use of this language feature is highly discouraged and can result in bugs when the library is updated. The default value for this setting is 'warning'.
+ **reportWildcardImportFromLibrary** [boolean or string, optional]: Generate or suppress diagnostics for a wildcard import from an external library. The use of this language feature is highly discouraged and can result in bugs when the library is updated. The default value for this setting is 'warning'.
-**reportOptionalSubscript** [boolean or string, optional]: Generate or suppress diagnostics for an attempt to subscript (index) a variable with an Optional type. The default value for this setting is 'error'.
+ **reportOptionalSubscript** [boolean or string, optional]: Generate or suppress diagnostics for an attempt to subscript (index) a variable with an Optional type. The default value for this setting is 'error'.
-**reportOptionalMemberAccess** [boolean or string, optional]: Generate or suppress diagnostics for an attempt to access a member of a variable with an Optional type. The default value for this setting is 'error'.
+ **reportOptionalMemberAccess** [boolean or string, optional]: Generate or suppress diagnostics for an attempt to access a member of a variable with an Optional type. The default value for this setting is 'error'.
-**reportOptionalCall** [boolean or string, optional]: Generate or suppress diagnostics for an attempt to call a variable with an Optional type. The default value for this setting is 'error'.
+ **reportOptionalCall** [boolean or string, optional]: Generate or suppress diagnostics for an attempt to call a variable with an Optional type. The default value for this setting is 'error'.
-**reportOptionalIterable** [boolean or string, optional]: Generate or suppress diagnostics for an attempt to use an Optional type as an iterable value (e.g. within a `for` statement). The default value for this setting is 'error'.
+ **reportOptionalIterable** [boolean or string, optional]: Generate or suppress diagnostics for an attempt to use an Optional type as an iterable value (e.g. within a `for` statement). The default value for this setting is 'error'.
-**reportOptionalContextManager** [boolean or string, optional]: Generate or suppress diagnostics for an attempt to use an Optional type as a context manager (as a parameter to a `with` statement). The default value for this setting is 'error'.
+ **reportOptionalContextManager** [boolean or string, optional]: Generate or suppress diagnostics for an attempt to use an Optional type as a context manager (as a parameter to a `with` statement). The default value for this setting is 'error'.
-**reportOptionalOperand** [boolean or string, optional]: Generate or suppress diagnostics for an attempt to use an Optional type as an operand to a binary or unary operator (like '+', '==', 'or', 'not'). The default value for this setting is 'error'.
+ **reportOptionalOperand** [boolean or string, optional]: Generate or suppress diagnostics for an attempt to use an Optional type as an operand to a binary or unary operator (like '+', '==', 'or', 'not'). The default value for this setting is 'error'.
-**reportTypedDictNotRequiredAccess** [boolean or string, optional]: Generate or suppress diagnostics for an attempt to access a non-required field within a TypedDict without first checking whether it is present. The default value for this setting is 'error'.
+ **reportTypedDictNotRequiredAccess** [boolean or string, optional]: Generate or suppress diagnostics for an attempt to access a non-required field within a TypedDict without first checking whether it is present. The default value for this setting is 'error'.
-**reportUntypedFunctionDecorator** [boolean or string, optional]: Generate or suppress diagnostics for function decorators that have no type annotations. These obscure the function type, defeating many type analysis features. The default value for this setting is 'none'.
+ **reportUntypedFunctionDecorator** [boolean or string, optional]: Generate or suppress diagnostics for function decorators that have no type annotations. These obscure the function type, defeating many type analysis features. The default value for this setting is 'none'.
-**reportUntypedClassDecorator** [boolean or string, optional]: Generate or suppress diagnostics for class decorators that have no type annotations. These obscure the class type, defeating many type analysis features. The default value for this setting is 'none'.
+ **reportUntypedClassDecorator** [boolean or string, optional]: Generate or suppress diagnostics for class decorators that have no type annotations. These obscure the class type, defeating many type analysis features. The default value for this setting is 'none'.
-**reportUntypedBaseClass** [boolean or string, optional]: Generate or suppress diagnostics for base classes whose type cannot be determined statically. These obscure the class type, defeating many type analysis features. The default value for this setting is 'none'.
+ **reportUntypedBaseClass** [boolean or string, optional]: Generate or suppress diagnostics for base classes whose type cannot be determined statically. These obscure the class type, defeating many type analysis features. The default value for this setting is 'none'.
-**reportUntypedNamedTuple** [boolean or string, optional]: Generate or suppress diagnostics when “namedtuple” is used rather than “NamedTuple”. The former contains no type information, whereas the latter does. The default value for this setting is 'none'.
+ **reportUntypedNamedTuple** [boolean or string, optional]: Generate or suppress diagnostics when “namedtuple” is used rather than “NamedTuple”. The former contains no type information, whereas the latter does. The default value for this setting is 'none'.
-**reportPrivateUsage** [boolean or string, optional]: Generate or suppress diagnostics for incorrect usage of private or protected variables or functions. Protected class members begin with a single underscore (“_”) and can be accessed only by subclasses. Private class members begin with a double underscore but do not end in a double underscore and can be accessed only within the declaring class. Variables and functions declared outside of a class are considered private if their names start with either a single or double underscore, and they cannot be accessed outside of the declaring module. The default value for this setting is 'none'.
+ **reportPrivateUsage** [boolean or string, optional]: Generate or suppress diagnostics for incorrect usage of private or protected variables or functions. Protected class members begin with a single underscore (“_”) and can be accessed only by subclasses. Private class members begin with a double underscore but do not end in a double underscore and can be accessed only within the declaring class. Variables and functions declared outside of a class are considered private if their names start with either a single or double underscore, and they cannot be accessed outside of the declaring module. The default value for this setting is 'none'.
-**reportTypeCommentUsage** [boolean or string, optional]: Prior to Python 3.5, the grammar did not support type annotations, so types needed to be specified using “type comments”. Python 3.5 eliminated the need for function type comments, and Python 3.6 eliminated the need for variable type comments. Future versions of Python will likely deprecate all support for type comments. If enabled, this check will flag any type comment usage unless it is required for compatibility with the specified language version. The default value for this setting is 'none'.
+ **reportTypeCommentUsage** [boolean or string, optional]: Prior to Python 3.5, the grammar did not support type annotations, so types needed to be specified using “type comments”. Python 3.5 eliminated the need for function type comments, and Python 3.6 eliminated the need for variable type comments. Future versions of Python will likely deprecate all support for type comments. If enabled, this check will flag any type comment usage unless it is required for compatibility with the specified language version. The default value for this setting is 'none'.
-**reportPrivateImportUsage** [boolean or string, optional]: Generate or suppress diagnostics for use of a symbol from a "py.typed" module that is not meant to be exported from that module. The default value for this setting is 'error'.
+ **reportPrivateImportUsage** [boolean or string, optional]: Generate or suppress diagnostics for use of a symbol from a "py.typed" module that is not meant to be exported from that module. The default value for this setting is 'error'.
-**reportConstantRedefinition** [boolean or string, optional]: Generate or suppress diagnostics for attempts to redefine variables whose names are all-caps with underscores and numerals. The default value for this setting is 'none'.
+ **reportConstantRedefinition** [boolean or string, optional]: Generate or suppress diagnostics for attempts to redefine variables whose names are all-caps with underscores and numerals. The default value for this setting is 'none'.
-**reportIncompatibleMethodOverride** [boolean or string, optional]: Generate or suppress diagnostics for methods that override a method of the same name in a base class in an incompatible manner (wrong number of parameters, incompatible parameter types, or incompatible return type). The default value for this setting is 'none'.
+ **reportIncompatibleMethodOverride** [boolean or string, optional]: Generate or suppress diagnostics for methods that override a method of the same name in a base class in an incompatible manner (wrong number of parameters, incompatible parameter types, or incompatible return type). The default value for this setting is 'none'.
-**reportIncompatibleVariableOverride** [boolean or string, optional]: Generate or suppress diagnostics for class variable declarations that override a symbol of the same name in a base class with a type that is incompatible with the base class symbol type. The default value for this setting is 'none'.
+ **reportIncompatibleVariableOverride** [boolean or string, optional]: Generate or suppress diagnostics for class variable declarations that override a symbol of the same name in a base class with a type that is incompatible with the base class symbol type. The default value for this setting is 'none'.
-**reportInconsistentConstructor** [boolean or string, optional]: Generate or suppress diagnostics when an `__init__` method signature is inconsistent with a `__new__` signature. The default value for this setting is 'none'.
+ **reportInconsistentConstructor** [boolean or string, optional]: Generate or suppress diagnostics when an `__init__` method signature is inconsistent with a `__new__` signature. The default value for this setting is 'none'.
-**reportOverlappingOverload** [boolean or string, optional]: Generate or suppress diagnostics for function overloads that overlap in signature and obscure each other or have incompatible return types. The default value for this setting is 'none'.
+ **reportOverlappingOverload** [boolean or string, optional]: Generate or suppress diagnostics for function overloads that overlap in signature and obscure each other or have incompatible return types. The default value for this setting is 'none'.
-**reportMissingSuperCall** [boolean or string, optional]: Generate or suppress diagnostics for `__init__`, `__init_subclass__`, `__enter__` and `__exit__` methods in a subclass that fail to call through to the same-named method on a base class. The default value for this setting is 'none'.
+ **reportMissingSuperCall** [boolean or string, optional]: Generate or suppress diagnostics for `__init__`, `__init_subclass__`, `__enter__` and `__exit__` methods in a subclass that fail to call through to the same-named method on a base class. The default value for this setting is 'none'.
-**reportUninitializedInstanceVariable** [boolean or string, optional]: Generate or suppress diagnostics for instance variables within a class that are not initialized or declared within the class body or the `__init__` method. The default value for this setting is 'none'.
+ **reportUninitializedInstanceVariable** [boolean or string, optional]: Generate or suppress diagnostics for instance variables within a class that are not initialized or declared within the class body or the `__init__` method. The default value for this setting is 'none'.
-**reportInvalidStringEscapeSequence** [boolean or string, optional]: Generate or suppress diagnostics for invalid escape sequences used within string literals. The Python specification indicates that such sequences will generate a syntax error in future versions. The default value for this setting is 'warning'.
+ **reportInvalidStringEscapeSequence** [boolean or string, optional]: Generate or suppress diagnostics for invalid escape sequences used within string literals. The Python specification indicates that such sequences will generate a syntax error in future versions. The default value for this setting is 'warning'.
-**reportUnknownParameterType** [boolean or string, optional]: Generate or suppress diagnostics for input or return parameters for functions or methods that have an unknown type. The default value for this setting is 'none'.
+ **reportUnknownParameterType** [boolean or string, optional]: Generate or suppress diagnostics for input or return parameters for functions or methods that have an unknown type. The default value for this setting is 'none'.
-**reportUnknownArgumentType** [boolean or string, optional]: Generate or suppress diagnostics for call arguments for functions or methods that have an unknown type. The default value for this setting is 'none'.
+ **reportUnknownArgumentType** [boolean or string, optional]: Generate or suppress diagnostics for call arguments for functions or methods that have an unknown type. The default value for this setting is 'none'.
-**reportUnknownLambdaType** [boolean or string, optional]: Generate or suppress diagnostics for input or return parameters for lambdas that have an unknown type. The default value for this setting is 'none'.
+ **reportUnknownLambdaType** [boolean or string, optional]: Generate or suppress diagnostics for input or return parameters for lambdas that have an unknown type. The default value for this setting is 'none'.
-**reportUnknownVariableType** [boolean or string, optional]: Generate or suppress diagnostics for variables that have an unknown type. The default value for this setting is 'none'.
+ **reportUnknownVariableType** [boolean or string, optional]: Generate or suppress diagnostics for variables that have an unknown type. The default value for this setting is 'none'.
-**reportUnknownMemberType** [boolean or string, optional]: Generate or suppress diagnostics for class or instance variables that have an unknown type. The default value for this setting is 'none'.
+ **reportUnknownMemberType** [boolean or string, optional]: Generate or suppress diagnostics for class or instance variables that have an unknown type. The default value for this setting is 'none'.
-**reportMissingParameterType** [boolean or string, optional]: Generate or suppress diagnostics for input parameters for functions or methods that are missing a type annotation. The 'self' and 'cls' parameters used within methods are exempt from this check. The default value for this setting is 'none'.
+ **reportMissingParameterType** [boolean or string, optional]: Generate or suppress diagnostics for input parameters for functions or methods that are missing a type annotation. The 'self' and 'cls' parameters used within methods are exempt from this check. The default value for this setting is 'none'.
-**reportMissingTypeArgument** [boolean or string, optional]: Generate or suppress diagnostics when a generic class is used without providing explicit or implicit type arguments. The default value for this setting is 'none'.
+ **reportMissingTypeArgument** [boolean or string, optional]: Generate or suppress diagnostics when a generic class is used without providing explicit or implicit type arguments. The default value for this setting is 'none'.
-**reportInvalidTypeVarUse** [boolean or string, optional]: Generate or suppress diagnostics when a TypeVar is used inappropriately (e.g. if a TypeVar appears only once) within a generic function signature. The default value for this setting is 'warning'.
+ **reportInvalidTypeVarUse** [boolean or string, optional]: Generate or suppress diagnostics when a TypeVar is used inappropriately (e.g. if a TypeVar appears only once) within a generic function signature. The default value for this setting is 'warning'.
-**reportCallInDefaultInitializer** [boolean or string, optional]: Generate or suppress diagnostics for function calls, list expressions, set expressions, or dictionary expressions within a default value initialization expression. Such calls can mask expensive operations that are performed at module initialization time. The default value for this setting is 'none'.
+ **reportCallInDefaultInitializer** [boolean or string, optional]: Generate or suppress diagnostics for function calls, list expressions, set expressions, or dictionary expressions within a default value initialization expression. Such calls can mask expensive operations that are performed at module initialization time. The default value for this setting is 'none'.
-**reportUnnecessaryIsInstance** [boolean or string, optional]: Generate or suppress diagnostics for 'isinstance' or 'issubclass' calls where the result is statically determined to be always true. Such calls are often indicative of a programming error. The default value for this setting is 'none'.
+ **reportUnnecessaryIsInstance** [boolean or string, optional]: Generate or suppress diagnostics for 'isinstance' or 'issubclass' calls where the result is statically determined to be always true. Such calls are often indicative of a programming error. The default value for this setting is 'none'.
-**reportUnnecessaryCast** [boolean or string, optional]: Generate or suppress diagnostics for 'cast' calls that are statically determined to be unnecessary. Such calls are sometimes indicative of a programming error. The default value for this setting is 'none'.
+ **reportUnnecessaryCast** [boolean or string, optional]: Generate or suppress diagnostics for 'cast' calls that are statically determined to be unnecessary. Such calls are sometimes indicative of a programming error. The default value for this setting is 'none'.
-**reportUnnecessaryComparison** [boolean or string, optional]: Generate or suppress diagnostics for '==' or '!=' comparisons or other conditional expressions that are statically determined to always evaluate to False or True. Such comparisons are sometimes indicative of a programming error. The default value for this setting is 'none'.
+ **reportUnnecessaryComparison** [boolean or string, optional]: Generate or suppress diagnostics for '==' or '!=' comparisons or other conditional expressions that are statically determined to always evaluate to False or True. Such comparisons are sometimes indicative of a programming error. The default value for this setting is 'none'.
-**reportUnnecessaryContains** [boolean or string, optional]: Generate or suppress diagnostics for 'in' operations that are statically determined to always evaluate to False or True. Such operations are sometimes indicative of a programming error. The default value for this setting is 'none'.
+ **reportUnnecessaryContains** [boolean or string, optional]: Generate or suppress diagnostics for 'in' operations that are statically determined to always evaluate to False or True. Such operations are sometimes indicative of a programming error. The default value for this setting is 'none'.
-**reportAssertAlwaysTrue** [boolean or string, optional]: Generate or suppress diagnostics for 'assert' statement that will provably always assert. This can be indicative of a programming error. The default value for this setting is 'warning'.
+ **reportAssertAlwaysTrue** [boolean or string, optional]: Generate or suppress diagnostics for 'assert' statement that will provably always assert. This can be indicative of a programming error. The default value for this setting is 'warning'.
-**reportSelfClsParameterName** [boolean or string, optional]: Generate or suppress diagnostics for a missing or misnamed “self” parameter in instance methods and “cls” parameter in class methods. Instance methods in metaclasses (classes that derive from “type”) are allowed to use “cls” for instance methods. The default value for this setting is 'warning'.
+ **reportSelfClsParameterName** [boolean or string, optional]: Generate or suppress diagnostics for a missing or misnamed “self” parameter in instance methods and “cls” parameter in class methods. Instance methods in metaclasses (classes that derive from “type”) are allowed to use “cls” for instance methods. The default value for this setting is 'warning'.
-**reportImplicitStringConcatenation** [boolean or string, optional]: Generate or suppress diagnostics for two or more string literals that follow each other, indicating an implicit concatenation. This is considered a bad practice and often masks bugs such as missing commas. The default value for this setting is 'none'.
+ **reportImplicitStringConcatenation** [boolean or string, optional]: Generate or suppress diagnostics for two or more string literals that follow each other, indicating an implicit concatenation. This is considered a bad practice and often masks bugs such as missing commas. The default value for this setting is 'none'.
-**reportUndefinedVariable** [boolean or string, optional]: Generate or suppress diagnostics for undefined variables. The default value for this setting is 'error'.
+ **reportUndefinedVariable** [boolean or string, optional]: Generate or suppress diagnostics for undefined variables. The default value for this setting is 'error'.
-**reportUnboundVariable** [boolean or string, optional]: Generate or suppress diagnostics for unbound and possibly unbound variables. The default value for this setting is 'error'.
+ **reportUnboundVariable** [boolean or string, optional]: Generate or suppress diagnostics for unbound and possibly unbound variables. The default value for this setting is 'error'.
-**reportInvalidStubStatement** [boolean or string, optional]: Generate or suppress diagnostics for statements that are syntactically correct but have no purpose within a type stub file. The default value for this setting is 'none'.
+ **reportInvalidStubStatement** [boolean or string, optional]: Generate or suppress diagnostics for statements that are syntactically correct but have no purpose within a type stub file. The default value for this setting is 'none'.
-**reportIncompleteStub** [boolean or string, optional]: Generate or suppress diagnostics for a module-level `__getattr__` call in a type stub file, indicating that it is incomplete. The default value for this setting is 'none'.
+ **reportIncompleteStub** [boolean or string, optional]: Generate or suppress diagnostics for a module-level `__getattr__` call in a type stub file, indicating that it is incomplete. The default value for this setting is 'none'.
-**reportUnsupportedDunderAll** [boolean or string, optional]: Generate or suppress diagnostics for statements that define or manipulate `__all__` in a way that is not allowed by a static type checker, thus rendering the contents of `__all__` to be unknown or incorrect. Also reports names within the `__all__` list that are not present in the module namespace. The default value for this setting is 'warning'.
+ **reportUnsupportedDunderAll** [boolean or string, optional]: Generate or suppress diagnostics for statements that define or manipulate `__all__` in a way that is not allowed by a static type checker, thus rendering the contents of `__all__` to be unknown or incorrect. Also reports names within the `__all__` list that are not present in the module namespace. The default value for this setting is 'warning'.
-**reportUnusedCallResult** [boolean or string, optional]: Generate or suppress diagnostics for call statements whose return value is not used in any way and is not None. The default value for this setting is 'none'.
+ **reportUnusedCallResult** [boolean or string, optional]: Generate or suppress diagnostics for call statements whose return value is not used in any way and is not None. The default value for this setting is 'none'.
-**reportUnusedCoroutine** [boolean or string, optional]: Generate or suppress diagnostics for call statements whose return value is not used in any way and is a Coroutine. This identifies a common error where an `await` keyword is mistakenly omitted. The default value for this setting is 'error'.
+ **reportUnusedCoroutine** [boolean or string, optional]: Generate or suppress diagnostics for call statements whose return value is not used in any way and is a Coroutine. This identifies a common error where an `await` keyword is mistakenly omitted. The default value for this setting is 'error'.
-**reportUnusedExpression** [boolean or string, optional]: Generate or suppress diagnostics for simple expressions whose results are not used in any way. The default value for this setting is 'none'.
+ **reportUnusedExpression** [boolean or string, optional]: Generate or suppress diagnostics for simple expressions whose results are not used in any way. The default value for this setting is 'none'.
-**reportUnnecessaryTypeIgnoreComment** [boolean or string, optional]: Generate or suppress diagnostics for a '# type: ignore' or '# pyright: ignore' comment that would have no effect if removed. The default value for this setting is 'none'.
+ **reportUnnecessaryTypeIgnoreComment** [boolean or string, optional]: Generate or suppress diagnostics for a '# type: ignore' or '# pyright: ignore' comment that would have no effect if removed. The default value for this setting is 'none'.
-**reportMatchNotExhaustive** [boolean or string, optional]: Generate or suppress diagnostics for a 'match' statement that does not provide cases that exhaustively match against all potential types of the target expression. The default value for this setting is 'none'.
+ **reportMatchNotExhaustive** [boolean or string, optional]: Generate or suppress diagnostics for a 'match' statement that does not provide cases that exhaustively match against all potential types of the target expression. The default value for this setting is 'none'.
**reportShadowedImports** [boolean or string, optional]: Generate or suppress diagnostics for files that are overriding a module in the stdlib. The default value for this setting is 'none'.
diff --git a/packages/pyright-internal/src/analyzer/backgroundAnalysisProgram.ts b/packages/pyright-internal/src/analyzer/backgroundAnalysisProgram.ts
index 97a738f4b..9f1d7ad65 100644
--- a/packages/pyright-internal/src/analyzer/backgroundAnalysisProgram.ts
+++ b/packages/pyright-internal/src/analyzer/backgroundAnalysisProgram.ts
@@ -67,6 +67,10 @@ export class BackgroundAnalysisProgram {
return this._backgroundAnalysis;
}
+ contains(filePath: string): boolean {
+ return !!this._program.getSourceFile(filePath);
+ }
+
setConfigOptions(configOptions: ConfigOptions) {
this._configOptions = configOptions;
this._backgroundAnalysis?.setConfigOptions(configOptions);
@@ -117,9 +121,9 @@ export class BackgroundAnalysisProgram {
this.markFilesDirty([path], /* evenIfContentsAreSame */ true);
}
- setFileClosed(filePath: string) {
- this._backgroundAnalysis?.setFileClosed(filePath);
- const diagnostics = this._program.setFileClosed(filePath);
+ setFileClosed(filePath: string, isTracked?: boolean) {
+ this._backgroundAnalysis?.setFileClosed(filePath, isTracked);
+ const diagnostics = this._program.setFileClosed(filePath, isTracked);
this._reportDiagnosticsForRemovedFiles(diagnostics);
}
diff --git a/packages/pyright-internal/src/analyzer/program.ts b/packages/pyright-internal/src/analyzer/program.ts
index 9215aac4b..0f3cd2e8c 100644
--- a/packages/pyright-internal/src/analyzer/program.ts
+++ b/packages/pyright-internal/src/analyzer/program.ts
@@ -272,7 +272,7 @@ export class Program {
}
addTrackedFile(filePath: string, isThirdPartyImport = false, isInPyTypedPackage = false): SourceFile {
- let sourceFileInfo = this._getSourceFileInfoFromPath(filePath);
+ let sourceFileInfo = this.getSourceFileInfo(filePath);
const importName = this._getImportNameForFile(filePath);
if (sourceFileInfo) {
@@ -315,7 +315,7 @@ export class Program {
contents: TextDocumentContentChangeEvent[],
options?: OpenFileOptions
) {
- let sourceFileInfo = this._getSourceFileInfoFromPath(filePath);
+ let sourceFileInfo = this.getSourceFileInfo(filePath);
if (!sourceFileInfo) {
const importName = this._getImportNameForFile(filePath);
const sourceFile = new SourceFile(
@@ -334,7 +334,7 @@ export class Program {
sourceFileInfo = {
sourceFile,
isTracked: options?.isTracked ?? false,
- chainedSourceFile: chainedFilePath ? this._getSourceFileInfoFromPath(chainedFilePath) : undefined,
+ chainedSourceFile: chainedFilePath ? this.getSourceFileInfo(chainedFilePath) : undefined,
isOpenByClient: true,
isTypeshedFile: false,
isThirdPartyImport: false,
@@ -360,26 +360,25 @@ export class Program {
}
getChainedFilePath(filePath: string): string | undefined {
- const sourceFileInfo = this._getSourceFileInfoFromPath(filePath);
+ const sourceFileInfo = this.getSourceFileInfo(filePath);
return sourceFileInfo?.chainedSourceFile?.sourceFile.getFilePath();
}
updateChainedFilePath(filePath: string, chainedFilePath: string | undefined) {
- const sourceFileInfo = this._getSourceFileInfoFromPath(filePath);
+ const sourceFileInfo = this.getSourceFileInfo(filePath);
if (sourceFileInfo) {
- sourceFileInfo.chainedSourceFile = chainedFilePath
- ? this._getSourceFileInfoFromPath(chainedFilePath)
- : undefined;
+ sourceFileInfo.chainedSourceFile = chainedFilePath ? this.getSourceFileInfo(chainedFilePath) : undefined;
sourceFileInfo.sourceFile.markDirty();
this._markFileDirtyRecursive(sourceFileInfo, new Set());
}
}
- setFileClosed(filePath: string): FileDiagnostics[] {
- const sourceFileInfo = this._getSourceFileInfoFromPath(filePath);
+ setFileClosed(filePath: string, isTracked?: boolean): FileDiagnostics[] {
+ const sourceFileInfo = this.getSourceFileInfo(filePath);
if (sourceFileInfo) {
sourceFileInfo.isOpenByClient = false;
+ sourceFileInfo.isTracked = isTracked ?? sourceFileInfo.isTracked;
sourceFileInfo.sourceFile.setClientVersion(null, []);
// There is no guarantee that content is saved before the file is closed.
@@ -395,10 +394,6 @@ export class Program {
return this._removeUnneededFiles();
}
- isFileOpen(filePath: string) {
- return this._getSourceFileInfoFromPath(filePath) !== undefined;
- }
-
markAllFilesDirty(evenIfContentsAreSame: boolean, indexingNeeded = true) {
const markDirtySet = new Set();
@@ -422,7 +417,7 @@ export class Program {
markFilesDirty(filePaths: string[], evenIfContentsAreSame: boolean, indexingNeeded = true) {
const markDirtySet = new Set();
filePaths.forEach((filePath) => {
- const sourceFileInfo = this._getSourceFileInfoFromPath(filePath);
+ const sourceFileInfo = this.getSourceFileInfo(filePath);
if (sourceFileInfo) {
const fileName = getFileName(filePath);
@@ -500,7 +495,7 @@ export class Program {
}
getSourceFile(filePath: string): SourceFile | undefined {
- const sourceFileInfo = this._getSourceFileInfoFromPath(filePath);
+ const sourceFileInfo = this.getSourceFileInfo(filePath);
if (!sourceFileInfo) {
return undefined;
}
@@ -512,8 +507,12 @@ export class Program {
return this.getBoundSourceFileInfo(filePath)?.sourceFile;
}
+ getSourceFileInfo(filePath: string): SourceFileInfo | undefined {
+ return this._sourceFileMap.get(normalizePathCase(this._fs, filePath));
+ }
+
getBoundSourceFileInfo(filePath: string): SourceFileInfo | undefined {
- const sourceFileInfo = this._getSourceFileInfoFromPath(filePath);
+ const sourceFileInfo = this.getSourceFileInfo(filePath);
if (!sourceFileInfo) {
return undefined;
}
@@ -821,7 +820,7 @@ export class Program {
// We need to track the relationship so if the original type stub is removed from the
// program, we can remove the corresponding shadowed file and any files it imports.
private _addShadowedFile(stubFile: SourceFileInfo, shadowImplPath: string): SourceFile {
- let shadowFileInfo = this._getSourceFileInfoFromPath(shadowImplPath);
+ let shadowFileInfo = this.getSourceFileInfo(shadowImplPath);
if (!shadowFileInfo) {
const importName = this._getImportNameForFile(shadowImplPath);
@@ -978,7 +977,7 @@ export class Program {
let sourceFileInfo: SourceFileInfo | undefined;
if (typeof filePathOrModule === 'string') {
- sourceFileInfo = this._getSourceFileInfoFromPath(filePathOrModule);
+ sourceFileInfo = this.getSourceFileInfo(filePathOrModule);
} else {
// Resolve the import.
const importResult = this._importResolver.resolveImport(
@@ -995,14 +994,14 @@ export class Program {
let resolvedPath = importResult.resolvedPaths[importResult.resolvedPaths.length - 1];
if (resolvedPath) {
// See if the source file already exists in the program.
- sourceFileInfo = this._getSourceFileInfoFromPath(resolvedPath);
+ sourceFileInfo = this.getSourceFileInfo(resolvedPath);
if (!sourceFileInfo) {
resolvedPath = normalizePathCase(this._fs, resolvedPath);
// Start tracking the source file.
this.addTrackedFile(resolvedPath);
- sourceFileInfo = this._getSourceFileInfoFromPath(resolvedPath);
+ sourceFileInfo = this.getSourceFileInfo(resolvedPath);
}
}
}
@@ -1107,7 +1106,7 @@ export class Program {
this._evaluator!,
execEnv,
this._createSourceMapper(execEnv, token, fileToCheck),
- (p) => isUserCode(this._getSourceFileInfoFromPath(p))
+ (p) => isUserCode(this.getSourceFileInfo(p))
);
}
@@ -1251,7 +1250,7 @@ export class Program {
circDep.normalizeOrder();
const firstFilePath = circDep.getPaths()[0];
- const firstSourceFile = this._getSourceFileInfoFromPath(firstFilePath)!;
+ const firstSourceFile = this.getSourceFileInfo(firstFilePath)!;
assert(firstSourceFile !== undefined);
firstSourceFile.sourceFile.addCircularDependency(circDep);
}
@@ -1274,7 +1273,7 @@ export class Program {
}
getTextOnRange(filePath: string, range: Range, token: CancellationToken): string | undefined {
- const sourceFileInfo = this._getSourceFileInfoFromPath(filePath);
+ const sourceFileInfo = this.getSourceFileInfo(filePath);
if (!sourceFileInfo) {
return undefined;
}
@@ -1307,7 +1306,7 @@ export class Program {
options: AutoImportOptions,
token: CancellationToken
): AutoImportResult[] {
- const sourceFileInfo = this._getSourceFileInfoFromPath(filePath);
+ const sourceFileInfo = this.getSourceFileInfo(filePath);
if (!sourceFileInfo) {
return [];
}
@@ -1438,7 +1437,7 @@ export class Program {
token: CancellationToken
): DocumentRange[] | undefined {
return this._runEvaluatorWithCancellationToken(token, () => {
- const sourceFileInfo = this._getSourceFileInfoFromPath(filePath);
+ const sourceFileInfo = this.getSourceFileInfo(filePath);
if (!sourceFileInfo) {
return undefined;
}
@@ -1462,7 +1461,7 @@ export class Program {
token: CancellationToken
): DocumentRange[] | undefined {
return this._runEvaluatorWithCancellationToken(token, () => {
- const sourceFileInfo = this._getSourceFileInfoFromPath(filePath);
+ const sourceFileInfo = this.getSourceFileInfo(filePath);
if (!sourceFileInfo) {
return undefined;
}
@@ -1494,7 +1493,7 @@ export class Program {
token: CancellationToken
) {
this._runEvaluatorWithCancellationToken(token, () => {
- const sourceFileInfo = this._getSourceFileInfoFromPath(filePath);
+ const sourceFileInfo = this.getSourceFileInfo(filePath);
if (!sourceFileInfo) {
return;
}
@@ -1554,7 +1553,7 @@ export class Program {
continue;
}
- const declFileInfo = this._getSourceFileInfoFromPath(decl.path);
+ const declFileInfo = this.getSourceFileInfo(decl.path);
if (!declFileInfo) {
// The file the declaration belongs to doesn't belong to the program.
continue;
@@ -1595,7 +1594,7 @@ export class Program {
this._handleMemoryHighUsage();
return this._runEvaluatorWithCancellationToken(token, () => {
- const sourceFileInfo = this._getSourceFileInfoFromPath(filePath);
+ const sourceFileInfo = this.getSourceFileInfo(filePath);
if (!sourceFileInfo) {
return undefined;
}
@@ -1621,7 +1620,7 @@ export class Program {
addSymbolsForDocument(filePath: string, symbolList: DocumentSymbol[], token: CancellationToken) {
return this._runEvaluatorWithCancellationToken(token, () => {
- const sourceFileInfo = this._getSourceFileInfoFromPath(filePath);
+ const sourceFileInfo = this.getSourceFileInfo(filePath);
if (sourceFileInfo) {
if (!sourceFileInfo.sourceFile.getCachedIndexResults()) {
// If we already have cached index for this file, no need to bind this file.
@@ -1671,7 +1670,7 @@ export class Program {
token: CancellationToken
): HoverResults | undefined {
return this._runEvaluatorWithCancellationToken(token, () => {
- const sourceFileInfo = this._getSourceFileInfoFromPath(filePath);
+ const sourceFileInfo = this.getSourceFileInfo(filePath);
if (!sourceFileInfo) {
return undefined;
}
@@ -1695,7 +1694,7 @@ export class Program {
token: CancellationToken
): DocumentHighlight[] | undefined {
return this._runEvaluatorWithCancellationToken(token, () => {
- const sourceFileInfo = this._getSourceFileInfoFromPath(filePath);
+ const sourceFileInfo = this.getSourceFileInfo(filePath);
if (!sourceFileInfo) {
return undefined;
}
@@ -1719,7 +1718,7 @@ export class Program {
token: CancellationToken
): SignatureHelpResults | undefined {
return this._runEvaluatorWithCancellationToken(token, () => {
- const sourceFileInfo = this._getSourceFileInfoFromPath(filePath);
+ const sourceFileInfo = this.getSourceFileInfo(filePath);
if (!sourceFileInfo) {
return undefined;
}
@@ -1746,7 +1745,7 @@ export class Program {
libraryMap: Map | undefined,
token: CancellationToken
): Promise {
- const sourceFileInfo = this._getSourceFileInfoFromPath(filePath);
+ const sourceFileInfo = this.getSourceFileInfo(filePath);
if (!sourceFileInfo) {
return undefined;
}
@@ -1821,7 +1820,7 @@ export class Program {
token: CancellationToken
) {
return this._runEvaluatorWithCancellationToken(token, () => {
- const sourceFileInfo = this._getSourceFileInfoFromPath(filePath);
+ const sourceFileInfo = this.getSourceFileInfo(filePath);
if (!sourceFileInfo) {
return;
}
@@ -1854,7 +1853,7 @@ export class Program {
renameModule(path: string, newPath: string, token: CancellationToken): FileEditActions | undefined {
return this._runEvaluatorWithCancellationToken(token, () => {
if (isFile(this._fs, path)) {
- const fileInfo = this._getSourceFileInfoFromPath(path);
+ const fileInfo = this.getSourceFileInfo(path);
if (!fileInfo) {
return undefined;
}
@@ -1884,7 +1883,7 @@ export class Program {
token: CancellationToken
): FileEditActions | undefined {
return this._runEvaluatorWithCancellationToken(token, () => {
- const fileInfo = this._getSourceFileInfoFromPath(filePath);
+ const fileInfo = this.getSourceFileInfo(filePath);
if (!fileInfo) {
return undefined;
}
@@ -1945,7 +1944,7 @@ export class Program {
token: CancellationToken
): { range: Range; declarations: Declaration[] } | undefined {
return this._runEvaluatorWithCancellationToken(token, () => {
- const sourceFileInfo = this._getSourceFileInfoFromPath(filePath);
+ const sourceFileInfo = this.getSourceFileInfo(filePath);
if (!sourceFileInfo) {
return undefined;
}
@@ -1992,7 +1991,7 @@ export class Program {
token: CancellationToken
): FileEditActions | undefined {
return this._runEvaluatorWithCancellationToken(token, () => {
- const sourceFileInfo = this._getSourceFileInfoFromPath(filePath);
+ const sourceFileInfo = this.getSourceFileInfo(filePath);
if (!sourceFileInfo) {
return undefined;
}
@@ -2107,7 +2106,7 @@ export class Program {
}
getCallForPosition(filePath: string, position: Position, token: CancellationToken): CallHierarchyItem | undefined {
- const sourceFileInfo = this._getSourceFileInfoFromPath(filePath);
+ const sourceFileInfo = this.getSourceFileInfo(filePath);
if (!sourceFileInfo) {
return undefined;
}
@@ -2144,7 +2143,7 @@ export class Program {
position: Position,
token: CancellationToken
): CallHierarchyIncomingCall[] | undefined {
- const sourceFileInfo = this._getSourceFileInfoFromPath(filePath);
+ const sourceFileInfo = this.getSourceFileInfo(filePath);
if (!sourceFileInfo) {
return undefined;
}
@@ -2200,7 +2199,7 @@ export class Program {
position: Position,
token: CancellationToken
): CallHierarchyOutgoingCall[] | undefined {
- const sourceFileInfo = this._getSourceFileInfoFromPath(filePath);
+ const sourceFileInfo = this.getSourceFileInfo(filePath);
if (!sourceFileInfo) {
return undefined;
}
@@ -2237,7 +2236,7 @@ export class Program {
args: any[],
token: CancellationToken
): TextEditAction[] | undefined {
- const sourceFileInfo = this._getSourceFileInfoFromPath(filePath);
+ const sourceFileInfo = this.getSourceFileInfo(filePath);
if (!sourceFileInfo) {
return undefined;
}
@@ -2289,12 +2288,12 @@ export class Program {
(userFile && !referencesResult.requiresGlobalSearch) ||
(!userFile &&
sourceFileInfo.isOpenByClient &&
- referencesResult.declarations.every((d) => this._getSourceFileInfoFromPath(d.path) === sourceFileInfo))
+ referencesResult.declarations.every((d) => this.getSourceFileInfo(d.path) === sourceFileInfo))
) {
return 'singleFileMode';
}
- if (referencesResult.declarations.every((d) => isUserCode(this._getSourceFileInfoFromPath(d.path)))) {
+ if (referencesResult.declarations.every((d) => isUserCode(this.getSourceFileInfo(d.path)))) {
return 'multiFileMode';
}
@@ -2305,7 +2304,7 @@ export class Program {
private _supportRenameModule(declarations: Declaration[], isDefaultWorkspace: boolean) {
// Rename module is not supported for standalone file and all decls must be on a user file.
- return !isDefaultWorkspace && declarations.every((d) => isUserCode(this._getSourceFileInfoFromPath(d.path)));
+ return !isDefaultWorkspace && declarations.every((d) => isUserCode(this.getSourceFileInfo(d.path)));
}
private _getReferenceResult(
@@ -2567,7 +2566,7 @@ export class Program {
execEnv,
this._evaluator!,
(stubFilePath: string, implFilePath: string) => {
- const stubFileInfo = this._getSourceFileInfoFromPath(stubFilePath);
+ const stubFileInfo = this.getSourceFileInfo(stubFilePath);
if (!stubFileInfo) {
return undefined;
}
@@ -2575,7 +2574,7 @@ export class Program {
return this.getBoundSourceFile(implFilePath);
},
(f) => this.getBoundSourceFileInfo(f),
- (f) => this._getSourceFileInfoFromPath(f),
+ (f) => this.getSourceFileInfo(f),
mapCompiled ?? false,
preferStubs ?? false,
from,
@@ -2778,8 +2777,8 @@ export class Program {
// We found a new import to add. See if it's already part
// of the program.
let importedFileInfo: SourceFileInfo;
- if (this._getSourceFileInfoFromPath(importInfo.path)) {
- importedFileInfo = this._getSourceFileInfoFromPath(importInfo.path)!;
+ if (this.getSourceFileInfo(importInfo.path)) {
+ importedFileInfo = this.getSourceFileInfo(importInfo.path)!;
} else {
const importName = this._getImportNameForFile(importInfo.path);
const sourceFile = new SourceFile(
@@ -2818,8 +2817,8 @@ export class Program {
// specified by the source file.
sourceFileInfo.imports = [];
newImportPathMap.forEach((_, path) => {
- if (this._getSourceFileInfoFromPath(path)) {
- sourceFileInfo.imports.push(this._getSourceFileInfoFromPath(path)!);
+ if (this.getSourceFileInfo(path)) {
+ sourceFileInfo.imports.push(this.getSourceFileInfo(path)!);
}
});
@@ -2829,7 +2828,7 @@ export class Program {
const builtinsImport = sourceFileInfo.sourceFile.getBuiltinsImport();
if (builtinsImport && builtinsImport.isImportFound) {
const resolvedBuiltinsPath = builtinsImport.resolvedPaths[builtinsImport.resolvedPaths.length - 1];
- sourceFileInfo.builtinsImport = this._getSourceFileInfoFromPath(resolvedBuiltinsPath);
+ sourceFileInfo.builtinsImport = this.getSourceFileInfo(resolvedBuiltinsPath);
}
// Resolve the ipython display import for the file. This needs to be
@@ -2839,16 +2838,12 @@ export class Program {
if (ipythonDisplayImport && ipythonDisplayImport.isImportFound) {
const resolvedIPythonDisplayPath =
ipythonDisplayImport.resolvedPaths[ipythonDisplayImport.resolvedPaths.length - 1];
- sourceFileInfo.ipythonDisplayImport = this._getSourceFileInfoFromPath(resolvedIPythonDisplayPath);
+ sourceFileInfo.ipythonDisplayImport = this.getSourceFileInfo(resolvedIPythonDisplayPath);
}
return filesAdded;
}
- private _getSourceFileInfoFromPath(filePath: string): SourceFileInfo | undefined {
- return this._sourceFileMap.get(normalizePathCase(this._fs, filePath));
- }
-
private _removeSourceFileFromListAndMap(filePath: string, indexToRemove: number) {
this._sourceFileMap.delete(normalizePathCase(this._fs, filePath));
this._sourceFileList.splice(indexToRemove, 1);
diff --git a/packages/pyright-internal/src/analyzer/service.ts b/packages/pyright-internal/src/analyzer/service.ts
index b6db9ebe2..8c09ef3c9 100644
--- a/packages/pyright-internal/src/analyzer/service.ts
+++ b/packages/pyright-internal/src/analyzer/service.ts
@@ -194,7 +194,10 @@ export class AnalyzerService {
service.setFileOpened(
fileInfo.sourceFile.getFilePath(),
version,
- fileInfo.sourceFile.getOpenFileContents()!
+ fileInfo.sourceFile.getOpenFileContents()!,
+ fileInfo.sourceFile.getIPythonMode(),
+ fileInfo.chainedSourceFile?.sourceFile.getFilePath(),
+ fileInfo.sourceFile.getRealFilePath()
);
}
}
@@ -272,14 +275,19 @@ export class AnalyzerService {
this._applyConfigOptions(host);
}
+ contains(filePath: string): boolean {
+ return this.backgroundAnalysisProgram.contains(filePath);
+ }
+
isTracked(filePath: string): boolean {
- for (const includeSpec of this._configOptions.include) {
- if (this._matchIncludeFileSpec(includeSpec.regExp, this._configOptions.exclude, filePath)) {
- return true;
- }
+ const fileInfo = this._program.getSourceFileInfo(filePath);
+ if (fileInfo) {
+ // If we already determined whether the file is tracked or not, don't do it again.
+ // This will make sure we have consistent look at the state once it is loaded to the memory.
+ return fileInfo.isTracked;
}
- return false;
+ return this._matchFileSpecs(filePath);
}
setFileOpened(
@@ -313,13 +321,13 @@ export class AnalyzerService {
version: number | null,
contents: TextDocumentContentChangeEvent[],
ipythonMode = IPythonMode.None,
- chainedFilePath?: string
+ realFilePath?: string
) {
this._backgroundAnalysisProgram.updateOpenFileContents(path, version, contents, {
isTracked: this.isTracked(path),
ipythonMode,
- chainedFilePath,
- realFilePath: undefined,
+ chainedFilePath: undefined,
+ realFilePath,
});
this._scheduleReanalysis(/* requireTrackedFileUpdate */ false);
}
@@ -328,15 +336,11 @@ export class AnalyzerService {
this._backgroundAnalysisProgram.startIndexing(indexOptions);
}
- setFileClosed(path: string) {
- this._backgroundAnalysisProgram.setFileClosed(path);
+ setFileClosed(path: string, isTracked?: boolean) {
+ this._backgroundAnalysisProgram.setFileClosed(path, isTracked);
this._scheduleReanalysis(/* requireTrackedFileUpdate */ false);
}
- isFileOpen(path: string) {
- return this._program.isFileOpen(path);
- }
-
getParseResult(path: string) {
return this._program.getBoundSourceFile(path)?.getParseResults();
}
@@ -1095,6 +1099,7 @@ export class AnalyzerService {
// Use a map to generate a list of unique files.
const fileMap = new Map();
+ // Scan all matching files from file system.
timingStats.findFilesTime.timeOperation(() => {
const matchedFiles = this._matchFiles(this._configOptions.include, this._configOptions.exclude);
@@ -1103,6 +1108,14 @@ export class AnalyzerService {
}
});
+ // And scan all matching open files. We need to do this since some of files are not backed by
+ // files in file system but only exist in memory (ex, virtual workspace)
+ this._backgroundAnalysisProgram.program
+ .getOpened()
+ .map((o) => o.sourceFile.getFilePath())
+ .filter((f) => this._matchFileSpecs(f))
+ .forEach((f) => fileMap.set(f, f));
+
return [...fileMap.values()];
}
@@ -1732,4 +1745,14 @@ export class AnalyzerService {
return false;
}
+
+ private _matchFileSpecs(filePath: string) {
+ for (const includeSpec of this._configOptions.include) {
+ if (this._matchIncludeFileSpec(includeSpec.regExp, this._configOptions.exclude, filePath)) {
+ return true;
+ }
+ }
+
+ return false;
+ }
}
diff --git a/packages/pyright-internal/src/analyzer/sourceFile.ts b/packages/pyright-internal/src/analyzer/sourceFile.ts
index 8663b8d75..68cc6348e 100644
--- a/packages/pyright-internal/src/analyzer/sourceFile.ts
+++ b/packages/pyright-internal/src/analyzer/sourceFile.ts
@@ -249,6 +249,14 @@ export class SourceFile {
this._ipythonMode = ipythonMode;
}
+ getRealFilePath(): string {
+ return this._realFilePath;
+ }
+
+ getIPythonMode(): IPythonMode {
+ return this._ipythonMode;
+ }
+
getFilePath(): string {
return this._filePath;
}
diff --git a/packages/pyright-internal/src/backgroundAnalysisBase.ts b/packages/pyright-internal/src/backgroundAnalysisBase.ts
index 62985d274..12020cc60 100644
--- a/packages/pyright-internal/src/backgroundAnalysisBase.ts
+++ b/packages/pyright-internal/src/backgroundAnalysisBase.ts
@@ -123,8 +123,8 @@ export class BackgroundAnalysisBase {
});
}
- setFileClosed(filePath: string) {
- this.enqueueRequest({ requestType: 'setFileClosed', data: filePath });
+ setFileClosed(filePath: string, isTracked?: boolean) {
+ this.enqueueRequest({ requestType: 'setFileClosed', data: { filePath, isTracked } });
}
markAllFilesDirty(evenIfContentsAreSame: boolean, indexingNeeded: boolean) {
@@ -433,7 +433,8 @@ export abstract class BackgroundAnalysisRunnerBase extends BackgroundThreadBase
}
case 'setFileClosed': {
- const diagnostics = this.program.setFileClosed(msg.data);
+ const { filePath, isTracked } = msg.data;
+ const diagnostics = this.program.setFileClosed(filePath, isTracked);
this._reportDiagnostics(diagnostics, this.program.getFilesToAnalyzeCount(), 0);
break;
}
diff --git a/packages/pyright-internal/src/common/pathUtils.ts b/packages/pyright-internal/src/common/pathUtils.ts
index b7bb397f5..f001f02f2 100644
--- a/packages/pyright-internal/src/common/pathUtils.ts
+++ b/packages/pyright-internal/src/common/pathUtils.ts
@@ -904,7 +904,9 @@ export function convertUriToPath(fs: FileSystem, uriString: string): string {
export function extractPathFromUri(uriString: string) {
const uri = URI.parse(uriString);
- let convertedPath = normalizePath(uri.path);
+
+ // When schema is "file", we use fsPath so that we can handle things like UNC paths.
+ let convertedPath = normalizePath(uri.scheme === 'file' ? uri.fsPath : uri.path);
// If this is a DOS-style path with a drive letter, remove
// the leading slash.
diff --git a/packages/pyright-internal/src/languageServerBase.ts b/packages/pyright-internal/src/languageServerBase.ts
index 01aa58c4f..f1933a545 100644
--- a/packages/pyright-internal/src/languageServerBase.ts
+++ b/packages/pyright-internal/src/languageServerBase.ts
@@ -168,7 +168,6 @@ export interface WorkspaceServiceInstance {
disableWorkspaceSymbol: boolean;
isInitialized: Deferred;
searchPathsToWatch: string[];
- owns(filePath: string): boolean;
}
export interface MessageAction {
@@ -464,6 +463,15 @@ export abstract class LanguageServerBase implements LanguageServerInterface {
return service;
}
+ async test_getWorkspaces() {
+ const workspaces = [...this._workspaceMap.values()];
+ for (const workspace of workspaces) {
+ await workspace.isInitialized.promise;
+ }
+
+ return workspaces;
+ }
+
async getWorkspaceForFile(filePath: string): Promise {
const workspace = this._workspaceMap.getWorkspaceForFile(this, filePath);
await workspace.isInitialized.promise;
@@ -1084,12 +1092,6 @@ export abstract class LanguageServerBase implements LanguageServerInterface {
token
);
- // We only allow renaming symbol defined in the files this workspace owns.
- // This is to make sure we don't rename files across workspaces in multiple workspaces context.
- if (result && result.declarations.some((d) => d.path && !workspace.owns(d.path))) {
- return null;
- }
-
return result?.range ?? null;
}
@@ -1356,7 +1358,6 @@ export abstract class LanguageServerBase implements LanguageServerInterface {
rootPath: string,
path: string,
kinds: string[] = [WellKnownWorkspaceKinds.Regular],
- owns?: (filePath: string) => boolean,
services?: WorkspaceServices
): WorkspaceServiceInstance {
// 5 seconds default
@@ -1374,7 +1375,6 @@ export abstract class LanguageServerBase implements LanguageServerInterface {
: () => defaultBackOffTime;
const rootUri = workspaceFolder?.uri ?? '';
- owns = owns ?? ((f) => f.startsWith(rootPath));
return {
workspaceName: workspaceFolder?.name ?? '',
@@ -1392,7 +1392,6 @@ export abstract class LanguageServerBase implements LanguageServerInterface {
disableWorkspaceSymbol: false,
isInitialized: createDeferred(),
searchPathsToWatch: [],
- owns,
};
}
@@ -1619,9 +1618,8 @@ export abstract class LanguageServerBase implements LanguageServerInterface {
}
protected getDocumentationUrlForDiagnosticRule(rule: string): string | undefined {
- // For now, return the same URL for all rules. We can separate these
- // in the future.
- return 'https://github.com/microsoft/pyright/blob/main/docs/configuration.md';
+ // 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;
diff --git a/packages/pyright-internal/src/languageService/analyzerServiceExecutor.ts b/packages/pyright-internal/src/languageService/analyzerServiceExecutor.ts
index ab77b3986..f90d4f027 100644
--- a/packages/pyright-internal/src/languageService/analyzerServiceExecutor.ts
+++ b/packages/pyright-internal/src/languageService/analyzerServiceExecutor.ts
@@ -68,7 +68,6 @@ export class AnalyzerServiceExecutor {
disableWorkspaceSymbol: true,
isInitialized: createDeferred(),
searchPathsToWatch: [],
- owns: workspace.owns,
};
const serverSettings = await ls.getSettings(workspace);
diff --git a/packages/pyright-internal/src/tests/config.test.ts b/packages/pyright-internal/src/tests/config.test.ts
index 623c89d40..e668519af 100644
--- a/packages/pyright-internal/src/tests/config.test.ts
+++ b/packages/pyright-internal/src/tests/config.test.ts
@@ -291,3 +291,17 @@ test('BasicPyprojectTomlParsing', () => {
assert.strictEqual(configOptions.diagnosticRuleSet.reportMissingImports, 'error');
assert.strictEqual(configOptions.diagnosticRuleSet.reportUnusedClass, 'warning');
});
+
+test('FindFilesInMemoryOnly', () => {
+ const cwd = normalizePath(process.cwd());
+ const service = new AnalyzerService('', createFromRealFileSystem(), options);
+ const commandLineOptions = new CommandLineOptions(cwd, /* fromVsCodeExtension */ true);
+ service.setOptions(commandLineOptions);
+
+ // Open a file that is not backed by the file system.
+ const untitled = combinePaths(cwd, 'untitled.py');
+ service.setFileOpened(untitled, 1, '# empty');
+
+ const fileList = service.test_getFileNamesFromFileSpecs();
+ assert(fileList.filter((f) => f === untitled));
+});
diff --git a/packages/pyright-internal/src/tests/harness/fourslash/testLanguageService.ts b/packages/pyright-internal/src/tests/harness/fourslash/testLanguageService.ts
index 361467a61..af7e833e3 100644
--- a/packages/pyright-internal/src/tests/harness/fourslash/testLanguageService.ts
+++ b/packages/pyright-internal/src/tests/harness/fourslash/testLanguageService.ts
@@ -105,7 +105,6 @@ export class TestLanguageService implements LanguageServerInterface {
disableWorkspaceSymbol: false,
isInitialized: createDeferred(),
searchPathsToWatch: [],
- owns: (f) => true,
};
}
decodeTextDocumentUri(uriString: string): string {
diff --git a/packages/pyright-internal/src/tests/harness/fourslash/testState.ts b/packages/pyright-internal/src/tests/harness/fourslash/testState.ts
index 5b956b0ab..edbf61fde 100644
--- a/packages/pyright-internal/src/tests/harness/fourslash/testState.ts
+++ b/packages/pyright-internal/src/tests/harness/fourslash/testState.ts
@@ -11,23 +11,16 @@ import * as JSONC from 'jsonc-parser';
import * as path from 'path';
import Char from 'typescript-char';
import {
- AnnotatedTextEdit,
CancellationToken,
- ChangeAnnotation,
CodeAction,
Command,
CompletionItem,
- CreateFile,
- DeleteFile,
Diagnostic,
DocumentHighlight,
DocumentHighlightKind,
ExecuteCommandParams,
MarkupContent,
MarkupKind,
- OptionalVersionedTextDocumentIdentifier,
- RenameFile,
- TextDocumentEdit,
TextEdit,
WorkspaceEdit,
} from 'vscode-languageserver';
@@ -91,6 +84,7 @@ import {
TestCancellationToken,
} from './fourSlashTypes';
import { TestFeatures, TestLanguageService } from './testLanguageService';
+import { verifyWorkspaceEdit } from './workspaceEditTestUtils';
export interface TextChange {
span: TextRange;
@@ -183,7 +177,6 @@ export class TestState {
disableWorkspaceSymbol: false,
isInitialized: createDeferred(),
searchPathsToWatch: [],
- owns: (f) => true,
};
const indexer = toBoolean(testData.globalOptions[GlobalMetadataOptionNames.indexer]);
@@ -258,28 +251,11 @@ export class TestState {
}
getMarkerName(m: Marker): string {
- let found: string | undefined;
- this.testData.markerPositions.forEach((marker, name) => {
- if (marker === m) {
- found = name;
- }
- });
-
- assert.ok(found);
- return found!;
+ return getMarkerName(this.testData, m);
}
getMarkerByName(markerName: string) {
- const markerPos = this.testData.markerPositions.get(markerName);
- if (markerPos === undefined) {
- throw new Error(
- `Unknown marker "${markerName}" Available markers: ${this.getMarkerNames()
- .map((m) => '"' + m + '"')
- .join(', ')}`
- );
- } else {
- return markerPos;
- }
+ return getMarkerByName(this.testData, markerName);
}
getMarkers(): Marker[] {
@@ -288,7 +264,7 @@ export class TestState {
}
getMarkerNames(): string[] {
- return [...this.testData.markerPositions.keys()];
+ return getMarkerNames(this.testData);
}
getPositionRange(markerString: string) {
@@ -766,155 +742,7 @@ export class TestState {
}
verifyWorkspaceEdit(expected: WorkspaceEdit, actual: WorkspaceEdit) {
- if (actual.changes) {
- this._verifyTextEditMap(expected.changes!, actual.changes);
- } else {
- assert(!expected.changes);
- }
-
- if (actual.documentChanges) {
- this._verifyDocumentEdits(expected.documentChanges!, actual.documentChanges);
- } else {
- assert(!expected.documentChanges);
- }
-
- if (actual.changeAnnotations) {
- this._verifyChangeAnnotations(expected.changeAnnotations!, actual.changeAnnotations);
- } else {
- assert(!expected.changeAnnotations);
- }
- }
-
- private _verifyChangeAnnotations(
- expected: { [id: string]: ChangeAnnotation },
- actual: { [id: string]: ChangeAnnotation }
- ) {
- assert.strictEqual(Object.entries(expected).length, Object.entries(actual).length);
-
- for (const key of Object.keys(expected)) {
- const expectedAnnotation = expected[key];
- const actualAnnotation = actual[key];
-
- // We need to improve it to test localized strings.
- assert.strictEqual(expectedAnnotation.label, actualAnnotation.label);
- assert.strictEqual(expectedAnnotation.description, actualAnnotation.description);
-
- assert.strictEqual(expectedAnnotation.needsConfirmation, actualAnnotation.needsConfirmation);
- }
- }
-
- private _textDocumentAreSame(
- expected: OptionalVersionedTextDocumentIdentifier,
- actual: OptionalVersionedTextDocumentIdentifier
- ) {
- return expected.version === actual.version && expected.uri === actual.uri;
- }
-
- private _verifyDocumentEdits(
- expected: (TextDocumentEdit | CreateFile | RenameFile | DeleteFile)[],
- actual: (TextDocumentEdit | CreateFile | RenameFile | DeleteFile)[]
- ) {
- assert.strictEqual(expected.length, actual.length);
-
- for (const op of expected) {
- assert(
- actual.some((a) => {
- const expectedKind = TextDocumentEdit.is(op) ? 'edit' : op.kind;
- const actualKind = TextDocumentEdit.is(a) ? 'edit' : a.kind;
- if (expectedKind !== actualKind) {
- return false;
- }
-
- switch (expectedKind) {
- case 'edit': {
- const expectedEdit = op as TextDocumentEdit;
- const actualEdit = a as TextDocumentEdit;
-
- if (!this._textDocumentAreSame(expectedEdit.textDocument, actualEdit.textDocument)) {
- return false;
- }
-
- return this._textEditsAreSame(expectedEdit.edits, actualEdit.edits);
- }
- case 'create': {
- const expectedOp = op as CreateFile;
- const actualOp = a as CreateFile;
- return (
- expectedOp.kind === actualOp.kind &&
- expectedOp.annotationId === actualOp.annotationId &&
- expectedOp.uri === actualOp.uri &&
- expectedOp.options?.ignoreIfExists === actualOp.options?.ignoreIfExists &&
- expectedOp.options?.overwrite === actualOp.options?.overwrite
- );
- }
- case 'rename': {
- const expectedOp = op as RenameFile;
- const actualOp = a as RenameFile;
- return (
- expectedOp.kind === actualOp.kind &&
- expectedOp.annotationId === actualOp.annotationId &&
- expectedOp.oldUri === actualOp.oldUri &&
- expectedOp.newUri === actualOp.newUri &&
- expectedOp.options?.ignoreIfExists === actualOp.options?.ignoreIfExists &&
- expectedOp.options?.overwrite === actualOp.options?.overwrite
- );
- }
- case 'delete': {
- const expectedOp = op as DeleteFile;
- const actualOp = a as DeleteFile;
- return (
- expectedOp.annotationId === actualOp.annotationId &&
- expectedOp.kind === actualOp.kind &&
- expectedOp.uri === actualOp.uri &&
- expectedOp.options?.ignoreIfNotExists === actualOp.options?.ignoreIfNotExists &&
- expectedOp.options?.recursive === actualOp.options?.recursive
- );
- }
- default:
- debug.assertNever(expectedKind);
- }
- })
- );
- }
- }
-
- private _verifyTextEditMap(expected: { [uri: string]: TextEdit[] }, actual: { [uri: string]: TextEdit[] }) {
- assert.strictEqual(Object.entries(expected).length, Object.entries(actual).length);
-
- for (const key of Object.keys(expected)) {
- assert(this._textEditsAreSame(expected[key], actual[key]));
- }
- }
-
- private _textEditsAreSame(
- expectedEdits: (TextEdit | AnnotatedTextEdit)[],
- actualEdits: (TextEdit | AnnotatedTextEdit)[]
- ) {
- if (expectedEdits.length !== actualEdits.length) {
- return false;
- }
-
- for (const edit of expectedEdits) {
- if (!actualEdits.some((a) => this._textEditAreSame(edit, a))) {
- return false;
- }
- }
-
- return true;
- }
-
- private _textEditAreSame(expected: TextEdit, actual: TextEdit) {
- if (!rangesAreEqual(expected.range, actual.range)) {
- return false;
- }
-
- if (expected.newText !== actual.newText) {
- return false;
- }
-
- const expectedAnnotation = AnnotatedTextEdit.is(expected) ? expected.annotationId : '';
- const actualAnnotation = AnnotatedTextEdit.is(actual) ? actual.annotationId : '';
- return expectedAnnotation === actualAnnotation;
+ return verifyWorkspaceEdit(expected, actual);
}
async verifyInvokeCodeAction(
@@ -2038,6 +1866,35 @@ export function createVfsInfoFromFourSlashData(projectRoot: string, testData: Fo
return { files, sourceFileNames, projectRoot, ignoreCase, rawConfigJson };
}
+export function getMarkerName(testData: FourSlashData, markerToFind: Marker) {
+ let found: string | undefined;
+ testData.markerPositions.forEach((marker, name) => {
+ if (marker === markerToFind) {
+ found = name;
+ }
+ });
+
+ assert.ok(found);
+ return found!;
+}
+
+export function getMarkerByName(testData: FourSlashData, markerName: string) {
+ const markerPos = testData.markerPositions.get(markerName);
+ if (markerPos === undefined) {
+ throw new Error(
+ `Unknown marker "${markerName}" Available markers: ${getMarkerNames(testData)
+ .map((m) => '"' + m + '"')
+ .join(', ')}`
+ );
+ } else {
+ return markerPos;
+ }
+}
+
+export function getMarkerNames(testData: FourSlashData): string[] {
+ return [...testData.markerPositions.keys()];
+}
+
function isConfig(file: FourSlashFile, ignoreCase: boolean): boolean {
const comparer = getStringComparer(ignoreCase);
return configFileNames.some((f) => comparer(getBaseFileName(file.fileName), f) === Comparison.EqualTo);
diff --git a/packages/pyright-internal/src/tests/harness/fourslash/workspaceEditTestUtils.ts b/packages/pyright-internal/src/tests/harness/fourslash/workspaceEditTestUtils.ts
new file mode 100644
index 000000000..de3aa19b4
--- /dev/null
+++ b/packages/pyright-internal/src/tests/harness/fourslash/workspaceEditTestUtils.ts
@@ -0,0 +1,175 @@
+/*
+ * workspaceEditTestUtils.ts
+ * Copyright (c) Microsoft Corporation.
+ * Licensed under the MIT license.
+ *
+ * Test Utils around workspace edits.
+ */
+
+import assert from 'assert';
+import {
+ AnnotatedTextEdit,
+ ChangeAnnotation,
+ CreateFile,
+ DeleteFile,
+ OptionalVersionedTextDocumentIdentifier,
+ RenameFile,
+ TextDocumentEdit,
+ TextEdit,
+ WorkspaceEdit,
+} from 'vscode-languageserver';
+
+import * as debug from '../../../common/debug';
+import { rangesAreEqual } from '../../../common/textRange';
+
+export function verifyWorkspaceEdit(expected: WorkspaceEdit, actual: WorkspaceEdit) {
+ if (actual.changes) {
+ verifyTextEditMap(expected.changes!, actual.changes);
+ } else {
+ assert(!expected.changes);
+ }
+
+ if (actual.documentChanges) {
+ verifyDocumentEdits(expected.documentChanges!, actual.documentChanges);
+ } else {
+ assert(!expected.documentChanges);
+ }
+
+ if (actual.changeAnnotations) {
+ verifyChangeAnnotations(expected.changeAnnotations!, actual.changeAnnotations);
+ } else {
+ assert(!expected.changeAnnotations);
+ }
+}
+
+export function verifyChangeAnnotations(
+ expected: { [id: string]: ChangeAnnotation },
+ actual: { [id: string]: ChangeAnnotation }
+) {
+ assert.strictEqual(Object.entries(expected).length, Object.entries(actual).length);
+
+ for (const key of Object.keys(expected)) {
+ const expectedAnnotation = expected[key];
+ const actualAnnotation = actual[key];
+
+ // We need to improve it to test localized strings.
+ assert.strictEqual(expectedAnnotation.label, actualAnnotation.label);
+ assert.strictEqual(expectedAnnotation.description, actualAnnotation.description);
+
+ assert.strictEqual(expectedAnnotation.needsConfirmation, actualAnnotation.needsConfirmation);
+ }
+}
+
+export function textDocumentAreSame(
+ expected: OptionalVersionedTextDocumentIdentifier,
+ actual: OptionalVersionedTextDocumentIdentifier
+) {
+ return expected.version === actual.version && expected.uri === actual.uri;
+}
+
+export function verifyDocumentEdits(
+ expected: (TextDocumentEdit | CreateFile | RenameFile | DeleteFile)[],
+ actual: (TextDocumentEdit | CreateFile | RenameFile | DeleteFile)[]
+) {
+ assert.strictEqual(expected.length, actual.length);
+
+ for (const op of expected) {
+ assert(
+ actual.some((a) => {
+ const expectedKind = TextDocumentEdit.is(op) ? 'edit' : op.kind;
+ const actualKind = TextDocumentEdit.is(a) ? 'edit' : a.kind;
+ if (expectedKind !== actualKind) {
+ return false;
+ }
+
+ switch (expectedKind) {
+ case 'edit': {
+ const expectedEdit = op as TextDocumentEdit;
+ const actualEdit = a as TextDocumentEdit;
+
+ if (!textDocumentAreSame(expectedEdit.textDocument, actualEdit.textDocument)) {
+ return false;
+ }
+
+ return textEditsAreSame(expectedEdit.edits, actualEdit.edits);
+ }
+ case 'create': {
+ const expectedOp = op as CreateFile;
+ const actualOp = a as CreateFile;
+ return (
+ expectedOp.kind === actualOp.kind &&
+ expectedOp.annotationId === actualOp.annotationId &&
+ expectedOp.uri === actualOp.uri &&
+ expectedOp.options?.ignoreIfExists === actualOp.options?.ignoreIfExists &&
+ expectedOp.options?.overwrite === actualOp.options?.overwrite
+ );
+ }
+ case 'rename': {
+ const expectedOp = op as RenameFile;
+ const actualOp = a as RenameFile;
+ return (
+ expectedOp.kind === actualOp.kind &&
+ expectedOp.annotationId === actualOp.annotationId &&
+ expectedOp.oldUri === actualOp.oldUri &&
+ expectedOp.newUri === actualOp.newUri &&
+ expectedOp.options?.ignoreIfExists === actualOp.options?.ignoreIfExists &&
+ expectedOp.options?.overwrite === actualOp.options?.overwrite
+ );
+ }
+ case 'delete': {
+ const expectedOp = op as DeleteFile;
+ const actualOp = a as DeleteFile;
+ return (
+ expectedOp.annotationId === actualOp.annotationId &&
+ expectedOp.kind === actualOp.kind &&
+ expectedOp.uri === actualOp.uri &&
+ expectedOp.options?.ignoreIfNotExists === actualOp.options?.ignoreIfNotExists &&
+ expectedOp.options?.recursive === actualOp.options?.recursive
+ );
+ }
+ default:
+ debug.assertNever(expectedKind);
+ }
+ })
+ );
+ }
+}
+
+export function verifyTextEditMap(expected: { [uri: string]: TextEdit[] }, actual: { [uri: string]: TextEdit[] }) {
+ assert.strictEqual(Object.entries(expected).length, Object.entries(actual).length);
+
+ for (const key of Object.keys(expected)) {
+ assert(textEditsAreSame(expected[key], actual[key]));
+ }
+}
+
+export function textEditsAreSame(
+ expectedEdits: (TextEdit | AnnotatedTextEdit)[],
+ actualEdits: (TextEdit | AnnotatedTextEdit)[]
+) {
+ if (expectedEdits.length !== actualEdits.length) {
+ return false;
+ }
+
+ for (const edit of expectedEdits) {
+ if (!actualEdits.some((a) => textEditAreSame(edit, a))) {
+ return false;
+ }
+ }
+
+ return true;
+}
+
+export function textEditAreSame(expected: TextEdit, actual: TextEdit) {
+ if (!rangesAreEqual(expected.range, actual.range)) {
+ return false;
+ }
+
+ if (expected.newText !== actual.newText) {
+ return false;
+ }
+
+ const expectedAnnotation = AnnotatedTextEdit.is(expected) ? expected.annotationId : '';
+ const actualAnnotation = AnnotatedTextEdit.is(actual) ? actual.annotationId : '';
+ return expectedAnnotation === actualAnnotation;
+}
diff --git a/packages/pyright-internal/src/tests/pathUtils.test.ts b/packages/pyright-internal/src/tests/pathUtils.test.ts
index 3f3a24ae7..76e6714d8 100644
--- a/packages/pyright-internal/src/tests/pathUtils.test.ts
+++ b/packages/pyright-internal/src/tests/pathUtils.test.ts
@@ -19,6 +19,7 @@ import {
comparePathsCaseInsensitive,
comparePathsCaseSensitive,
containsPath,
+ convertUriToPath,
deduplicateFolders,
ensureTrailingDirectorySeparator,
getAnyExtensionFromPath,
@@ -341,3 +342,13 @@ test('deduplicateFolders', () => {
assert.deepStrictEqual(folders.sort(), expected.sort());
});
+
+test('convert UNC path', () => {
+ const cwd = normalizeSlashes('/');
+ const fs = new vfs.TestFileSystem(/*ignoreCase*/ true, { cwd });
+
+ const path = convertUriToPath(fs, 'file://server/c$/folder/file.py');
+
+ // When converting UNC path, server part shouldn't be removed.
+ assert(path.indexOf('server') > 0);
+});
diff --git a/packages/pyright-internal/src/workspaceMap.ts b/packages/pyright-internal/src/workspaceMap.ts
index 73d187caf..944ae55ad 100644
--- a/packages/pyright-internal/src/workspaceMap.ts
+++ b/packages/pyright-internal/src/workspaceMap.ts
@@ -62,17 +62,23 @@ export class WorkspaceMap extends Map {
let bestRootPath: string | undefined;
let bestInstance: WorkspaceServiceInstance | undefined;
+ // The order of how we find the best matching workspace for the given file is
+ // 1. The given file is the workspace itself (ex, a file being a virtual workspace itself).
+ // 2. The given file matches the fileSpec of the service under the workspace
+ // (the file is a user file the workspace provides LSP service for).
+ // 3. The given file doesn't match anything but we have only 1 regular workspace
+ // (ex, open a library file from the workspace).
+ // 4. The given file doesn't match anything and there are multiple workspaces but one of workspaces
+ // contains the file (ex, open a library file already imported by a workspace).
+ // 5. If none of the above works, then it matches the default workspace.
this.forEach((workspace) => {
if (workspace.path) {
- // Is the file is under this workspace folder?
- if (!workspace.owns(filePath)) {
+ if (workspace.path !== filePath && !workspace.serviceInstance.isTracked(filePath)) {
return;
}
- // Is this the fist candidate? If not, is this workspace folder
- // contained within the previous candidate folder? We always want
- // to select the innermost folder, since that overrides the
- // outer folders.
+ // Among workspaces that own the file, make sure we return the inner most one which
+ // we consider as the best workspace.
if (bestRootPath === undefined || workspace.path.startsWith(bestRootPath)) {
bestRootPath = workspace.path;
bestInstance = workspace;
@@ -83,14 +89,25 @@ export class WorkspaceMap extends Map {
// If there were multiple workspaces or we couldn't find any,
// create a default one to use for this file.
if (bestInstance === undefined) {
+ const regularWorkspaces = this.getNonDefaultWorkspaces(WellKnownWorkspaceKinds.Regular);
+
+ // If we have only 1 regular workspace, then use that.
+ if (regularWorkspaces.length === 1) {
+ return regularWorkspaces[0];
+ }
+
+ // If we have multiple workspaces, see whether we can at least find one that contains the file.
+ // the file might not be tracked (user file), but still belongs to a workspace as a library file or as an orphan file to the workspace.
+ const containingWorkspace = this._getBestWorkspace(
+ regularWorkspaces.filter((w) => w.serviceInstance.contains(filePath))
+ );
+ if (containingWorkspace) {
+ return containingWorkspace;
+ }
+
+ // If no workspace contains it, then it belongs to the default workspace.
let defaultWorkspace = this.get(this._defaultWorkspacePath);
if (!defaultWorkspace) {
- // If there is only one workspace, use that one.
- const workspaceNames = [...this.keys()];
- if (workspaceNames.length === 1) {
- return this.get(workspaceNames[0])!;
- }
-
// Create a default workspace for files that are outside
// of all workspaces.
defaultWorkspace = {
@@ -105,7 +122,6 @@ export class WorkspaceMap extends Map {
disableWorkspaceSymbol: false,
isInitialized: createDeferred(),
searchPathsToWatch: [],
- owns: (f) => true,
};
this.set(this._defaultWorkspacePath, defaultWorkspace);
ls.updateSettingsForWorkspace(defaultWorkspace).ignoreErrors();
@@ -116,4 +132,33 @@ export class WorkspaceMap extends Map {
return bestInstance;
}
+
+ getContainingWorkspace(filePath: string) {
+ return this._getBestWorkspace(
+ this.getNonDefaultWorkspaces(WellKnownWorkspaceKinds.Regular).filter((w) => filePath.startsWith(w.path))
+ );
+ }
+
+ private _getBestWorkspace(workspaces: WorkspaceServiceInstance[]) {
+ if (workspaces.length === 0) {
+ return undefined;
+ }
+
+ if (workspaces.length === 1) {
+ return workspaces[0];
+ }
+
+ // Best workspace is the inner most workspace.
+ return workspaces.reduce((previousWorkspace, currentWorkspace) => {
+ if (!previousWorkspace) {
+ return currentWorkspace;
+ }
+
+ if (currentWorkspace.path.startsWith(previousWorkspace.path)) {
+ return currentWorkspace;
+ }
+
+ return previousWorkspace;
+ }, workspaces[0]);
+ }
}