Skip to content

Commit 5a3c2d7

Browse files
authored
Merge pull request #1051 from krassowski/fix-settings-reconciliation
Fix settings reconciliation for nested properties
2 parents 376c996 + d60304b commit 5a3c2d7

File tree

7 files changed

+555
-47
lines changed

7 files changed

+555
-47
lines changed

packages/.eslintrc.js

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@ module.exports = {
4040
ignoreGlobals: true,
4141
allow: [
4242
'cell_type',
43+
'config_schema',
4344
'execution_count',
4445
'language_info',
4546
'nbconvert_exporter',
@@ -52,7 +53,8 @@ module.exports = {
5253
'lsp_to_ce',
5354
'ce_to_cm',
5455
'cm_to_lsp',
55-
'lsp_to_cm'
56+
'lsp_to_cm',
57+
'workspace_configuration'
5658
]
5759
}
5860
],

packages/jupyterlab-lsp/schema/plugin.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@
3232
"language_servers": {
3333
"title": "Language Server",
3434
"description": "Language-server specific configuration, keyed by implementation, e.g: \n\npyls: {\n serverSettings: {\n pyls: {\n plugins: {\n pydocstyle: {\n enabled: true\n },\n pyflakes: {\n enabled: false\n },\n flake8: {\n enabled: true\n }\n }\n }\n }\n}\n\nAlternatively, using dotted naming convention:\n\npyls: {\n serverSettings: {\n \"pyls.plugins.pydocstyle.enabled\": true,\n \"pyls.plugins.pyflakes.enabled\": false,\n \"pyls.plugins.flake8.enabled\": true\n }\n}",
35+
"type": "object",
3536
"default": {
3637
"pyright": {
3738
"serverSettings": {

packages/jupyterlab-lsp/src/index.ts

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ import { ISettingRegistry } from '@jupyterlab/settingregistry';
2626
import { IStatusBar } from '@jupyterlab/statusbar';
2727
import { ITranslator, nullTranslator } from '@jupyterlab/translation';
2828
import { IFormRendererRegistry } from '@jupyterlab/ui-components';
29+
import { JSONExt } from '@lumino/coreutils';
2930

3031
import '../style/index.css';
3132

@@ -158,13 +159,16 @@ export class LSPExtension {
158159
let languageServerSettings = (options.language_servers ||
159160
{}) as TLanguageServerConfigurations;
160161

161-
// Add `configuration` as a copy of `serverSettings` to work with changed name upstream
162-
// Add `rank` as a copy of priority for the same reason.
162+
// Rename `serverSettings` to `configuration` to work with changed name upstream,
163+
// rename `priority` to `rank` for the same reason.
163164
languageServerSettings = Object.fromEntries(
164165
Object.entries(languageServerSettings).map(([key, value]) => {
165-
value.configuration = value.serverSettings;
166-
value.rank = value.priority;
167-
return [key, value];
166+
const copy = JSONExt.deepCopy(value);
167+
copy.configuration = copy.serverSettings;
168+
copy.rank = copy.priority;
169+
delete copy.priority;
170+
delete copy.serverSettings;
171+
return [key, copy];
168172
})
169173
);
170174

packages/jupyterlab-lsp/src/settings.spec.ts

Lines changed: 250 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,258 @@
1+
import { LanguageServerManager } from '@jupyterlab/lsp';
2+
import { ISettingRegistry } from '@jupyterlab/settingregistry';
3+
import { JSONExt } from '@lumino/coreutils';
4+
15
import { SettingsSchemaManager } from './settings';
26

37
const DEAULT_SERVER_PRIORITY = 50;
48

9+
const SCHEMA: ISettingRegistry.ISchema = {
10+
type: 'object',
11+
definitions: {
12+
'language-server': {
13+
type: 'object',
14+
default: {},
15+
properties: {
16+
priority: {
17+
title: 'Priority of the server',
18+
type: 'number',
19+
default: 50,
20+
minimum: 1
21+
},
22+
serverSettings: {
23+
title: 'Language Server Configurations',
24+
type: 'object',
25+
default: {},
26+
additionalProperties: true
27+
}
28+
}
29+
}
30+
},
31+
properties: {
32+
language_servers: {
33+
title: 'Language Server',
34+
type: 'object',
35+
default: {
36+
pyright: {
37+
serverSettings: {
38+
'python.analysis.useLibraryCodeForTypes': true
39+
}
40+
},
41+
'bash-language-server': {
42+
serverSettings: {
43+
'bashIde.enableSourceErrorDiagnostics': true
44+
}
45+
}
46+
},
47+
patternProperties: {
48+
'.*': {
49+
$ref: '#/definitions/language-server'
50+
}
51+
},
52+
additionalProperties: {
53+
$ref: '#/definitions/language-server'
54+
}
55+
}
56+
}
57+
};
58+
59+
const PYRIGHT_SCHEMA = {
60+
$schema: 'http://json-schema.org/draft-07/schema#',
61+
title: 'Pyright Language Server Configuration',
62+
description:
63+
'Pyright Configuration Schema. Distributed under MIT License, Copyright (c) Microsoft Corporation.',
64+
type: 'object',
65+
properties: {
66+
'python.analysis.diagnosticSeverityOverrides': {
67+
type: 'object',
68+
description:
69+
'Allows a user to override the severity levels for individual diagnostics.',
70+
scope: 'resource',
71+
properties: {
72+
reportGeneralTypeIssues: {
73+
type: 'string',
74+
description:
75+
'Diagnostics for general type inconsistencies, unsupported operations, argument/parameter mismatches, etc. Covers all of the basic type-checking rules not covered by other rules. Does not include syntax errors.',
76+
default: 'error',
77+
enum: ['none', 'information', 'warning', 'error']
78+
},
79+
reportPropertyTypeMismatch: {
80+
type: 'string',
81+
description:
82+
'Diagnostics for property whose setter and getter have mismatched types.',
83+
default: 'none',
84+
enum: ['none', 'information', 'warning', 'error']
85+
},
86+
reportFunctionMemberAccess: {
87+
type: 'string',
88+
description: 'Diagnostics for member accesses on functions.',
89+
default: 'none',
90+
enum: ['none', 'information', 'warning', 'error']
91+
},
92+
reportMissingImports: {
93+
type: 'string',
94+
description:
95+
'Diagnostics for imports that have no corresponding imported python file or type stub file.',
96+
default: 'error',
97+
enum: ['none', 'information', 'warning', 'error']
98+
},
99+
reportUnusedImport: {
100+
type: 'string',
101+
description:
102+
'Diagnostics for an imported symbol that is not referenced within that file.',
103+
default: 'none',
104+
enum: ['none', 'information', 'warning', 'error']
105+
},
106+
reportUnusedClass: {
107+
type: 'string',
108+
description:
109+
'Diagnostics for a class with a private name (starting with an underscore) that is not accessed.',
110+
default: 'none',
111+
enum: ['none', 'information', 'warning', 'error']
112+
}
113+
}
114+
},
115+
'python.analysis.logLevel': {
116+
type: 'string',
117+
default: 'Information',
118+
description: 'Specifies the level of logging for the Output panel',
119+
enum: ['Error', 'Warning', 'Information', 'Trace']
120+
},
121+
'python.analysis.useLibraryCodeForTypes': {
122+
type: 'boolean',
123+
default: false,
124+
description:
125+
'Use library implementations to extract type information when type stub is not present.',
126+
scope: 'resource'
127+
},
128+
'python.pythonPath': {
129+
type: 'string',
130+
default: 'python',
131+
description: 'Path to Python, you can use a custom version of Python.',
132+
scope: 'resource'
133+
}
134+
}
135+
};
136+
137+
const COLLAPSED_PYRIGHT_SETTINGS = {
138+
pyright: {
139+
priority: 50,
140+
serverSettings: {
141+
'python.analysis.autoImportCompletions': false,
142+
'python.analysis.extraPaths': [],
143+
'python.analysis.stubPath': 'typings',
144+
'python.pythonPath': 'python',
145+
'python.analysis.diagnosticSeverityOverrides.reportGeneralTypeIssues':
146+
'error',
147+
'python.analysis.diagnosticSeverityOverrides.reportPropertyTypeMismatch':
148+
'none',
149+
'python.analysis.diagnosticSeverityOverrides.reportFunctionMemberAccess':
150+
'none',
151+
'python.analysis.diagnosticSeverityOverrides.reportMissingImports': 'none'
152+
}
153+
}
154+
};
155+
156+
function map(object: Record<string, any>) {
157+
return new Map(Object.entries(object));
158+
}
159+
160+
const AVAILABLE_SESSIONS = map({
161+
pyright: null as any,
162+
pylsp: null as any
163+
}) as LanguageServerManager['sessions'];
164+
5165
describe('SettingsSchemaManager', () => {
166+
describe('#expandDottedAsNeeded()', () => {
167+
it('should uncollapse pyright defaults', () => {
168+
const partiallyExpaneded = SettingsSchemaManager.expandDottedAsNeeded({
169+
dottedSettings: COLLAPSED_PYRIGHT_SETTINGS,
170+
specs: map({
171+
pyright: {
172+
display_name: 'pyright',
173+
// server-specific defaults and allowed values
174+
config_schema: PYRIGHT_SCHEMA
175+
}
176+
}) as LanguageServerManager['specs']
177+
});
178+
expect(partiallyExpaneded).toEqual({
179+
pyright: {
180+
priority: 50,
181+
serverSettings: {
182+
'python.analysis.autoImportCompletions': false,
183+
'python.analysis.diagnosticSeverityOverrides': {
184+
reportFunctionMemberAccess: 'none',
185+
reportGeneralTypeIssues: 'error',
186+
reportMissingImports: 'none',
187+
reportPropertyTypeMismatch: 'none'
188+
},
189+
'python.analysis.extraPaths': [],
190+
'python.analysis.stubPath': 'typings',
191+
'python.pythonPath': 'python'
192+
}
193+
}
194+
});
195+
});
196+
});
197+
198+
describe('#transformSchemas()', () => {
199+
it('should merge dotted defaults', () => {
200+
const schema = JSONExt.deepCopy(SCHEMA) as any;
201+
202+
// Set a few defaults as if these came from `overrides.json`:
203+
// - using fully dotted name
204+
schema.properties.language_servers.default.pyright.serverSettings[
205+
'python.analysis.diagnosticSeverityOverrides.reportGeneralTypeIssues'
206+
] = 'warning';
207+
// - using nesting on final level (as defined in source pyright schema)
208+
schema.properties.language_servers.default.pyright.serverSettings[
209+
'python.analysis.diagnosticSeverityOverrides'
210+
] = {
211+
reportPropertyTypeMismatch: 'warning'
212+
};
213+
214+
const { defaults } = SettingsSchemaManager.transformSchemas({
215+
// plugin schema which includes overrides from `overrides.json`
216+
schema,
217+
specs: map({
218+
pyright: {
219+
display_name: 'pyright',
220+
// server-specific defaults and allowed values
221+
config_schema: PYRIGHT_SCHEMA,
222+
// overrides defined in specs files e.g. `jupyter_server_config.py`
223+
workspace_configuration: {
224+
// using fully dotted name
225+
'python.analysis.diagnosticSeverityOverrides.reportFunctionMemberAccess':
226+
'warning',
227+
// using nesting on final level (as defined in source pyright schema)
228+
'python.analysis.diagnosticSeverityOverrides': {
229+
reportUnusedImport: 'warning'
230+
}
231+
}
232+
}
233+
}) as LanguageServerManager['specs'],
234+
sessions: AVAILABLE_SESSIONS
235+
});
236+
const defaultOverrides =
237+
defaults.pyright.serverSettings[
238+
'python.analysis.diagnosticSeverityOverrides'
239+
];
240+
expect(defaultOverrides).toEqual({
241+
// `overrides.json`:
242+
// - should provide `reportGeneralTypeIssues` defined with fully dotted key
243+
reportGeneralTypeIssues: 'warning',
244+
// - should provide `reportPropertyTypeMismatch` defined with nesting on final level
245+
// `jupyter_server_config.py`:
246+
reportPropertyTypeMismatch: 'warning',
247+
// - should provide `reportFunctionMemberAccess` defined with fully dotted key
248+
reportFunctionMemberAccess: 'warning',
249+
// - should provide `reportUnusedImport` defined with nesting on final level
250+
reportUnusedImport: 'warning'
251+
// should NOT include `reportUnusedClass` default defined in server schema
252+
});
253+
});
254+
});
255+
6256
describe('#mergeByServer()', () => {
7257
it('prioritises user `priority` over the default', () => {
8258
const defaults = {

0 commit comments

Comments
 (0)