All files / src/services jsonHover.ts

86.67% Statements 65/75
78.33% Branches 47/60
88.89% Functions 8/9
87.32% Lines 62/71

Press n or j to go to the next uncovered block, b, p or k for the previous block.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128          1x     1x   1x           672x 343x 343x 343x     1x   14x 14x 14x     14x     14x 6x 6x 3x 3x           14x   14x 14x       14x     14x 14x               14x 14x 14x   14x 14x 14x 14x 33x 15x 15x 15x 5x 5x 1x 4x 4x   5x 5x 5x 4x         33x   14x 14x 2x   14x 14x 2x   14x   14x 5x 5x   5x   14x         1x       19x 19x 19x             5x     5x  
/*---------------------------------------------------------------------------------------------
 *  Copyright (c) Microsoft Corporation. All rights reserved.
 *  Licensed under the MIT License. See License.txt in the project root for license information.
 *--------------------------------------------------------------------------------------------*/
 
import * as Parser from '../parser/jsonParser';
import * as SchemaService from './jsonSchemaService';
import { JSONWorkerContribution } from '../jsonContributions';
import { TextDocument, PromiseConstructor, Thenable, Position, Range, Hover, MarkedString } from '../jsonLanguageTypes';
 
export class JSONHover {
 
	private schemaService: SchemaService.IJSONSchemaService;
	private contributions: JSONWorkerContribution[];
	private promise: PromiseConstructor;
 
	constructor(schemaService: SchemaService.IJSONSchemaService, contributions: JSONWorkerContribution[] = [], promiseConstructor: PromiseConstructor) {
		this.schemaService = schemaService;
		this.contributions = contributions;
		this.promise = promiseConstructor || Promise;
	}
 
	public doHover(document: TextDocument, position: Position, doc: Parser.JSONDocument): Thenable<Hover | null> {
 
		const offset = document.offsetAt(position);
		let node = doc.getNodeFromOffset(offset);
		Iif (!node || (node.type === 'object' || node.type === 'array') && offset > node.offset + 1 && offset < node.offset + node.length - 1) {
			return this.promise.resolve(null);
		}
		const hoverRangeNode = node;
 
		// use the property description when hovering over an object key
		if (node.type === 'string') {
			const parent = node.parent;
			if (parent && parent.type === 'property' && parent.keyNode === node) {
				node = parent.valueNode;
				Iif (!node) {
					return this.promise.resolve(null);
				}
			}
		}
 
		const hoverRange = Range.create(document.positionAt(hoverRangeNode.offset), document.positionAt(hoverRangeNode.offset + hoverRangeNode.length));
 
		var createHover = (contents: MarkedString[]) => {
			const result: Hover = {
				contents: contents,
				range: hoverRange
			};
			return result;
		};
 
		const location = Parser.getNodePath(node);
		for (let i = this.contributions.length - 1; i >= 0; i--) {
			const contribution = this.contributions[i];
			const promise = contribution.getInfoContribution(document.uri, location);
			if (promise) {
				return promise.then(htmlContent => createHover(htmlContent));
			}
		}
 
		return this.schemaService.getSchemaForResource(document.uri, doc).then((schema) => {
			Eif (schema && node) {
				const matchingSchemas = doc.getMatchingSchemas(schema.schema, node.offset);
 
				let title: string | undefined = undefined;
				let markdownDescription: string | undefined = undefined;
				let markdownEnumValueDescription: string | undefined = undefined, enumValue: string | undefined = undefined;
				matchingSchemas.every((s) => {
					if (s.node === node && !s.inverted && s.schema) {
						title = title || s.schema.title;
						markdownDescription = markdownDescription || s.schema.markdownDescription || toMarkdown(s.schema.description);
						if (s.schema.enum) {
							const idx = s.schema.enum.indexOf(Parser.getNodeValue(node));
							if (s.schema.markdownEnumDescriptions) {
								markdownEnumValueDescription = s.schema.markdownEnumDescriptions[idx];
							} else Eif (s.schema.enumDescriptions) {
								markdownEnumValueDescription = toMarkdown(s.schema.enumDescriptions[idx]);
							}
							Eif (markdownEnumValueDescription) {
								enumValue = s.schema.enum[idx];
								if (typeof enumValue !== 'string') {
									enumValue = JSON.stringify(enumValue);
								}
							}
						}
					}
					return true;
				});
				let result = '';
				if (title) {
					result = toMarkdown(title);
				}
				Eif (markdownDescription) {
					if (result.length > 0) {
						result += "\n\n";
					}
					result += markdownDescription;
				}
				if (markdownEnumValueDescription) {
					Eif (result.length > 0) {
						result += "\n\n";
					}
					result += `\`${toMarkdownCodeBlock(enumValue!)}\`: ${markdownEnumValueDescription}`;
				}
				return createHover([result]);
			}
			return null;
		});
	}
}
function toMarkdown(plain: string): string;
function toMarkdown(plain: string | undefined): string | undefined;
function toMarkdown(plain: string | undefined): string | undefined {
	Eif (plain) {
		const res = plain.replace(/([^\n\r])(\r?\n)([^\n\r])/gm, '$1\n\n$3'); // single new lines to \n\n (Markdown paragraph)
		return res.replace(/[\\`*_{}[\]()#+\-.!]/g, "\\$&"); // escape markdown syntax tokens: http://daringfireball.net/projects/markdown/syntax#backslash
	}
	return undefined;
}
 
function toMarkdownCodeBlock(content: string) {
	// see https://daringfireball.net/projects/markdown/syntax#precode
	Iif (content.indexOf('`') !== -1) {
		return '`` ' + content + ' ``';
	}
	return content;
}