Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

refactor(language service): Clean up the language service interface #831

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 5 additions & 5 deletions src/languageserver/handlers/schemaSelectionHandlers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,13 +14,13 @@ import { JSONSchemaDescription, JSONSchemaDescriptionExt, SchemaSelectionRequest
export class JSONSchemaSelection {
constructor(
private readonly schemaService: YAMLSchemaService,
private readonly yamlSettings: SettingsState,
private readonly connection: Connection
private readonly yamlSettings?: SettingsState,
private readonly connection?: Connection
) {
this.connection.onRequest(SchemaSelectionRequests.getSchema, (fileUri) => {
this.connection?.onRequest(SchemaSelectionRequests.getSchema, (fileUri) => {
return this.getSchemas(fileUri);
});
this.connection.onRequest(SchemaSelectionRequests.getAllSchemas, (fileUri) => {
this.connection?.onRequest(SchemaSelectionRequests.getAllSchemas, (fileUri) => {
return this.getAllSchemas(fileUri);
});
}
Expand All @@ -38,7 +38,7 @@ export class JSONSchemaSelection {
}

private async getSchemasForFile(docUri: string): Promise<Map<string, JSONSchema>> {
const document = this.yamlSettings.documents.get(docUri);
const document = this.yamlSettings?.documents.get(docUri);
const schemas = new Map<string, JSONSchema>();
if (!document) {
return schemas;
Expand Down
6 changes: 3 additions & 3 deletions src/languageservice/services/documentSymbols.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ import { convertErrorToTelemetryMsg } from '../utils/objects';
export class YAMLDocumentSymbols {
private jsonDocumentSymbols;

constructor(schemaService: YAMLSchemaService, private readonly telemetry: Telemetry) {
constructor(schemaService: YAMLSchemaService, private readonly telemetry?: Telemetry) {
this.jsonDocumentSymbols = new JSONDocumentSymbols(schemaService);

// override 'getKeyLabel' to handle complex mapping
Expand Down Expand Up @@ -54,7 +54,7 @@ export class YAMLDocumentSymbols {
}
}
} catch (err) {
this.telemetry.sendError('yaml.documentSymbols.error', { error: convertErrorToTelemetryMsg(err) });
this.telemetry?.sendError('yaml.documentSymbols.error', { error: convertErrorToTelemetryMsg(err) });
}
return results;
}
Expand All @@ -76,7 +76,7 @@ export class YAMLDocumentSymbols {
}
}
} catch (err) {
this.telemetry.sendError('yaml.hierarchicalDocumentSymbols.error', { error: convertErrorToTelemetryMsg(err) });
this.telemetry?.sendError('yaml.hierarchicalDocumentSymbols.error', { error: convertErrorToTelemetryMsg(err) });
}

return results;
Expand Down
4 changes: 2 additions & 2 deletions src/languageservice/services/yamlCodeLens.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ import { convertErrorToTelemetryMsg } from '../utils/objects';
import { getSchemaTitle } from '../utils/schemaUtils';

export class YamlCodeLens {
constructor(private schemaService: YAMLSchemaService, private readonly telemetry: Telemetry) {}
constructor(private schemaService: YAMLSchemaService, private readonly telemetry?: Telemetry) {}

async getCodeLens(document: TextDocument): Promise<CodeLens[]> {
const result = [];
Expand All @@ -39,7 +39,7 @@ export class YamlCodeLens {
result.push(lens);
}
} catch (err) {
this.telemetry.sendError('yaml.codeLens.error', { error: convertErrorToTelemetryMsg(err) });
this.telemetry?.sendError('yaml.codeLens.error', { error: convertErrorToTelemetryMsg(err) });
}

return result;
Expand Down
6 changes: 3 additions & 3 deletions src/languageservice/services/yamlCompletion.ts
Original file line number Diff line number Diff line change
Expand Up @@ -81,7 +81,7 @@ export class YamlCompletion {
private schemaService: YAMLSchemaService,
private clientCapabilities: ClientCapabilities = {},
private yamlDocument: YamlDocuments,
private readonly telemetry: Telemetry
private readonly telemetry?: Telemetry
) {}

configure(languageSettings: LanguageSettings): void {
Expand Down Expand Up @@ -279,7 +279,7 @@ export class YamlCompletion {
}
},
error: (message: string) => {
this.telemetry.sendError('yaml.completion.error', { error: convertErrorToTelemetryMsg(message) });
this.telemetry?.sendError('yaml.completion.error', { error: convertErrorToTelemetryMsg(message) });
},
log: (message: string) => {
console.log(message);
Expand Down Expand Up @@ -530,7 +530,7 @@ export class YamlCompletion {
const types: { [type: string]: boolean } = {};
this.getValueCompletions(schema, currentDoc, node, offset, document, collector, types, doComplete);
} catch (err) {
this.telemetry.sendError('yaml.completion.error', { error: convertErrorToTelemetryMsg(err) });
this.telemetry?.sendError('yaml.completion.error', { error: convertErrorToTelemetryMsg(err) });
}

this.finalizeParentCompletion(result);
Expand Down
4 changes: 2 additions & 2 deletions src/languageservice/services/yamlDefinition.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ import { convertErrorToTelemetryMsg } from '../utils/objects';
import { TextBuffer } from '../utils/textBuffer';

export class YamlDefinition {
constructor(private readonly telemetry: Telemetry) {}
constructor(private readonly telemetry?: Telemetry) {}

getDefinition(document: TextDocument, params: DefinitionParams): DefinitionLink[] | undefined {
try {
Expand All @@ -33,7 +33,7 @@ export class YamlDefinition {
}
}
} catch (err) {
this.telemetry.sendError('yaml.definition.error', { error: convertErrorToTelemetryMsg(err) });
this.telemetry?.sendError('yaml.definition.error', { error: convertErrorToTelemetryMsg(err) });
}

return undefined;
Expand Down
12 changes: 8 additions & 4 deletions src/languageservice/services/yamlHover.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ export class YAMLHover {
private indentation: string;
private schemaService: YAMLSchemaService;

constructor(schemaService: YAMLSchemaService, private readonly telemetry: Telemetry) {
constructor(schemaService: YAMLSchemaService, private readonly telemetry?: Telemetry) {
this.shouldHover = true;
this.schemaService = schemaService;
}
Expand Down Expand Up @@ -55,7 +55,7 @@ export class YAMLHover {
currentDoc.currentDocIndex = currentDocIndex;
return this.getHover(document, position, currentDoc);
} catch (error) {
this.telemetry.sendError('yaml.hover.error', { error: convertErrorToTelemetryMsg(error) });
this.telemetry?.sendError('yaml.hover.error', { error: convertErrorToTelemetryMsg(error) });
}
}

Expand Down Expand Up @@ -88,10 +88,14 @@ export class YAMLHover {
);

const createHover = (contents: string): Hover => {
const regex = new RegExp(this.indentation, 'g');
if (this.indentation !== undefined) {
const indentationMatchRegex = new RegExp(this.indentation, 'g');
contents = contents.replace(indentationMatchRegex, '&emsp;');
}

const markupContent: MarkupContent = {
kind: MarkupKind.Markdown,
value: contents.replace(regex, '&emsp;'),
value: contents,
};
const result: Hover = {
contents: markupContent,
Expand Down
4 changes: 2 additions & 2 deletions src/languageservice/services/yamlLinks.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import { yamlDocumentsCache } from '../parser/yaml-documents';
import { convertErrorToTelemetryMsg } from '../utils/objects';

export class YamlLinks {
constructor(private readonly telemetry: Telemetry) {}
constructor(private readonly telemetry?: Telemetry) {}

findLinks(document: TextDocument): Promise<DocumentLink[]> {
try {
Expand All @@ -23,7 +23,7 @@ export class YamlLinks {
// Wait for all the promises to return and then flatten them into one DocumentLink array
return Promise.all(linkPromises).then((yamlLinkArray) => [].concat(...yamlLinkArray));
} catch (err) {
this.telemetry.sendError('yaml.documentLink.error', { error: convertErrorToTelemetryMsg(err) });
this.telemetry?.sendError('yaml.documentLink.error', { error: convertErrorToTelemetryMsg(err) });
}
}
}
4 changes: 2 additions & 2 deletions src/languageservice/services/yamlValidation.ts
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ export class YAMLValidation {

private MATCHES_MULTIPLE = 'Matches multiple schemas when only one must validate.';

constructor(schemaService: YAMLSchemaService, private readonly telemetry: Telemetry) {
constructor(schemaService: YAMLSchemaService, private readonly telemetry?: Telemetry) {
this.validationEnabled = true;
this.jsonValidation = new JSONValidation(schemaService, Promise);
}
Expand Down Expand Up @@ -108,7 +108,7 @@ export class YAMLValidation {
index++;
}
} catch (err) {
this.telemetry.sendError('yaml.validation.error', { error: convertErrorToTelemetryMsg(err) });
this.telemetry?.sendError('yaml.validation.error', { error: convertErrorToTelemetryMsg(err) });
}

let previousErr: Diagnostic;
Expand Down
40 changes: 18 additions & 22 deletions src/languageservice/yamlLanguageService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -43,10 +43,8 @@ import { TextDocument } from 'vscode-languageserver-textdocument';
import { getFoldingRanges } from './services/yamlFolding';
import { FoldingRangesContext, SchemaVersions } from './yamlTypes';
import { YamlCodeActions } from './services/yamlCodeActions';
import { commandExecutor } from '../languageserver/commandExecutor';
import { doDocumentOnTypeFormatting } from './services/yamlOnTypeFormatting';
import { YamlCodeLens } from './services/yamlCodeLens';
import { registerCommands } from './services/yamlCommands';
import { Telemetry } from './telemetry';
import { YamlVersion } from './parser/yamlParser07';
import { YamlCompletion } from './services/yamlCompletion';
Expand Down Expand Up @@ -180,29 +178,27 @@ export interface LanguageService {
resolveCodeLens(param: CodeLens): Thenable<CodeLens> | CodeLens;
}

export function getLanguageService(
schemaRequestService: SchemaRequestService,
workspaceContext: WorkspaceContextService,
connection: Connection,
telemetry: Telemetry,
yamlSettings: SettingsState,
clientCapabilities?: ClientCapabilities
): LanguageService {
const schemaService = new YAMLSchemaService(schemaRequestService, workspaceContext);
const completer = new YamlCompletion(schemaService, clientCapabilities, yamlDocumentsCache, telemetry);
const hover = new YAMLHover(schemaService, telemetry);
const yamlDocumentSymbols = new YAMLDocumentSymbols(schemaService, telemetry);
const yamlValidation = new YAMLValidation(schemaService, telemetry);
export function getLanguageService(params: {
schemaRequestService: SchemaRequestService;
workspaceContext: WorkspaceContextService;
connection?: Connection;
telemetry?: Telemetry;
yamlSettings?: SettingsState;
clientCapabilities?: ClientCapabilities;
}): LanguageService {
const schemaService = new YAMLSchemaService(params.schemaRequestService, params.workspaceContext);
const completer = new YamlCompletion(schemaService, params.clientCapabilities, yamlDocumentsCache, params.telemetry);
const hover = new YAMLHover(schemaService, params.telemetry);
const yamlDocumentSymbols = new YAMLDocumentSymbols(schemaService, params.telemetry);
const yamlValidation = new YAMLValidation(schemaService, params.telemetry);
const formatter = new YAMLFormatter();
const yamlCodeActions = new YamlCodeActions(clientCapabilities);
const yamlCodeLens = new YamlCodeLens(schemaService, telemetry);
const yamlLinks = new YamlLinks(telemetry);
const yamlDefinition = new YamlDefinition(telemetry);
const yamlCodeActions = new YamlCodeActions(params.clientCapabilities);
const yamlCodeLens = new YamlCodeLens(schemaService, params.telemetry);
const yamlLinks = new YamlLinks(params.telemetry);
const yamlDefinition = new YamlDefinition(params.telemetry);

new JSONSchemaSelection(schemaService, yamlSettings, connection);
new JSONSchemaSelection(schemaService, params.yamlSettings, params.connection);

// register all commands
registerCommands(commandExecutor, connection);
return {
configure: (settings) => {
schemaService.clearExternalSchemas();
Expand Down
18 changes: 10 additions & 8 deletions src/yamlServerInit.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import { YamlCommands } from './commands';
import { WorkspaceHandlers } from './languageserver/handlers/workspaceHandlers';
import { commandExecutor } from './languageserver/commandExecutor';
import { Telemetry } from './languageservice/telemetry';
import { registerCommands } from './languageservice/services/yamlCommands';

export class YAMLServerInit {
languageService: LanguageService;
Expand Down Expand Up @@ -57,14 +58,14 @@ export class YAMLServerInit {
// public for test setup
connectionInitialized(params: InitializeParams): InitializeResult {
this.yamlSettings.capabilities = params.capabilities;
this.languageService = getCustomLanguageService(
this.schemaRequestService,
this.workspaceContext,
this.connection,
this.telemetry,
this.yamlSettings,
params.capabilities
);
this.languageService = getCustomLanguageService({
schemaRequestService: this.schemaRequestService,
workspaceContext: this.workspaceContext,
connection: this.connection,
yamlSettings: this.yamlSettings,
telemetry: this.telemetry,
clientCapabilities: params.capabilities,
});

// Only try to parse the workspace root if its not null. Otherwise initialize will fail
if (params.rootUri) {
Expand Down Expand Up @@ -95,6 +96,7 @@ export class YAMLServerInit {
this.yamlSettings.capabilities.workspace.didChangeWatchedFiles.dynamicRegistration
);
this.registerHandlers();
registerCommands(commandExecutor, this.connection);

return {
capabilities: {
Expand Down
97 changes: 97 additions & 0 deletions test/yamlLanguageService.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Red Hat, Inc. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { assert } from 'chai';
import { Position, TextDocument } from 'vscode-languageserver-textdocument';
import { getLanguageService, LanguageService, SchemaRequestService, WorkspaceContextService } from '../src';
import { workspaceContext } from '../src/languageservice/services/schemaRequestHandler';
import { caretPosition, setupSchemaIDTextDocument } from './utils/testHelper';

/**
* Builds a simple schema request service
* @param contentMap Mapping of a schema uri to the schema content
*/
function schemaRequestServiceBuilder(contentMap: { [uri: string]: string }): SchemaRequestService {
return async (uri: string) => {
return contentMap[uri];
};
}

describe('getLanguageService()', () => {
it('successfully creates an instance without optional arguments', () => {
getLanguageService({
schemaRequestService: {} as SchemaRequestService,
workspaceContext: {} as WorkspaceContextService,
});
});

describe('minimal language service hover happy path', () => {
const schemaUri = 'my.schema.uri';
const schemaContentMap: { [uri: string]: string } = {};

let schemaRequestService: SchemaRequestService;
let textDocument: TextDocument;
let hoverPosition: Position; // Position the 'mouse' is hovering on the content
let minimalYamlService: LanguageService;

before(async () => {
// Setup object that resolves schema content
schemaContentMap[schemaUri] = `
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"type": "object",
"properties": {
"firstName": {
"type": "string",
"description": "The person's first name."
}
}
}
`;
schemaRequestService = schemaRequestServiceBuilder(schemaContentMap);

// Setup the document and where the hover is on it
const contentWithHoverPosition = 'fi|r|stName: "Nikolas"';
const { content, position: offset } = caretPosition(contentWithHoverPosition);
textDocument = setupSchemaIDTextDocument(content);
hoverPosition = textDocument.positionAt(offset);

// Setup minimal language service + indicate to provide hover functionality
minimalYamlService = getLanguageService({
schemaRequestService: schemaRequestService,
workspaceContext: workspaceContext,
});
minimalYamlService.configure({
hover: true,
schemas: [
{
fileMatch: [textDocument.uri],
uri: schemaUri,
},
],
});
});

it('successfully creates an instance without optional arguments', async () => {
const result = await minimalYamlService.doHover(textDocument, hoverPosition);

assert.deepEqual(result, {
contents: {
kind: 'markdown',
value: "The person's first name\\.\n\nSource: [my.schema.uri](my.schema.uri)",
},
range: {
start: {
line: 0,
character: 0,
},
end: {
line: 0,
character: 9,
},
},
});
});
});
});