Skip to content

Commit 5f599f9

Browse files
authored
Added error reporting for unknown or unsupported config settings. This addresses #11040. (#11045)
1 parent cadded6 commit 5f599f9

File tree

1 file changed

+49
-1
lines changed

1 file changed

+49
-1
lines changed

packages/pyright-internal/src/common/configOptions.ts

Lines changed: 49 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1151,9 +1151,12 @@ export class ConfigOptions {
11511151
initializeFromJson(configObj: any, configDirUri: Uri, serviceProvider: ServiceProvider, host: Host) {
11521152
this.initializedFromJson = true;
11531153
const console = serviceProvider.tryGet(ServiceKeys.console) ?? new NullConsole();
1154+
const configObjKeys = configObj && typeof configObj === 'object' ? Object.getOwnPropertyNames(configObj) : [];
1155+
const unusedConfigKeys = new Set<string>(configObjKeys);
11541156

11551157
// Read the "include" entry.
11561158
if (configObj.include !== undefined) {
1159+
unusedConfigKeys.delete('include');
11571160
if (!Array.isArray(configObj.include)) {
11581161
console.error(`Config "include" entry must contain an array.`);
11591162
} else {
@@ -1173,6 +1176,7 @@ export class ConfigOptions {
11731176

11741177
// Read the "exclude" entry.
11751178
if (configObj.exclude !== undefined) {
1179+
unusedConfigKeys.delete('exclude');
11761180
if (!Array.isArray(configObj.exclude)) {
11771181
console.error(`Config "exclude" entry must contain an array.`);
11781182
} else {
@@ -1192,6 +1196,7 @@ export class ConfigOptions {
11921196

11931197
// Read the "ignore" entry.
11941198
if (configObj.ignore !== undefined) {
1199+
unusedConfigKeys.delete('ignore');
11951200
if (!Array.isArray(configObj.ignore)) {
11961201
console.error(`Config "ignore" entry must contain an array.`);
11971202
} else {
@@ -1213,6 +1218,7 @@ export class ConfigOptions {
12131218

12141219
// Read the "strict" entry.
12151220
if (configObj.strict !== undefined) {
1221+
unusedConfigKeys.delete('strict');
12161222
if (!Array.isArray(configObj.strict)) {
12171223
console.error(`Config "strict" entry must contain an array.`);
12181224
} else {
@@ -1232,6 +1238,7 @@ export class ConfigOptions {
12321238

12331239
// If there is a "typeCheckingMode", it can override the provided setting.
12341240
if (configObj.typeCheckingMode !== undefined) {
1241+
unusedConfigKeys.delete('typeCheckingMode');
12351242
if (
12361243
configObj.typeCheckingMode === 'off' ||
12371244
configObj.typeCheckingMode === 'basic' ||
@@ -1245,6 +1252,7 @@ export class ConfigOptions {
12451252
}
12461253

12471254
if (configObj.useLibraryCodeForTypes !== undefined) {
1255+
unusedConfigKeys.delete('useLibraryCodeForTypes');
12481256
if (typeof configObj.useLibraryCodeForTypes === 'boolean') {
12491257
this.useLibraryCodeForTypes = configObj.useLibraryCodeForTypes;
12501258
} else {
@@ -1255,6 +1263,7 @@ export class ConfigOptions {
12551263
// Apply overrides from the config file for the boolean rules.
12561264
const configRuleSet = { ...this.diagnosticRuleSet };
12571265
getBooleanDiagnosticRules(/* includeNonOverridable */ true).forEach((ruleName) => {
1266+
unusedConfigKeys.delete(ruleName);
12581267
(configRuleSet as any)[ruleName] = this._convertBoolean(
12591268
configObj[ruleName],
12601269
ruleName,
@@ -1264,6 +1273,7 @@ export class ConfigOptions {
12641273

12651274
// Apply overrides from the config file for the diagnostic level rules.
12661275
getDiagLevelDiagnosticRules().forEach((ruleName) => {
1276+
unusedConfigKeys.delete(ruleName);
12671277
(configRuleSet as any)[ruleName] = this._convertDiagnosticLevel(
12681278
configObj[ruleName],
12691279
ruleName,
@@ -1274,6 +1284,7 @@ export class ConfigOptions {
12741284

12751285
// Read the "venvPath".
12761286
if (configObj.venvPath !== undefined) {
1287+
unusedConfigKeys.delete('venvPath');
12771288
if (typeof configObj.venvPath !== 'string') {
12781289
console.error(`Config "venvPath" field must contain a string.`);
12791290
} else {
@@ -1283,6 +1294,7 @@ export class ConfigOptions {
12831294

12841295
// Read the "venv" name.
12851296
if (configObj.venv !== undefined) {
1297+
unusedConfigKeys.delete('venv');
12861298
if (typeof configObj.venv !== 'string') {
12871299
console.error(`Config "venv" field must contain a string.`);
12881300
} else {
@@ -1293,6 +1305,7 @@ export class ConfigOptions {
12931305
// Read the config "extraPaths".
12941306
const configExtraPaths: Uri[] = [];
12951307
if (configObj.extraPaths !== undefined) {
1308+
unusedConfigKeys.delete('extraPaths');
12961309
if (!Array.isArray(configObj.extraPaths)) {
12971310
console.error(`Config "extraPaths" field must contain an array.`);
12981311
} else {
@@ -1310,6 +1323,7 @@ export class ConfigOptions {
13101323

13111324
// Read the default "pythonVersion".
13121325
if (configObj.pythonVersion !== undefined) {
1326+
unusedConfigKeys.delete('pythonVersion');
13131327
if (typeof configObj.pythonVersion === 'string') {
13141328
const version = PythonVersion.fromString(configObj.pythonVersion);
13151329
if (version) {
@@ -1324,6 +1338,7 @@ export class ConfigOptions {
13241338

13251339
// Read the default "pythonPlatform".
13261340
if (configObj.pythonPlatform !== undefined) {
1341+
unusedConfigKeys.delete('pythonPlatform');
13271342
if (typeof configObj.pythonPlatform !== 'string') {
13281343
console.error(`Config "pythonPlatform" field must contain a string.`);
13291344
} else {
@@ -1335,7 +1350,8 @@ export class ConfigOptions {
13351350
// or supported. It was added specifically to improve initialization
13361351
// performance for playgrounds or web-based environments where native
13371352
// libraries will not be present.
1338-
if (configObj.skipNativeLibraries) {
1353+
if (configObj.skipNativeLibraries !== undefined) {
1354+
unusedConfigKeys.delete('skipNativeLibraries');
13391355
if (typeof configObj.skipNativeLibraries === 'boolean') {
13401356
this.skipNativeLibraries = configObj.skipNativeLibraries;
13411357
} else {
@@ -1345,6 +1361,7 @@ export class ConfigOptions {
13451361

13461362
// Read the "typeshedPath" setting.
13471363
if (configObj.typeshedPath !== undefined) {
1364+
unusedConfigKeys.delete('typeshedPath');
13481365
if (typeof configObj.typeshedPath !== 'string') {
13491366
console.error(`Config "typeshedPath" field must contain a string.`);
13501367
} else {
@@ -1358,6 +1375,7 @@ export class ConfigOptions {
13581375

13591376
// Keep this for backward compatibility
13601377
if (configObj.typingsPath !== undefined) {
1378+
unusedConfigKeys.delete('typingsPath');
13611379
if (typeof configObj.typingsPath !== 'string') {
13621380
console.error(`Config "typingsPath" field must contain a string.`);
13631381
} else {
@@ -1367,6 +1385,7 @@ export class ConfigOptions {
13671385
}
13681386

13691387
if (configObj.stubPath !== undefined) {
1388+
unusedConfigKeys.delete('stubPath');
13701389
if (typeof configObj.stubPath !== 'string') {
13711390
console.error(`Config "stubPath" field must contain a string.`);
13721391
} else {
@@ -1378,6 +1397,7 @@ export class ConfigOptions {
13781397
// Don't initialize to a default value because we want the command-line "verbose"
13791398
// switch to apply if this setting isn't specified in the config file.
13801399
if (configObj.verboseOutput !== undefined) {
1400+
unusedConfigKeys.delete('verboseOutput');
13811401
if (typeof configObj.verboseOutput !== 'boolean') {
13821402
console.error(`Config "verboseOutput" field must be true or false.`);
13831403
} else {
@@ -1387,6 +1407,7 @@ export class ConfigOptions {
13871407

13881408
// Read the "defineConstant" setting.
13891409
if (configObj.defineConstant !== undefined) {
1410+
unusedConfigKeys.delete('defineConstant');
13901411
if (typeof configObj.defineConstant !== 'object' || Array.isArray(configObj.defineConstant)) {
13911412
console.error(`Config "defineConstant" field must contain a map indexed by constant names.`);
13921413
} else {
@@ -1405,6 +1426,7 @@ export class ConfigOptions {
14051426

14061427
// Read the "useLibraryCodeForTypes" setting.
14071428
if (configObj.useLibraryCodeForTypes !== undefined) {
1429+
unusedConfigKeys.delete('useLibraryCodeForTypes');
14081430
if (typeof configObj.useLibraryCodeForTypes !== 'boolean') {
14091431
console.error(`Config "useLibraryCodeForTypes" field must be true or false.`);
14101432
} else {
@@ -1414,6 +1436,7 @@ export class ConfigOptions {
14141436

14151437
// Read the "autoImportCompletions" setting.
14161438
if (configObj.autoImportCompletions !== undefined) {
1439+
unusedConfigKeys.delete('autoImportCompletions');
14171440
if (typeof configObj.autoImportCompletions !== 'boolean') {
14181441
console.error(`Config "autoImportCompletions" field must be true or false.`);
14191442
} else {
@@ -1423,6 +1446,7 @@ export class ConfigOptions {
14231446

14241447
// Read the "indexing" setting.
14251448
if (configObj.indexing !== undefined) {
1449+
unusedConfigKeys.delete('indexing');
14261450
if (typeof configObj.indexing !== 'boolean') {
14271451
console.error(`Config "indexing" field must be true or false.`);
14281452
} else {
@@ -1432,6 +1456,7 @@ export class ConfigOptions {
14321456

14331457
// Read the "logTypeEvaluationTime" setting.
14341458
if (configObj.logTypeEvaluationTime !== undefined) {
1459+
unusedConfigKeys.delete('logTypeEvaluationTime');
14351460
if (typeof configObj.logTypeEvaluationTime !== 'boolean') {
14361461
console.error(`Config "logTypeEvaluationTime" field must be true or false.`);
14371462
} else {
@@ -1441,6 +1466,7 @@ export class ConfigOptions {
14411466

14421467
// Read the "typeEvaluationTimeThreshold" setting.
14431468
if (configObj.typeEvaluationTimeThreshold !== undefined) {
1469+
unusedConfigKeys.delete('typeEvaluationTimeThreshold');
14441470
if (typeof configObj.typeEvaluationTimeThreshold !== 'number') {
14451471
console.error(`Config "typeEvaluationTimeThreshold" field must be a number.`);
14461472
} else {
@@ -1450,6 +1476,7 @@ export class ConfigOptions {
14501476

14511477
// Read the "functionSignatureDisplay" setting.
14521478
if (configObj.functionSignatureDisplay !== undefined) {
1479+
unusedConfigKeys.delete('functionSignatureDisplay');
14531480
if (typeof configObj.functionSignatureDisplay !== 'string') {
14541481
console.error(`Config "functionSignatureDisplay" field must be true or false.`);
14551482
} else {
@@ -1461,6 +1488,13 @@ export class ConfigOptions {
14611488
}
14621489
}
14631490
}
1491+
1492+
unusedConfigKeys.delete('executionEnvironments');
1493+
unusedConfigKeys.delete('extends');
1494+
1495+
Array.from(unusedConfigKeys).forEach((unknownKey) => {
1496+
console.error(`Config contains unrecognized setting "${unknownKey}".`);
1497+
});
14641498
}
14651499

14661500
static resolveExtends(configObj: any, configDirUri: Uri): Uri | undefined {
@@ -1626,6 +1660,9 @@ export class ConfigOptions {
16261660
configExtraPaths: Uri[]
16271661
): ExecutionEnvironment | undefined {
16281662
try {
1663+
const envObjKeys = envObj && typeof envObj === 'object' ? Object.getOwnPropertyNames(envObj) : [];
1664+
const unusedEnvKeys = new Set<string>(envObjKeys);
1665+
16291666
const newExecEnv = new ExecutionEnvironment(
16301667
this._getEnvironmentName(),
16311668
configDirUri,
@@ -1636,13 +1673,15 @@ export class ConfigOptions {
16361673
);
16371674

16381675
// Validate the root.
1676+
unusedEnvKeys.delete('root');
16391677
if (envObj.root && typeof envObj.root === 'string') {
16401678
newExecEnv.root = configDirUri.resolvePaths(envObj.root);
16411679
} else {
16421680
console.error(`Config executionEnvironments index ${index}: missing root value.`);
16431681
}
16441682

16451683
// Validate the extraPaths.
1684+
unusedEnvKeys.delete('extraPaths');
16461685
if (envObj.extraPaths) {
16471686
if (!Array.isArray(envObj.extraPaths)) {
16481687
console.error(
@@ -1668,6 +1707,7 @@ export class ConfigOptions {
16681707
}
16691708

16701709
// Validate the pythonVersion.
1710+
unusedEnvKeys.delete('pythonVersion');
16711711
if (envObj.pythonVersion) {
16721712
if (typeof envObj.pythonVersion === 'string') {
16731713
const version = PythonVersion.fromString(envObj.pythonVersion);
@@ -1682,6 +1722,7 @@ export class ConfigOptions {
16821722
}
16831723

16841724
// Validate the pythonPlatform.
1725+
unusedEnvKeys.delete('pythonPlatform');
16851726
if (envObj.pythonPlatform) {
16861727
if (typeof envObj.pythonPlatform === 'string') {
16871728
newExecEnv.pythonPlatform = envObj.pythonPlatform;
@@ -1691,6 +1732,7 @@ export class ConfigOptions {
16911732
}
16921733

16931734
// Validate the name.
1735+
unusedEnvKeys.delete('name');
16941736
if (envObj.name) {
16951737
if (typeof envObj.name === 'string') {
16961738
newExecEnv.name = envObj.name;
@@ -1701,6 +1743,7 @@ export class ConfigOptions {
17011743

17021744
// Apply overrides from the config file for the boolean overrides.
17031745
getBooleanDiagnosticRules(/* includeNonOverridable */ true).forEach((ruleName) => {
1746+
unusedEnvKeys.delete(ruleName);
17041747
(newExecEnv.diagnosticRuleSet as any)[ruleName] = this._convertBoolean(
17051748
envObj[ruleName],
17061749
ruleName,
@@ -1710,13 +1753,18 @@ export class ConfigOptions {
17101753

17111754
// Apply overrides from the config file for the diagnostic level overrides.
17121755
getDiagLevelDiagnosticRules().forEach((ruleName) => {
1756+
unusedEnvKeys.delete(ruleName);
17131757
(newExecEnv.diagnosticRuleSet as any)[ruleName] = this._convertDiagnosticLevel(
17141758
envObj[ruleName],
17151759
ruleName,
17161760
newExecEnv.diagnosticRuleSet[ruleName] as DiagnosticLevel
17171761
);
17181762
});
17191763

1764+
Array.from(unusedEnvKeys).forEach((unknownKey) => {
1765+
console.error(`Config executionEnvironments index ${index}: unrecognized setting "${unknownKey}".`);
1766+
});
1767+
17201768
return newExecEnv;
17211769
} catch {
17221770
console.error(`Config executionEnvironments index ${index} is not accessible.`);

0 commit comments

Comments
 (0)