mirror of
https://github.com/microsoft/pyright.git
synced 2024-08-16 11:20:22 +03:00
Added support for configuration inheritance through an "extends" configuration option. This addresses #4366. (#7997)
This commit is contained in:
parent
0a83d6459c
commit
421dabee57
@ -16,6 +16,8 @@ Relative paths specified within the config file are relative to the config file
|
||||
|
||||
- **strict** [array of paths, optional]: Paths of directories or files that should use “strict” analysis if they are included. This is the same as manually adding a “# pyright: strict” comment. In strict mode, most type-checking rules are enabled. Refer to [this table](configuration.md#diagnostic-settings-defaults) for details about which rules are enabled in strict mode. Paths may contain wildcard characters ** (a directory or multiple levels of directories), * (a sequence of zero or more characters), or ? (a single character).
|
||||
|
||||
- **extends** [path, optional]: Path to another `.json` or `.toml` file that is used as a “base configuration”, allowing this configuration to inherit configuration settings. Top-level keys within this configuration overwrite top-level keys in the base configuration. Multiple levels of inheritance are supported. Relative paths specified in a configuration file are resolved relative to the location of that configuration file.
|
||||
|
||||
- **defineConstant** [map of constants to values (boolean or string), optional]: Set of identifiers that should be assumed to contain a constant value wherever used within this program. For example, `{ "DEBUG": true }` indicates that pyright should assume that the identifier `DEBUG` will always be equal to `True`. If this identifier is used within a conditional expression (such as `if not DEBUG:`) pyright will use the indicated value to determine whether the guarded block is reachable or not. Member expressions that reference one of these constants (e.g. `my_module.DEBUG`) are also supported.
|
||||
|
||||
- **typeshedPath** [path, optional]: Path to a directory that contains typeshed type stub files. Pyright ships with a bundled copy of typeshed type stubs. If you want to use a different version of typeshed stubs, you can clone the [typeshed github repo](https://github.com/python/typeshed) to a local directory and reference the location with this path. This option is useful if you’re actively contributing updates to typeshed.
|
||||
|
@ -54,7 +54,7 @@ import { MaxAnalysisTime, Program } from './program';
|
||||
import { findPythonSearchPaths } from './pythonPathUtils';
|
||||
import { IPythonMode } from './sourceFile';
|
||||
|
||||
export const configFileNames = ['pyrightconfig.json'];
|
||||
export const configFileName = 'pyrightconfig.json';
|
||||
export const pyprojectTomlName = 'pyproject.toml';
|
||||
|
||||
// How long since the last user activity should we wait until running
|
||||
@ -84,6 +84,11 @@ export interface AnalyzerServiceOptions {
|
||||
fileSystem?: FileSystem;
|
||||
}
|
||||
|
||||
interface ConfigFileContents {
|
||||
configFileDirUri: Uri;
|
||||
configFileJsonObj: object;
|
||||
}
|
||||
|
||||
// Hold uniqueId for this service. It can be used to distinguish each service later.
|
||||
let _nextServiceId = 1;
|
||||
|
||||
@ -103,7 +108,8 @@ export class AnalyzerService {
|
||||
private _sourceFileWatcher: FileWatcher | undefined;
|
||||
private _reloadConfigTimer: any;
|
||||
private _libraryReanalysisTimer: any;
|
||||
private _configFileUri: Uri | undefined;
|
||||
private _primaryConfigFileUri: Uri | undefined;
|
||||
private _extendedConfigFileUris: Uri[] = [];
|
||||
private _configFileWatcher: FileWatcher | undefined;
|
||||
private _libraryFileWatcher: FileWatcher | undefined;
|
||||
private _librarySearchUrisToWatch: Uri[] | undefined;
|
||||
@ -534,11 +540,12 @@ export class AnalyzerService {
|
||||
? Uri.file(commandLineOptions.configFilePath, this.serviceProvider, /* checkRelative */ true)
|
||||
: projectRoot.resolvePaths(commandLineOptions.configFilePath)
|
||||
);
|
||||
|
||||
if (!this.fs.existsSync(configFilePath)) {
|
||||
this._console.info(`Configuration file not found at ${configFilePath.toUserVisibleString()}.`);
|
||||
configFilePath = projectRoot;
|
||||
} else {
|
||||
if (configFilePath.lastExtension.endsWith('.json')) {
|
||||
if (configFilePath.lastExtension.endsWith('.json') || configFilePath.lastExtension.endsWith('.toml')) {
|
||||
projectRoot = configFilePath.getDirectory();
|
||||
} else {
|
||||
projectRoot = configFilePath;
|
||||
@ -648,30 +655,23 @@ export class AnalyzerService {
|
||||
}
|
||||
}
|
||||
|
||||
this._configFileUri = configFilePath || pyprojectFilePath;
|
||||
|
||||
configOptions.disableTaggedHints = !!commandLineOptions.disableTaggedHints;
|
||||
|
||||
// If we found a config file, parse it to compute the effective options.
|
||||
let configJsonObj: object | undefined;
|
||||
if (configFilePath) {
|
||||
this._console.info(`Loading configuration file at ${configFilePath.toUserVisibleString()}`);
|
||||
configJsonObj = this._parseJsonConfigFile(configFilePath);
|
||||
} else if (pyprojectFilePath) {
|
||||
this._console.info(`Loading pyproject.toml file at ${pyprojectFilePath.toUserVisibleString()}`);
|
||||
configJsonObj = this._parsePyprojectTomlFile(pyprojectFilePath);
|
||||
}
|
||||
const configs = this._getExtendedConfigurations(configFilePath ?? pyprojectFilePath);
|
||||
|
||||
if (configJsonObj) {
|
||||
configOptions.initializeFromJson(
|
||||
configJsonObj,
|
||||
this._typeCheckingMode,
|
||||
this.serviceProvider,
|
||||
host,
|
||||
commandLineOptions
|
||||
);
|
||||
if (configs) {
|
||||
for (const config of configs) {
|
||||
configOptions.initializeFromJson(
|
||||
config.configFileJsonObj,
|
||||
config.configFileDirUri,
|
||||
this._typeCheckingMode,
|
||||
this.serviceProvider,
|
||||
host,
|
||||
commandLineOptions
|
||||
);
|
||||
}
|
||||
|
||||
const configFileDir = this._configFileUri!.getDirectory();
|
||||
const configFileDir = this._primaryConfigFileUri!.getDirectory();
|
||||
|
||||
// If no include paths were provided, assume that all files within
|
||||
// the project should be included.
|
||||
@ -860,6 +860,61 @@ export class AnalyzerService {
|
||||
return configOptions;
|
||||
}
|
||||
|
||||
// Loads the config JSON object from the specified config file along with any
|
||||
// chained config files specified in the "extends" property (recursively).
|
||||
private _getExtendedConfigurations(primaryConfigFileUri: Uri | undefined): ConfigFileContents[] | undefined {
|
||||
this._primaryConfigFileUri = primaryConfigFileUri;
|
||||
this._extendedConfigFileUris = [];
|
||||
|
||||
if (!primaryConfigFileUri) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
let curConfigFileUri = primaryConfigFileUri;
|
||||
|
||||
const configJsonObjs: ConfigFileContents[] = [];
|
||||
|
||||
while (true) {
|
||||
this._extendedConfigFileUris.push(curConfigFileUri);
|
||||
|
||||
let configFileJsonObj: object | undefined;
|
||||
|
||||
// Is this a TOML or JSON file?
|
||||
if (curConfigFileUri.lastExtension.endsWith('.toml')) {
|
||||
this._console.info(`Loading pyproject.toml file at ${curConfigFileUri.toUserVisibleString()}`);
|
||||
configFileJsonObj = this._parsePyprojectTomlFile(curConfigFileUri);
|
||||
} else {
|
||||
this._console.info(`Loading configuration file at ${curConfigFileUri.toUserVisibleString()}`);
|
||||
configFileJsonObj = this._parseJsonConfigFile(curConfigFileUri);
|
||||
}
|
||||
|
||||
if (!configFileJsonObj) {
|
||||
break;
|
||||
}
|
||||
|
||||
// Push onto the start of the array so base configs are processed first.
|
||||
configJsonObjs.unshift({ configFileJsonObj, configFileDirUri: curConfigFileUri.getDirectory() });
|
||||
|
||||
const baseConfigUri = ConfigOptions.resolveExtends(configFileJsonObj, curConfigFileUri.getDirectory());
|
||||
if (!baseConfigUri) {
|
||||
break;
|
||||
}
|
||||
|
||||
// Check for circular references.
|
||||
if (this._extendedConfigFileUris.some((uri) => uri.equals(baseConfigUri))) {
|
||||
this._console.error(
|
||||
`Circular reference in configuration file "extends" setting: ${curConfigFileUri.toUserVisibleString()} ` +
|
||||
`extends ${baseConfigUri.toUserVisibleString()}`
|
||||
);
|
||||
break;
|
||||
}
|
||||
|
||||
curConfigFileUri = baseConfigUri;
|
||||
}
|
||||
|
||||
return configJsonObjs;
|
||||
}
|
||||
|
||||
private _getTypeStubFolder() {
|
||||
const stubPath =
|
||||
this._configOptions.stubPath ??
|
||||
@ -914,12 +969,11 @@ export class AnalyzerService {
|
||||
}
|
||||
|
||||
private _findConfigFile(searchPath: Uri): Uri | undefined {
|
||||
for (const name of configFileNames) {
|
||||
const fileName = searchPath.resolvePaths(name);
|
||||
if (this.fs.existsSync(fileName)) {
|
||||
return this.fs.realCasePath(fileName);
|
||||
}
|
||||
const fileName = searchPath.resolvePaths(configFileName);
|
||||
if (this.fs.existsSync(fileName)) {
|
||||
return this.fs.realCasePath(fileName);
|
||||
}
|
||||
|
||||
return undefined;
|
||||
}
|
||||
|
||||
@ -1573,8 +1627,8 @@ export class AnalyzerService {
|
||||
return;
|
||||
}
|
||||
|
||||
if (this._configFileUri) {
|
||||
this._configFileWatcher = this.fs.createFileSystemWatcher([this._configFileUri], (event) => {
|
||||
if (this._primaryConfigFileUri) {
|
||||
this._configFileWatcher = this.fs.createFileSystemWatcher(this._extendedConfigFileUris, (event) => {
|
||||
if (this._verboseOutput) {
|
||||
this._console.info(`Received fs event '${event}' for config file`);
|
||||
}
|
||||
@ -1588,7 +1642,7 @@ export class AnalyzerService {
|
||||
|
||||
if (event === 'add' || event === 'change') {
|
||||
const fileName = getFileName(path);
|
||||
if (fileName && configFileNames.some((name) => name === fileName)) {
|
||||
if (fileName === configFileName) {
|
||||
if (this._verboseOutput) {
|
||||
this._console.info(`Received fs event '${event}' for config file`);
|
||||
}
|
||||
@ -1624,8 +1678,8 @@ export class AnalyzerService {
|
||||
private _reloadConfigFile() {
|
||||
this._updateConfigFileWatcher();
|
||||
|
||||
if (this._configFileUri) {
|
||||
this._console.info(`Reloading configuration file at ${this._configFileUri.toUserVisibleString()}`);
|
||||
if (this._primaryConfigFileUri) {
|
||||
this._console.info(`Reloading configuration file at ${this._primaryConfigFileUri.toUserVisibleString()}`);
|
||||
|
||||
const host = this._backgroundAnalysisProgram.host;
|
||||
|
||||
|
@ -19,8 +19,8 @@ import { DiagnosticRule } from './diagnosticRules';
|
||||
import { FileSystem } from './fileSystem';
|
||||
import { Host } from './host';
|
||||
import { PythonVersion, latestStablePythonVersion } from './pythonVersion';
|
||||
import { ServiceProvider } from './serviceProvider';
|
||||
import { ServiceKeys } from './serviceKeys';
|
||||
import { ServiceProvider } from './serviceProvider';
|
||||
import { Uri } from './uri/uri';
|
||||
import { FileSpec, getFileSpec, isDirectory } from './uri/uriUtils';
|
||||
|
||||
@ -1104,6 +1104,7 @@ export class ConfigOptions {
|
||||
// Initialize the structure from a JSON object.
|
||||
initializeFromJson(
|
||||
configObj: any,
|
||||
configDirUri: Uri,
|
||||
typeCheckingMode: string | undefined,
|
||||
serviceProvider: ServiceProvider,
|
||||
host: Host,
|
||||
@ -1125,7 +1126,7 @@ export class ConfigOptions {
|
||||
} else if (isAbsolute(fileSpec)) {
|
||||
console.error(`Ignoring path "${fileSpec}" in "include" array because it is not relative.`);
|
||||
} else {
|
||||
this.include.push(getFileSpec(this.projectRoot, fileSpec));
|
||||
this.include.push(getFileSpec(configDirUri, fileSpec));
|
||||
}
|
||||
});
|
||||
}
|
||||
@ -1144,7 +1145,7 @@ export class ConfigOptions {
|
||||
} else if (isAbsolute(fileSpec)) {
|
||||
console.error(`Ignoring path "${fileSpec}" in "exclude" array because it is not relative.`);
|
||||
} else {
|
||||
this.exclude.push(getFileSpec(this.projectRoot, fileSpec));
|
||||
this.exclude.push(getFileSpec(configDirUri, fileSpec));
|
||||
}
|
||||
});
|
||||
}
|
||||
@ -1163,7 +1164,7 @@ export class ConfigOptions {
|
||||
} else if (isAbsolute(fileSpec)) {
|
||||
console.error(`Ignoring path "${fileSpec}" in "ignore" array because it is not relative.`);
|
||||
} else {
|
||||
this.ignore.push(getFileSpec(this.projectRoot, fileSpec));
|
||||
this.ignore.push(getFileSpec(configDirUri, fileSpec));
|
||||
}
|
||||
});
|
||||
}
|
||||
@ -1182,14 +1183,14 @@ export class ConfigOptions {
|
||||
} else if (isAbsolute(fileSpec)) {
|
||||
console.error(`Ignoring path "${fileSpec}" in "strict" array because it is not relative.`);
|
||||
} else {
|
||||
this.strict.push(getFileSpec(this.projectRoot, fileSpec));
|
||||
this.strict.push(getFileSpec(configDirUri, fileSpec));
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// If there is a "typeCheckingMode", it can override the provided setting.
|
||||
let configTypeCheckingMode: string | undefined;
|
||||
let configTypeCheckingMode: string | undefined = this.typeCheckingMode;
|
||||
if (configObj.typeCheckingMode !== undefined) {
|
||||
if (
|
||||
configObj.typeCheckingMode === 'off' ||
|
||||
@ -1239,17 +1240,15 @@ export class ConfigOptions {
|
||||
});
|
||||
|
||||
// Read the "venvPath".
|
||||
this.venvPath = undefined;
|
||||
if (configObj.venvPath !== undefined) {
|
||||
if (typeof configObj.venvPath !== 'string') {
|
||||
console.error(`Config "venvPath" field must contain a string.`);
|
||||
} else {
|
||||
this.venvPath = this.projectRoot.resolvePaths(configObj.venvPath);
|
||||
this.venvPath = configDirUri.resolvePaths(configObj.venvPath);
|
||||
}
|
||||
}
|
||||
|
||||
// Read the "venv" name.
|
||||
this.venv = undefined;
|
||||
if (configObj.venv !== undefined) {
|
||||
if (typeof configObj.venv !== 'string') {
|
||||
console.error(`Config "venv" field must contain a string.`);
|
||||
@ -1269,7 +1268,7 @@ export class ConfigOptions {
|
||||
if (typeof path !== 'string') {
|
||||
console.error(`Config "extraPaths" field ${pathIndex} must be a string.`);
|
||||
} else {
|
||||
this.defaultExtraPaths!.push(this.projectRoot.resolvePaths(path));
|
||||
this.defaultExtraPaths!.push(configDirUri.resolvePaths(path));
|
||||
}
|
||||
});
|
||||
}
|
||||
@ -1312,19 +1311,17 @@ export class ConfigOptions {
|
||||
this.ensureDefaultPythonPlatform(host, console);
|
||||
|
||||
// Read the "typeshedPath" setting.
|
||||
this.typeshedPath = undefined;
|
||||
if (configObj.typeshedPath !== undefined) {
|
||||
if (typeof configObj.typeshedPath !== 'string') {
|
||||
console.error(`Config "typeshedPath" field must contain a string.`);
|
||||
} else {
|
||||
this.typeshedPath = configObj.typeshedPath
|
||||
? this.projectRoot.resolvePaths(configObj.typeshedPath)
|
||||
? configDirUri.resolvePaths(configObj.typeshedPath)
|
||||
: undefined;
|
||||
}
|
||||
}
|
||||
|
||||
// Read the "stubPath" setting.
|
||||
this.stubPath = undefined;
|
||||
|
||||
// Keep this for backward compatibility
|
||||
if (configObj.typingsPath !== undefined) {
|
||||
@ -1332,7 +1329,7 @@ export class ConfigOptions {
|
||||
console.error(`Config "typingsPath" field must contain a string.`);
|
||||
} else {
|
||||
console.error(`Config "typingsPath" is now deprecated. Please, use stubPath instead.`);
|
||||
this.stubPath = this.projectRoot.resolvePaths(configObj.typingsPath);
|
||||
this.stubPath = configDirUri.resolvePaths(configObj.typingsPath);
|
||||
}
|
||||
}
|
||||
|
||||
@ -1340,7 +1337,7 @@ export class ConfigOptions {
|
||||
if (typeof configObj.stubPath !== 'string') {
|
||||
console.error(`Config "stubPath" field must contain a string.`);
|
||||
} else {
|
||||
this.stubPath = this.projectRoot.resolvePaths(configObj.stubPath);
|
||||
this.stubPath = configDirUri.resolvePaths(configObj.stubPath);
|
||||
}
|
||||
}
|
||||
|
||||
@ -1384,14 +1381,23 @@ export class ConfigOptions {
|
||||
|
||||
// Read the "executionEnvironments" array. This should be done at the end
|
||||
// after we've established default values.
|
||||
this.executionEnvironments = [];
|
||||
if (configObj.executionEnvironments !== undefined) {
|
||||
if (!Array.isArray(configObj.executionEnvironments)) {
|
||||
console.error(`Config "executionEnvironments" field must contain an array.`);
|
||||
} else {
|
||||
this.executionEnvironments = [];
|
||||
|
||||
const execEnvironments = configObj.executionEnvironments as ExecutionEnvironment[];
|
||||
|
||||
execEnvironments.forEach((env, index) => {
|
||||
const execEnv = this._initExecutionEnvironmentFromJson(env, index, console, commandLineOptions);
|
||||
const execEnv = this._initExecutionEnvironmentFromJson(
|
||||
env,
|
||||
configDirUri,
|
||||
index,
|
||||
console,
|
||||
commandLineOptions
|
||||
);
|
||||
|
||||
if (execEnv) {
|
||||
this.executionEnvironments.push(execEnv);
|
||||
}
|
||||
@ -1450,6 +1456,18 @@ export class ConfigOptions {
|
||||
}
|
||||
}
|
||||
|
||||
static resolveExtends(configObj: any, configDirUri: Uri): Uri | undefined {
|
||||
if (configObj.extends !== undefined) {
|
||||
if (typeof configObj.extends !== 'string') {
|
||||
console.error(`Config "extends" field must contain a string.`);
|
||||
} else {
|
||||
return configDirUri.resolvePaths(configObj.extends);
|
||||
}
|
||||
}
|
||||
|
||||
return undefined;
|
||||
}
|
||||
|
||||
ensureDefaultPythonPlatform(host: Host, console: ConsoleInterface) {
|
||||
// If no default python platform was specified, assume that the
|
||||
// user wants to use the current platform.
|
||||
@ -1552,6 +1570,7 @@ export class ConfigOptions {
|
||||
|
||||
private _initExecutionEnvironmentFromJson(
|
||||
envObj: any,
|
||||
configDirUri: Uri,
|
||||
index: number,
|
||||
console: ConsoleInterface,
|
||||
commandLineOptions?: CommandLineOptions
|
||||
@ -1559,7 +1578,7 @@ export class ConfigOptions {
|
||||
try {
|
||||
const newExecEnv = new ExecutionEnvironment(
|
||||
this._getEnvironmentName(),
|
||||
this.projectRoot,
|
||||
configDirUri,
|
||||
this.defaultPythonVersion,
|
||||
this.defaultPythonPlatform,
|
||||
this.defaultExtraPaths
|
||||
@ -1567,7 +1586,7 @@ export class ConfigOptions {
|
||||
|
||||
// Validate the root.
|
||||
if (envObj.root && typeof envObj.root === 'string') {
|
||||
newExecEnv.root = this.projectRoot.resolvePaths(envObj.root);
|
||||
newExecEnv.root = configDirUri.resolvePaths(envObj.root);
|
||||
} else {
|
||||
console.error(`Config executionEnvironments index ${index}: missing root value.`);
|
||||
}
|
||||
@ -1587,7 +1606,7 @@ export class ConfigOptions {
|
||||
` extraPaths field ${pathIndex} must be a string.`
|
||||
);
|
||||
} else {
|
||||
newExecEnv.extraPaths.push(this.projectRoot.resolvePaths(path));
|
||||
newExecEnv.extraPaths.push(configDirUri.resolvePaths(path));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
@ -12,11 +12,11 @@ import {
|
||||
FileSystemWatcher,
|
||||
WatchKind,
|
||||
} from 'vscode-languageserver';
|
||||
import { DynamicFeature } from './dynamicFeature';
|
||||
import { configFileNames } from '../analyzer/service';
|
||||
import { configFileName } from '../analyzer/service';
|
||||
import { FileSystem } from '../common/fileSystem';
|
||||
import { deduplicateFolders, isFile } from '../common/uri/uriUtils';
|
||||
import { WorkspaceFactory } from '../workspaceFactory';
|
||||
import { FileSystem } from '../common/fileSystem';
|
||||
import { DynamicFeature } from './dynamicFeature';
|
||||
|
||||
export class FileWatcherDynamicFeature extends DynamicFeature {
|
||||
constructor(
|
||||
@ -33,7 +33,7 @@ export class FileWatcherDynamicFeature extends DynamicFeature {
|
||||
|
||||
// Set default (config files and all workspace files) first.
|
||||
const watchers: FileSystemWatcher[] = [
|
||||
...configFileNames.map((fileName) => ({ globPattern: `**/${fileName}`, kind: watchKind })),
|
||||
{ globPattern: `**/${configFileName}`, kind: watchKind },
|
||||
{ globPattern: '**', kind: watchKind },
|
||||
];
|
||||
|
||||
|
@ -216,7 +216,7 @@ test('PythonPlatform', () => {
|
||||
const nullConsole = new NullConsole();
|
||||
|
||||
const sp = createServiceProvider(fs, nullConsole);
|
||||
configOptions.initializeFromJson(json, undefined, sp, new NoAccessHost());
|
||||
configOptions.initializeFromJson(json, cwd, undefined, sp, new NoAccessHost());
|
||||
|
||||
const env = configOptions.executionEnvironments[0];
|
||||
assert.strictEqual(env.pythonPlatform, 'platform');
|
||||
@ -340,9 +340,10 @@ test('verify config fileSpecs after cloning', () => {
|
||||
ignore: ['**/node_modules/**'],
|
||||
};
|
||||
|
||||
const config = new ConfigOptions(Uri.file(process.cwd(), fs));
|
||||
const rootUri = Uri.file(process.cwd(), fs);
|
||||
const config = new ConfigOptions(rootUri);
|
||||
const sp = createServiceProvider(fs, new NullConsole());
|
||||
config.initializeFromJson(configFile, undefined, sp, new TestAccessHost());
|
||||
config.initializeFromJson(configFile, rootUri, undefined, sp, new TestAccessHost());
|
||||
const cloned = deserialize(serialize(config));
|
||||
|
||||
assert.deepEqual(config.ignore, cloned.ignore);
|
||||
@ -371,3 +372,18 @@ test('extra paths on undefined execution root/default workspace', () => {
|
||||
expectedExtraPaths.map((u) => u.getFilePath())
|
||||
);
|
||||
});
|
||||
|
||||
test('Extended config files', () => {
|
||||
const cwd = normalizePath(combinePaths(process.cwd(), 'src/tests/samples/project_with_extended_config'));
|
||||
const service = createAnalyzer();
|
||||
const commandLineOptions = new CommandLineOptions(cwd, /* fromVsCodeExtension */ true);
|
||||
|
||||
service.setOptions(commandLineOptions);
|
||||
|
||||
const fileList = service.test_getFileNamesFromFileSpecs();
|
||||
const fileNames = fileList.map((p) => p.fileName).sort();
|
||||
assert.deepStrictEqual(fileNames, ['sample.pyi', 'test.py']);
|
||||
|
||||
const configOptions = service.test_getConfigOptions(commandLineOptions);
|
||||
assert.equal(configOptions.typeCheckingMode, 'strict');
|
||||
});
|
||||
|
@ -180,7 +180,13 @@ export class TestState {
|
||||
const configOptions = this._convertGlobalOptionsToConfigOptions(vfsInfo.projectRoot, mountPaths);
|
||||
|
||||
if (this.rawConfigJson) {
|
||||
configOptions.initializeFromJson(this.rawConfigJson, 'standard', this.serviceProvider, testAccessHost);
|
||||
configOptions.initializeFromJson(
|
||||
this.rawConfigJson,
|
||||
Uri.file(projectRoot, this.serviceProvider),
|
||||
'standard',
|
||||
this.serviceProvider,
|
||||
testAccessHost
|
||||
);
|
||||
this._applyTestConfigOptions(configOptions);
|
||||
}
|
||||
|
||||
|
@ -9,7 +9,7 @@
|
||||
import assert from 'assert';
|
||||
import * as JSONC from 'jsonc-parser';
|
||||
|
||||
import { configFileNames } from '../../../analyzer/service';
|
||||
import { configFileName } from '../../../analyzer/service';
|
||||
import { Comparison, toBoolean } from '../../../common/core';
|
||||
import { combinePaths, getBaseFileName } from '../../../common/pathUtils';
|
||||
import { getStringComparer } from '../../../common/stringUtils';
|
||||
@ -76,5 +76,5 @@ export function getMarkerNames(testData: FourSlashData): string[] {
|
||||
|
||||
function isConfig(file: FourSlashFile, ignoreCase: boolean): boolean {
|
||||
const comparer = getStringComparer(ignoreCase);
|
||||
return configFileNames.some((f) => comparer(getBaseFileName(file.fileName), f) === Comparison.EqualTo);
|
||||
return comparer(getBaseFileName(file.fileName), configFileName) === Comparison.EqualTo;
|
||||
}
|
||||
|
@ -0,0 +1,3 @@
|
||||
[tool.pyright]
|
||||
extends = "sub2/baseconfig2.json"
|
||||
typeCheckingMode = "strict"
|
@ -0,0 +1,2 @@
|
||||
[tool.pyright]
|
||||
extends = "baseconfig1.toml"
|
@ -0,0 +1,4 @@
|
||||
{
|
||||
"extends": "../sub3/baseconfig3.json",
|
||||
"typeCheckingMode": "standard"
|
||||
}
|
@ -0,0 +1,4 @@
|
||||
{
|
||||
"stubPath": "stubs",
|
||||
"typeCheckingMode": "basic"
|
||||
}
|
@ -0,0 +1 @@
|
||||
x: int
|
@ -0,0 +1,7 @@
|
||||
# pyright: reportMissingModuleSource=false
|
||||
|
||||
from typing import assert_type
|
||||
from sample import x
|
||||
|
||||
assert_type(x, int)
|
||||
|
@ -23,6 +23,12 @@
|
||||
}
|
||||
},
|
||||
"properties": {
|
||||
"extends": {
|
||||
"$id": "#/properties/extends",
|
||||
"type": "string",
|
||||
"title": "Path to configuration file that this configuration extends",
|
||||
"pattern": "^(.*)$"
|
||||
},
|
||||
"include": {
|
||||
"$id": "#/properties/include",
|
||||
"type": "array",
|
||||
|
Loading…
Reference in New Issue
Block a user