44 * Licensed under the MIT License. See License.txt in the project root for license information.
55 *--------------------------------------------------------------------------------------------*/
66
7+ import * as l10n from '@vscode/l10n' ;
8+ import * as path from 'path' ;
9+ import { TextDocument } from 'vscode-languageserver-textdocument' ;
710import { Hover , MarkupContent , MarkupKind , Position , Range } from 'vscode-languageserver-types' ;
8- import { matchOffsetToDocument } from '../utils/arrUtils' ;
9- import { LanguageSettings } from '../yamlLanguageService' ;
10- import { YAMLSchemaService } from './yamlSchemaService' ;
11+ import { URI } from 'vscode-uri' ;
12+ import { isAlias , isMap , isSeq , Node , stringify as stringifyYAML } from 'yaml' ;
13+ import { ASTNode , ObjectASTNode , PropertyASTNode } from '../jsonASTTypes' ;
14+ import { JSONSchema } from '../jsonSchema' ;
1115import { setKubernetesParserOption } from '../parser/isKubernetes' ;
12- import { TextDocument } from 'vscode-languageserver-textdocument ' ;
16+ import { getNodeValue , IApplicableSchema } from '../parser/jsonParser07 ' ;
1317import { yamlDocumentsCache } from '../parser/yaml-documents' ;
1418import { SingleYAMLDocument } from '../parser/yamlParser07' ;
15- import { getNodeValue , IApplicableSchema } from '../parser/jsonParser07' ;
16- import { JSONSchema } from '../jsonSchema' ;
17- import { URI } from 'vscode-uri' ;
18- import * as path from 'path' ;
19- import * as l10n from '@vscode/l10n' ;
2019import { Telemetry } from '../telemetry' ;
21- import { ASTNode } from 'vscode-json-languageservice' ;
22- import { stringify as stringifyYAML } from 'yaml' ;
20+ import { matchOffsetToDocument } from '../utils/arrUtils' ;
2321import { toYamlStringScalar } from '../utils/yamlScalar' ;
22+ import { LanguageSettings } from '../yamlLanguageService' ;
23+ import { YAMLSchemaService } from './yamlSchemaService' ;
2424
2525export class YAMLHover {
2626 private shouldHover : boolean ;
27+ private shouldHoverAnchor : boolean ;
2728 private indentation : string ;
2829 private schemaService : YAMLSchemaService ;
2930
@@ -38,6 +39,7 @@ export class YAMLHover {
3839 configure ( languageSettings : LanguageSettings ) : void {
3940 if ( languageSettings ) {
4041 this . shouldHover = languageSettings . hover ;
42+ this . shouldHoverAnchor = languageSettings . hoverAnchor ;
4143 this . indentation = languageSettings . indentation ;
4244 }
4345 }
@@ -103,6 +105,14 @@ export class YAMLHover {
103105 return result ;
104106 } ;
105107
108+ if ( this . shouldHoverAnchor && node . type === 'property' && node . valueNode ) {
109+ if ( node . valueNode . type === 'object' ) {
110+ const resolved = this . resolveMergeKeys ( node . valueNode as ObjectASTNode , doc ) ;
111+ const contents = '```yaml\n' + stringifyYAML ( resolved , null , 2 ) + '\n```' ;
112+ return Promise . resolve ( createHover ( contents ) ) ;
113+ }
114+ }
115+
106116 const removePipe = ( value : string ) : string => {
107117 return value . replace ( / \s \| \| \s * $ / , '' ) ;
108118 } ;
@@ -207,6 +217,123 @@ export class YAMLHover {
207217 } ) ;
208218 }
209219
220+ /**
221+ * Resolves merge keys (<<) and anchors recursively in an object node
222+ * @param node The object AST node to resolve
223+ * @param doc The YAML document for resolving anchors
224+ * @param currentRecursionLevel Current recursion level (default: 0)
225+ * @returns A plain JavaScript object with all merges resolved
226+ */
227+ private resolveMergeKeys ( node : ObjectASTNode , doc : SingleYAMLDocument , currentRecursionLevel = 0 ) : Record < string , unknown > {
228+ const result : Record < string , unknown > = { } ;
229+ const unprocessedProperties : PropertyASTNode [ ] = [ ...node . properties ] ;
230+
231+ while ( unprocessedProperties . length > 0 ) {
232+ const propertyNode = unprocessedProperties . shift ( ) ;
233+ const key = propertyNode . keyNode . value ;
234+
235+ if ( key === '<<' && propertyNode . valueNode ) {
236+ // Handle merge key
237+ const mergeValue = this . resolveMergeValue ( propertyNode . valueNode , doc , currentRecursionLevel + 1 ) ;
238+ if ( mergeValue && typeof mergeValue === 'object' && ! Array . isArray ( mergeValue ) ) {
239+ // Merge properties from the resolved value
240+ const mergeKeys = Object . keys ( mergeValue ) ;
241+ for ( const mergeKey of mergeKeys ) {
242+ result [ mergeKey ] = mergeValue [ mergeKey ] ;
243+ }
244+ }
245+ } else {
246+ // Regular property
247+ result [ key ] = this . astNodeToValue ( propertyNode . valueNode , doc , currentRecursionLevel ) ;
248+ }
249+ }
250+
251+ return result ;
252+ }
253+
254+ /**
255+ * Resolves a merge value (which might be an alias) and recursively resolves its merge keys
256+ * @param node The AST node that might be an alias or object
257+ * @param doc The YAML document for resolving anchors
258+ * @param currentRecursionLevel Current recursion level
259+ * @returns The resolved value
260+ */
261+ private resolveMergeValue ( node : ASTNode , doc : SingleYAMLDocument , currentRecursionLevel : number ) : unknown {
262+ const MAX_MERGE_RECURSION_LEVEL = 10 ;
263+
264+ // Check if we've exceeded max recursion level
265+ if ( currentRecursionLevel >= MAX_MERGE_RECURSION_LEVEL ) {
266+ return { '<<' : node . parent . internalNode [ 'value' ] + ' (recursion limit reached)' } ;
267+ }
268+
269+ // If it's an object node, resolve its merge keys
270+ if ( node . type === 'object' ) {
271+ return this . resolveMergeKeys ( node as ObjectASTNode , doc , currentRecursionLevel ) ;
272+ }
273+
274+ // Otherwise, convert to value
275+ return this . astNodeToValue ( node , doc , currentRecursionLevel ) ;
276+ }
277+
278+ /**
279+ * Converts an AST node to a plain JavaScript value
280+ * @param node The AST node to convert
281+ * @param doc The YAML document for resolving anchors
282+ * @param currentRecursionLevel Current recursion level
283+ * @returns The converted value
284+ */
285+ private astNodeToValue ( node : ASTNode | undefined , doc : SingleYAMLDocument , currentRecursionLevel : number ) : unknown {
286+ if ( ! node ) {
287+ return null ;
288+ }
289+
290+ switch ( node . type ) {
291+ case 'object' : {
292+ return this . resolveMergeKeys ( node as ObjectASTNode , doc , currentRecursionLevel ) ;
293+ }
294+ case 'array' : {
295+ return node . children . map ( ( child ) => this . astNodeToValue ( child , doc , currentRecursionLevel ) ) ;
296+ }
297+ case 'string' :
298+ case 'number' :
299+ case 'boolean' :
300+ case 'null' : {
301+ return node . value ;
302+ }
303+ default : {
304+ return this . nodeToValue ( node . internalNode as Node ) ;
305+ }
306+ }
307+ }
308+
309+ /**
310+ * Converts a YAML Node to a plain JavaScript value
311+ * @param node The YAML node to convert
312+ * @returns The converted value
313+ */
314+ private nodeToValue ( node : Node ) : unknown {
315+ if ( isAlias ( node ) ) {
316+ return node . source ;
317+ }
318+ if ( isMap ( node ) ) {
319+ const result : Record < string , unknown > = { } ;
320+ for ( const pair of node . items ) {
321+ if ( pair . key && pair . value ) {
322+ const key = this . nodeToValue ( pair . key as Node ) ;
323+ const value = this . nodeToValue ( pair . value as Node ) ;
324+ if ( typeof key === 'string' ) {
325+ result [ key ] = value ;
326+ }
327+ }
328+ }
329+ return result ;
330+ }
331+ if ( isSeq ( node ) ) {
332+ return node . items . map ( ( item ) => this . nodeToValue ( item as Node ) ) ;
333+ }
334+ return ( node as { value ?: unknown } ) . value ;
335+ }
336+
210337 // copied from https://github.com/microsoft/vscode-json-languageservice/blob/2ea5ad3d2ffbbe40dea11cfe764a502becf113ce/src/services/jsonHover.ts#L112
211338 private toMarkdown ( plain : string | undefined ) : string | undefined {
212339 if ( plain ) {
0 commit comments