Skip to content
Open
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
1 change: 1 addition & 0 deletions src/jsonLanguageService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,7 @@ export function getLanguageService(params: LanguageServiceParams): LanguageServi
jsonSchemaService.clearExternalSchemas();
settings.schemas?.forEach(jsonSchemaService.registerExternalSchema.bind(jsonSchemaService));
jsonValidation.configure(settings);
jsonHover.configure(settings);
},
resetSchema: (uri: string) => jsonSchemaService.onResourceChange(uri),
doValidation: jsonValidation.doValidation.bind(jsonValidation),
Expand Down
11 changes: 11 additions & 0 deletions src/jsonLanguageTypes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -138,6 +138,17 @@ export interface LanguageSettings {
* A list of known schemas and/or associations of schemas to file names.
*/
schemas?: SchemaConfiguration[];

/**
* Settings for hover information.
*/
hover?: {
/**
* If set to true, the default value defined in the schema will be shown in hover information.
* Defaults to false.
*/
showDefaultValue?: boolean;
};
}

export type SeverityLevel = 'error' | 'warning' | 'ignore';
Expand Down
13 changes: 12 additions & 1 deletion src/services/jsonHover.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,18 +6,24 @@
import * as Parser from '../parser/jsonParser.js';
import * as SchemaService from './jsonSchemaService.js';
import { JSONWorkerContribution } from '../jsonContributions.js';
import { TextDocument, PromiseConstructor, Position, Range, Hover, MarkedString } from '../jsonLanguageTypes.js';
import { TextDocument, PromiseConstructor, Position, Range, Hover, MarkedString, LanguageSettings } from '../jsonLanguageTypes.js';

export class JSONHover {

private schemaService: SchemaService.IJSONSchemaService;
private contributions: JSONWorkerContribution[];
private promise: PromiseConstructor;
private showDefaultValue: boolean;

constructor(schemaService: SchemaService.IJSONSchemaService, contributions: JSONWorkerContribution[] = [], promiseConstructor: PromiseConstructor) {
this.schemaService = schemaService;
this.contributions = contributions;
this.promise = promiseConstructor || Promise;
this.showDefaultValue = false;
}

public configure(settings: LanguageSettings): void {
this.showDefaultValue = settings.hover?.showDefaultValue === true;
}

public doHover(document: TextDocument, position: Position, doc: Parser.JSONDocument): PromiseLike<Hover | null> {
Expand Down Expand Up @@ -67,11 +73,13 @@ export class JSONHover {
let title: string | undefined = undefined;
let markdownDescription: string | undefined = undefined;
let markdownEnumValueDescription: string | undefined = undefined, enumValue: string | undefined = undefined;
let defaultValue: any = undefined;

const matchingSchemas = doc.getMatchingSchemas(schema.schema, node.offset).filter((s) => s.node === node && !s.inverted).map((s) => s.schema);
for (const schema of matchingSchemas) {
title = title || schema.title;
markdownDescription = markdownDescription || schema.markdownDescription || toMarkdown(schema.description);
defaultValue = schema.default;
if (schema.enum) {
const idx = schema.enum.indexOf(Parser.getNodeValue(node));
if (schema.markdownEnumDescriptions) {
Expand Down Expand Up @@ -104,6 +112,9 @@ export class JSONHover {
}
result += `\`${toMarkdownCodeBlock(enumValue!)}\`: ${markdownEnumValueDescription}`;
}
if (this.showDefaultValue && defaultValue !== undefined) {
result += `\n\n---\n\nDefault value: \`${JSON.stringify(defaultValue)}\``;
}
return createHover([result]);
});
}
Expand Down
65 changes: 60 additions & 5 deletions src/test/hover.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,19 +6,19 @@
import * as assert from 'assert';
import { suite, test } from 'node:test';

import { Hover, Position, TextDocument, getLanguageService, JSONSchema, LanguageServiceParams } from '../jsonLanguageService.js';
import { Hover, Position, TextDocument, getLanguageService, JSONSchema, LanguageServiceParams, LanguageSettings } from '../jsonLanguageService.js';

suite('JSON Hover', () => {

async function testComputeInfo(value: string, schema: JSONSchema, position: Position, serviceParams?: LanguageServiceParams): Promise<Hover> {
async function testComputeInfo(value: string, schema: JSONSchema, position: Position, languageSettings?: Omit<LanguageSettings, 'schemas'>, serviceParams?: LanguageServiceParams): Promise<Hover> {
const uri = 'test://test.json';
const schemaUri = "http://myschemastore/test1";

if (!serviceParams) {
serviceParams = { schemaRequestService: requestService };
}
const service = getLanguageService(serviceParams);
service.configure({ schemas: [{ fileMatch: ["*.json"], uri: schemaUri, schema }] });
service.configure({ schemas: [{ fileMatch: ["*.json"], uri: schemaUri, schema }], ...languageSettings });


const document = TextDocument.create(uri, 'json', 0, value);
Expand Down Expand Up @@ -169,6 +169,61 @@ suite('JSON Hover', () => {
});
});

test('Default value', async function () {
const schema: JSONSchema = {
type: 'object',
properties: {
'prop1': {
type: 'string',
description: 'A string property',
default: 'myDefault'
},
'prop2': {
type: 'number',
default: 42
},
'prop3': {
type: 'object',
default: { key: 'value' }
},
'prop4': {
type: 'string',
description: 'A string property without default'
}
}
};

// showDefaultValue not configured (defaults to false) - default value should NOT appear
await testComputeInfo('{ "prop1": "val" }', schema, { line: 0, character: 12 }).then(result => {
assert.deepEqual(result.contents, ['A string property']);
});

// showDefaultValue: false - default value should NOT appear
await testComputeInfo('{ "prop1": "val" }', schema, { line: 0, character: 12 }, { hover: { showDefaultValue: false } }).then(result => {
assert.deepEqual(result.contents, ['A string property']);
});

// showDefaultValue: true - default value should appear
await testComputeInfo('{ "prop1": "val" }', schema, { line: 0, character: 12 }, { hover: { showDefaultValue: true } }).then(result => {
assert.deepEqual(result.contents, ['A string property\n\n---\n\nDefault value: `"myDefault"`']);
});

// showDefaultValue: true, numeric default
await testComputeInfo('{ "prop2": 1 }', schema, { line: 0, character: 11 }, { hover: { showDefaultValue: true } }).then(result => {
assert.deepEqual(result.contents, ['\n\n---\n\nDefault value: `42`']);
});

// showDefaultValue: true, object default
await testComputeInfo('{ "prop3": {} }', schema, { line: 0, character: 11 }, { hover: { showDefaultValue: true } }).then(result => {
assert.deepEqual(result.contents, ['\n\n---\n\nDefault value: `{"key":"value"}`']);
});

// showDefaultValue: true, but schema has no default - default value should NOT appear
await testComputeInfo('{ "prop4": "val" }', schema, { line: 0, character: 12 }, { hover: { showDefaultValue: true } }).then(result => {
assert.deepEqual(result.contents, ['A string property without default']);
});
});

test('Contribution descriptions', async function () {
const serviceParams: LanguageServiceParams = {
contributions: [{
Expand All @@ -187,10 +242,10 @@ suite('JSON Hover', () => {
}]
};

let result = await testComputeInfo('{ "prop1": [ 1, false ] }', {}, { line: 0, character: 3 }, serviceParams);
let result = await testComputeInfo('{ "prop1": [ 1, false ] }', {}, { line: 0, character: 3 }, undefined, serviceParams);
assert.deepEqual(result.contents, ['test://test.json: prop1']);

result = await testComputeInfo('{ "prop1": [ 1, false ] }', {}, { line: 0, character: 13 }, serviceParams);
result = await testComputeInfo('{ "prop1": [ 1, false ] }', {}, { line: 0, character: 13 }, undefined, serviceParams);
assert.deepEqual(result.contents, ['test://test.json: prop1/0']);

});
Expand Down