Skip to content

Commit 30bc498

Browse files
committed
#22: Refactor variable names for clarity and consistency.
1 parent 7befd0c commit 30bc498

File tree

2 files changed

+151
-49
lines changed

2 files changed

+151
-49
lines changed

src/server.ts

Lines changed: 33 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -44,27 +44,27 @@ const documents: TextDocuments<TextDocument> = new TextDocuments(TextDocument);
4444
/**
4545
* Client capabilities manager, to define what is and is not able to do.
4646
*/
47-
const clientCapabilities = new Capabilities();
47+
const capabilities = new Capabilities();
4848

4949
/**
5050
* Provider of the semantic highlighting capability of the language server.
5151
*/
52-
let semanticTokensProvider: SemanticTokensProvider;
52+
let provider: SemanticTokensProvider;
5353

5454
/**
5555
* Defines procedures to be executed on the initialization process
5656
* of the connection with the client
5757
*/
5858
connection.onInitialize((params: InitializeParams) => {
59-
const capabilities = params.capabilities;
60-
clientCapabilities.initialize(capabilities);
61-
semanticTokensProvider = new SemanticTokensProvider(params.capabilities.textDocument!.semanticTokens!);
59+
const caps = params.capabilities;
60+
capabilities.initialize(caps);
61+
provider = new SemanticTokensProvider(params.capabilities.textDocument!.semanticTokens!);
6262
const result: InitializeResult = {
6363
capabilities: {
6464
textDocumentSync: TextDocumentSyncKind.Incremental
6565
}
6666
};
67-
if (clientCapabilities.workspace) {
67+
if (capabilities.workspace) {
6868
result.capabilities.workspace = {
6969
workspaceFolders: {
7070
supported: true
@@ -82,24 +82,24 @@ connection.onInitialize((params: InitializeParams) => {
8282
* Configuration, Workspace Folder and Document Semantic Tokens
8383
*/
8484
connection.onInitialized(() => {
85-
if (clientCapabilities.configuration) {
85+
if (capabilities.configuration) {
8686
connection.client.register(DidChangeConfigurationNotification.type, void 0);
8787
}
88-
if (clientCapabilities.workspace) {
88+
if (capabilities.workspace) {
8989
connection.workspace.onDidChangeWorkspaceFolders(_event => {
9090
connection.console.log("Workspace folder change event received");
9191
});
9292
}
93-
if (clientCapabilities.tokens) {
94-
const registrationOptions: SemanticTokensRegistrationOptions = {
93+
if (capabilities.tokens) {
94+
const options: SemanticTokensRegistrationOptions = {
9595
documentSelector: null,
96-
legend: semanticTokensProvider.legend,
96+
legend: provider.legend,
9797
range: false,
9898
full: {
9999
delta: true
100100
}
101101
};
102-
connection.client.register(SemanticTokensRegistrationType.type, registrationOptions);
102+
connection.client.register(SemanticTokensRegistrationType.type, options);
103103
}
104104
});
105105

@@ -111,29 +111,29 @@ const defaultSettings: DefaultSettings = { limit: 1000 };
111111
/**
112112
* The global settings, used when the `workspace/configuration` request is not supported by the client.
113113
*/
114-
let globalSettings: DefaultSettings = defaultSettings;
114+
let settings: DefaultSettings = defaultSettings;
115115

116116
/**
117117
* Cache for the settings of all open documents
118118
*/
119-
const documentSettings: Map<string, Thenable<DefaultSettings>> = new Map();
119+
const cache: Map<string, Thenable<DefaultSettings>> = new Map();
120120

121121
/**
122122
* Retrieves the settings for a document
123123
* @param resource - String for the scheme of the document for which to retrieve its settings
124124
* @returns - A Promise for the settings of the document requested
125125
*/
126126
function getDocumentSettings(resource: string): Thenable<DefaultSettings> {
127-
if (!clientCapabilities.configuration) {
128-
return Promise.resolve(globalSettings);
127+
if (!capabilities.configuration) {
128+
return Promise.resolve(settings);
129129
}
130-
let result = documentSettings.get(resource);
130+
let result = cache.get(resource);
131131
if (!result) {
132132
result = connection.workspace.getConfiguration({
133133
scopeUri: resource,
134134
section: "languageServerExample"
135-
});
136-
documentSettings.set(resource, result);
135+
}).then(config => (config && typeof config === "object") ? config : defaultSettings);
136+
cache.set(resource, result);
137137
}
138138
return result;
139139
}
@@ -145,13 +145,15 @@ function getDocumentSettings(resource: string): Thenable<DefaultSettings> {
145145
* @param textDocument - Document for which to perform the validation procedure
146146
* @returns {Promise<void>}
147147
*/
148-
async function validateTextDocument(textDocument: TextDocument): Promise<void> {
149-
const settings = await getDocumentSettings(textDocument.uri);
150-
const text = textDocument.getText();
148+
async function validateTextDocument(document: TextDocument): Promise<void> {
149+
const config = await getDocumentSettings(document.uri);
150+
const text = document.getText();
151151
const diagnostics: Diagnostic[] = [];
152152
const errors = getParserErrors(text);
153+
const effective = config || defaultSettings;
154+
const limit = effective.limit;
153155
errors.forEach((error, index) => {
154-
if (settings?.limit !== null && index >= settings.limit) {
156+
if (limit !== null && index >= limit) {
155157
return;
156158
}
157159
const diagnostic: Diagnostic = {
@@ -165,20 +167,19 @@ async function validateTextDocument(textDocument: TextDocument): Promise<void> {
165167
};
166168
diagnostics.push(diagnostic);
167169
});
168-
connection.sendDiagnostics({ uri: textDocument.uri, diagnostics });
170+
connection.sendDiagnostics({ uri: document.uri, diagnostics });
169171
}
170172

171173
/**
172174
* Resets all cached document settings and revalidates all open text
173175
* documents with there is a change in the configuration of the client.
174176
*/
175177
connection.onDidChangeConfiguration(change => {
176-
if (clientCapabilities.configuration) {
177-
documentSettings.clear();
178+
if (capabilities.configuration) {
179+
cache.clear();
178180
} else {
179-
globalSettings = <DefaultSettings>(
180-
(change.settings.languageServerExample || defaultSettings)
181-
);
181+
const config = change.settings.languageServerExample;
182+
settings = (config && typeof config === "object") ? config : defaultSettings;
182183
}
183184
documents.all().forEach(validateTextDocument);
184185
});
@@ -187,7 +188,7 @@ connection.onDidChangeConfiguration(change => {
187188
* Clears the settings cache for a closed document, once it is closed
188189
*/
189190
documents.onDidClose(e => {
190-
documentSettings.delete(e.document.uri);
191+
cache.delete(e.document.uri);
191192
});
192193

193194
/**
@@ -214,7 +215,7 @@ connection.languages.semanticTokens.on(params => {
214215
if (!document) {
215216
return { data: [] };
216217
}
217-
return semanticTokensProvider.provideSemanticTokens(document);
218+
return provider.provideSemanticTokens(document);
218219
});
219220

220221
/**
@@ -226,7 +227,7 @@ connection.languages.semanticTokens.onDelta(params => {
226227
if (!document) {
227228
return { data: [] };
228229
}
229-
return semanticTokensProvider.provideDeltas(document);
230+
return provider.provideDeltas(document);
230231
});
231232

232233
/**

src/test/integration.test.ts

Lines changed: 118 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -10,33 +10,33 @@ import * as fs from "fs";
1010
*/
1111
describe("LSP Server Integration", () => {
1212
let server: ChildProcess;
13-
let messageHandlers: Map<number, (response: any) => void> = new Map();
14-
let notificationHandlers: ((notification: any) => void)[] = [];
15-
let messageId = 0;
13+
let handlers: Map<number, (response: any) => void> = new Map();
14+
let notifications: ((notification: any) => void)[] = [];
15+
let id = 0;
1616
let buffer = Buffer.alloc(0);
1717

1818
/**
1919
* Sends a request to the LSP server
2020
*/
2121
function sendRequest(method: string, params: any): Promise<any> {
2222
return new Promise((resolve, reject) => {
23-
const id = ++messageId;
23+
const current = ++id;
2424
const request = {
2525
jsonrpc: "2.0",
26-
id: id,
26+
id: current,
2727
method: method,
2828
params: params
2929
};
3030
const content = JSON.stringify(request);
3131
const header = `Content-Length: ${Buffer.byteLength(content)}\r\n\r\n`;
3232
const message = header + content;
3333
const timeout = setTimeout(() => {
34-
messageHandlers.delete(id);
34+
handlers.delete(current);
3535
reject(new Error(`Request ${method} timed out`));
3636
}, 10000);
37-
messageHandlers.set(id, (response) => {
37+
handlers.set(current, (response) => {
3838
clearTimeout(timeout);
39-
messageHandlers.delete(id);
39+
handlers.delete(current);
4040
if (response.error) {
4141
reject(new Error(response.error.message));
4242
} else {
@@ -85,10 +85,10 @@ describe("LSP Server Integration", () => {
8585

8686
try {
8787
const message = JSON.parse(content);
88-
if (message.id !== undefined && messageHandlers.has(message.id)) {
89-
messageHandlers.get(message.id)!(message);
88+
if (message.id !== undefined && handlers.has(message.id)) {
89+
handlers.get(message.id)!(message);
9090
} else if (message.method) {
91-
notificationHandlers.forEach(handler => handler(message));
91+
notifications.forEach(handler => handler(message));
9292
}
9393
} catch (e) {
9494
console.error("Failed to parse message:", e);
@@ -97,8 +97,8 @@ describe("LSP Server Integration", () => {
9797
}
9898

9999
beforeEach((done) => {
100-
messageHandlers.clear();
101-
notificationHandlers = [];
100+
handlers.clear();
101+
notifications = [];
102102
buffer = Buffer.alloc(0);
103103
const serverPath = path.join(__dirname, "..", "..", "out", "server.js");
104104

@@ -204,11 +204,11 @@ describe("LSP Server Integration", () => {
204204
const handler = (message: any) => {
205205
if (message.method === "textDocument/publishDiagnostics" &&
206206
message.params.uri === uri) {
207-
notificationHandlers = notificationHandlers.filter(h => h !== handler);
207+
notifications = notifications.filter(h => h !== handler);
208208
resolve(message.params.diagnostics);
209209
}
210210
};
211-
notificationHandlers.push(handler);
211+
notifications.push(handler);
212212
});
213213
sendNotification("textDocument/didOpen", {
214214
textDocument: {
@@ -254,11 +254,11 @@ describe("LSP Server Integration", () => {
254254
if (message.method === "textDocument/publishDiagnostics" &&
255255
message.params.uri === uri &&
256256
message.params.diagnostics.length > 0) {
257-
notificationHandlers = notificationHandlers.filter(h => h !== handler);
257+
notifications = notifications.filter(h => h !== handler);
258258
resolve(message.params.diagnostics);
259259
}
260260
};
261-
notificationHandlers.push(handler);
261+
notifications.push(handler);
262262
});
263263

264264
sendNotification("textDocument/didChange", {
@@ -275,4 +275,105 @@ describe("LSP Server Integration", () => {
275275
expect(Array.isArray(diagnostics)).toBeTruthy();
276276
expect((diagnostics as any[]).length).toBeGreaterThan(0);
277277
}, 15000);
278+
279+
test("Server handles null settings during validation without crashing", async () => {
280+
await sendRequest("initialize", {
281+
processId: process.pid,
282+
rootUri: null,
283+
capabilities: {
284+
workspace: {
285+
configuration: false
286+
},
287+
textDocument: {
288+
semanticTokens: {
289+
tokenTypes: [],
290+
tokenModifiers: []
291+
}
292+
}
293+
}
294+
});
295+
sendNotification("initialized", {});
296+
const uri = "file:///nullsettings.eo";
297+
const content = "-- invalid syntax to trigger validation --\n".repeat(1100);
298+
299+
const diagnosticsPromise = new Promise((resolve) => {
300+
const handler = (message: any) => {
301+
if (message.method === "textDocument/publishDiagnostics" &&
302+
message.params.uri === uri) {
303+
notifications = notifications.filter(h => h !== handler);
304+
resolve(message.params.diagnostics);
305+
}
306+
};
307+
notifications.push(handler);
308+
});
309+
310+
sendNotification("textDocument/didOpen", {
311+
textDocument: {
312+
uri: uri,
313+
languageId: "eo",
314+
version: 1,
315+
text: content
316+
}
317+
});
318+
319+
const diagnostics = await diagnosticsPromise;
320+
expect(Array.isArray(diagnostics)).toBeTruthy();
321+
}, 15000);
322+
323+
test("Server handles malformed workspace configuration without crashing", async () => {
324+
await sendRequest("initialize", {
325+
processId: process.pid,
326+
rootUri: null,
327+
capabilities: {
328+
workspace: {
329+
configuration: true
330+
},
331+
textDocument: {
332+
semanticTokens: {
333+
tokenTypes: [],
334+
tokenModifiers: []
335+
}
336+
}
337+
}
338+
});
339+
sendNotification("initialized", {});
340+
341+
sendNotification("workspace/didChangeConfiguration", {
342+
settings: {
343+
languageServerExample: null
344+
}
345+
});
346+
347+
const uri = "file:///malformed.eo";
348+
const content = "-- invalid syntax --";
349+
350+
const diagnosticsPromise = new Promise((resolve, reject) => {
351+
const timeout = setTimeout(() => {
352+
notifications = notifications.filter(h => h !== handler);
353+
resolve([]);
354+
}, 5000);
355+
356+
const handler = (message: any) => {
357+
if (message.method === "textDocument/publishDiagnostics" &&
358+
message.params.uri === uri) {
359+
clearTimeout(timeout);
360+
notifications = notifications.filter(h => h !== handler);
361+
resolve(message.params.diagnostics);
362+
}
363+
};
364+
notifications.push(handler);
365+
});
366+
367+
sendNotification("textDocument/didOpen", {
368+
textDocument: {
369+
uri: uri,
370+
languageId: "eo",
371+
version: 1,
372+
text: content
373+
}
374+
});
375+
376+
const diagnostics = await diagnosticsPromise;
377+
expect(Array.isArray(diagnostics)).toBeTruthy();
378+
}, 15000);
278379
});

0 commit comments

Comments
 (0)