diff --git a/README.md b/README.md index 6d6c052a..e25e55f9 100755 --- a/README.md +++ b/README.md @@ -50,9 +50,11 @@ The following settings are supported: - `[yaml].editor.formatOnType`: Enable/disable on type indent and auto formatting array - `yaml.disableDefaultProperties`: Disable adding not required properties with default values into completion text - `yaml.suggest.parentSkeletonSelectedFirst`: If true, the user must select some parent skeleton first before autocompletion starts to suggest the rest of the properties.\nWhen yaml object is not empty, autocompletion ignores this setting and returns all properties and skeletons. -- `yaml.style.flowMapping` : Forbids flow style mappings if set to `forbid` +- `yaml.style.flowMapping` : Forbids flow style mappings if set to `forbid` - `yaml.style.flowSequence` : Forbids flow style sequences if set to `forbid` - `yaml.keyOrdering` : Enforces alphabetical ordering of keys in mappings when set to `true`. Default is `false` +- `yaml.gitlabci.enabled` : Enables gitlab-ci add-ons +- `yaml.gitlabci.codelensEnabled` : Enables gitlab-ci related code lens ##### Adding custom tags diff --git a/src/languageserver/handlers/settingsHandlers.ts b/src/languageserver/handlers/settingsHandlers.ts index 95ddba77..d5f6aa96 100644 --- a/src/languageserver/handlers/settingsHandlers.ts +++ b/src/languageserver/handlers/settingsHandlers.ts @@ -123,6 +123,10 @@ export class SettingsHandler { flowSequence: settings.yaml.style?.flowSequence ?? 'allow', }; this.yamlSettings.keyOrdering = settings.yaml.keyOrdering ?? false; + if (settings.yaml.gitlabci) { + this.yamlSettings.gitlabci.enabled = settings.yaml.gitlabci.enabled ?? true; + this.yamlSettings.gitlabci.codelensEnabled = settings.yaml.gitlabci.codelensEnabled ?? true; + } } this.yamlSettings.schemaConfigurationSettings = []; @@ -259,6 +263,7 @@ export class SettingsHandler { flowSequence: this.yamlSettings.style?.flowSequence, yamlVersion: this.yamlSettings.yamlVersion, keyOrdering: this.yamlSettings.keyOrdering, + gitlabci: this.yamlSettings.gitlabci, }; if (this.yamlSettings.schemaAssociations) { diff --git a/src/languageservice/parser/yaml-documents.ts b/src/languageservice/parser/yaml-documents.ts index 3fb99ddd..a44a774d 100644 --- a/src/languageservice/parser/yaml-documents.ts +++ b/src/languageservice/parser/yaml-documents.ts @@ -262,6 +262,7 @@ interface YamlCachedDocument { export class YamlDocuments { // a mapping of URIs to cached documents private cache = new Map(); + private textDocumentMapping = new Map(); /** * Get cached YAMLDocument @@ -272,9 +273,19 @@ export class YamlDocuments { */ getYamlDocument(document: TextDocument, parserOptions?: ParserOptions, addRootObject = false): YAMLDocument { this.ensureCache(document, parserOptions ?? defaultOptions, addRootObject); + this.textDocumentMapping.set(document.uri, document); return this.cache.get(document.uri).document; } + getAllDocuments(): [string, YAMLDocument, TextDocument][] { + const documents: [string, YAMLDocument, TextDocument][] = []; + for (const [uri, doc] of this.cache.entries()) { + const txtdoc = this.textDocumentMapping.get(uri); + documents.push([uri, doc.document, txtdoc]); + } + return documents; + } + /** * For test purpose only! */ diff --git a/src/languageservice/services/gitlabciUtils.ts b/src/languageservice/services/gitlabciUtils.ts new file mode 100644 index 00000000..41c73688 --- /dev/null +++ b/src/languageservice/services/gitlabciUtils.ts @@ -0,0 +1,223 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Red Hat, Inc. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { TextDocument } from 'vscode-languageserver-textdocument'; +import { LocationLink, Position, Range } from 'vscode-languageserver-types'; +import { isSeq, isMap, isScalar, isPair, YAMLMap, Node, Pair, isNode, Scalar, visit } from 'yaml'; +import { SingleYAMLDocument, YAMLDocument, yamlDocumentsCache } from '../parser/yaml-documents'; +import { readFileSync, readdirSync, statSync } from 'fs'; +import { WorkspaceFolder } from 'vscode-languageserver'; +import { URI } from 'vscode-uri'; + +// Find node within all yaml documents +export function findNodeFromPath( + allDocuments: [string, YAMLDocument, TextDocument][], + path: string[] +): [string, Pair, TextDocument] | undefined { + for (const [uri, docctx, doctxt] of allDocuments) { + for (const doc of docctx.documents) { + if (isMap(doc.internalDocument.contents)) { + let node: YAMLMap = doc.internalDocument.contents; + // Follow path + for (let i = 0; i < path.length; ++i) { + const target = node.items.find(({ key: key }) => key == path[i]); + if (target && i == path.length - 1) { + return [uri, target, doctxt]; + } else if (target && isMap(target.value)) { + node = target.value; + } else { + break; + } + } + } + } + } +} + +// Like findNodeFromPath but will follow extends tags +export function findNodeFromPathRecursive( + allDocuments: [string, YAMLDocument, TextDocument][], + path: string[], + maxDepth = 16 +): [string, Pair, TextDocument][] { + const result = []; + let pathResult = findNodeFromPath(allDocuments, path); + for (let i = 0; pathResult && i < maxDepth; ++i) { + result.push(pathResult); + const target = pathResult[1]; + path = null; + if (isMap(target.value)) { + // Find extends within result + const extendsNode = findChildWithKey(target.value, 'extends'); + if (extendsNode) { + // Only follow the first extends tag + if (isScalar(extendsNode.value)) { + path = [extendsNode.value.value as string]; + } else if (isSeq(extendsNode.value) && isScalar(extendsNode.value.items[0])) { + path = [extendsNode.value.items[0].value as string]; + } + } + } + if (path === null) { + break; + } + pathResult = findNodeFromPath(allDocuments, path); + } + + return result; +} + +// Will create a LocationLink from a pair node +export function createDefinitionFromTarget(target: Pair, document: TextDocument, uri: string): LocationLink { + const start = target.key.range[0]; + const endDef = target.key.range[1]; + const endFull = target.value.range[2]; + const targetRange = Range.create(document.positionAt(start), document.positionAt(endFull)); + const selectionRange = Range.create(document.positionAt(start), document.positionAt(endDef)); + + return LocationLink.create(uri, targetRange, selectionRange); +} + +// Returns whether or not the node has a parent with the given key +// Useful to find the parent for nested nodes (e.g. extends with an array) +export function findParentWithKey(node: Node, key: string, currentDoc: SingleYAMLDocument, maxDepth = 2): Pair { + let parent = currentDoc.getParent(node); + for (let i = 0; i < maxDepth; ++i) { + if (parent && isPair(parent) && isScalar(parent.key) && parent.key.value === key) { + return parent; + } + parent = currentDoc.getParent(parent); + } + + return null; +} + +// Find if possible a child with the given key +export function findChildWithKey(node: YAMLMap, targetKey: string): Pair | undefined { + return node.items.find(({ key: key }) => key == targetKey); +} + +// Get all potential job nodes from all documents +// A job node is a map node at the root of the document +export function getJobNodes( + allDocuments: [string, YAMLDocument, TextDocument][] +): [LocationLink, TextDocument, Pair][] { + const jobNodes = []; + for (const [uri, docctx, doctxt] of allDocuments) { + for (const doc of docctx.documents) { + if (isMap(doc.internalDocument.contents)) { + for (const node of doc.internalDocument.contents.items) { + if (isNode(node.key) && isMap(node.value)) { + const loc = createDefinitionFromTarget(node as Pair, doctxt, uri); + jobNodes.push([loc, doctxt, node]); + } + } + } + } + } + + return jobNodes; +} + +// Find where jobs are used, such as within extends or needs nodes and reference tags +export function findUsages(allDocuments: [string, YAMLDocument, TextDocument][]): Map { + const targetAttributes = ['extends', 'needs']; + const usages = new Map(); + const jobNodes = getJobNodes(allDocuments); + + for (const [jobLoc, doc, job] of jobNodes) { + // !reference tags + visit(job.value, (_, node) => { + // Support only top level jobs so the sequence must be of length 1 + if (isSeq(node) && node.tag === '!reference' && node.items.length === 1 && isScalar(node.items[0])) { + const jobName = node.items[0].value as string; + const range = Range.create(doc.positionAt(node.items[0].range[0]), doc.positionAt(node.items[0].range[1])); + const loc = LocationLink.create(jobLoc.targetUri, range, range); + if (usages.has(jobName)) usages.get(jobName).push(loc); + else usages.set(jobName, [loc]); + } + }); + + // Extends / needs attributes + // For each attribute of each job + for (const item of job.value.items) { + if (isScalar(item.key)) { + if (targetAttributes.includes(item.key.value as string)) { + const referencedJobs: Scalar[] = []; + + // Get all job names + if (isScalar(item.value) && typeof item.value.value === 'string') { + referencedJobs.push(item.value); + } else if (isSeq(item.value)) { + for (const seqItem of item.value.items) { + if (isScalar(seqItem) && typeof seqItem.value === 'string') { + referencedJobs.push(seqItem); + } + } + } + + for (const referencedJob of referencedJobs) { + const jobName = referencedJob.value as string; + const targetRange = Range.create(doc.positionAt(referencedJob.range[0]), doc.positionAt(referencedJob.range[1])); + const loc = LocationLink.create(jobLoc.targetUri, targetRange, targetRange); + + // Add it to the references + if (usages.has(jobName)) usages.get(jobName).push(loc); + else usages.set(jobName, [loc]); + } + } + } + } + } + + return usages; +} + +export function toExportedPos(pos: Position): object { + return { lineNumber: pos.line + 1, column: pos.character + 1 }; +} + +export function toExportedRange(range: Range): object { + return { + startLineNumber: range.start.line + 1, + startColumn: range.start.character + 1, + endLineNumber: range.end.line + 1, + endColumn: range.end.character + 1, + }; +} + +// Parse the file at this parse and add it to the cache +function registerFile(path: string): void { + const content = readFileSync(path, 'utf8'); + const doc = TextDocument.create('file://' + path, 'yaml', 1, content); + yamlDocumentsCache.getYamlDocument(doc); +} + +function registerWorkspaceFiles(path: string): void { + try { + const files = readdirSync(path); + for (const file of files) { + const filePath = path + '/' + file; + const stats = statSync(filePath); + + if (file.endsWith('.yaml') || file.endsWith('.yml')) { + registerFile(filePath); + } else if (stats.isDirectory()) { + registerWorkspaceFiles(filePath); + } + } + } catch (e) { + console.warn('Error reading directory: ' + path + ', ignoring it'); + return; + } +} + +// Walk through all the files in the workspace and put them in cache +// Useful to have cross files references for gitlabci +export function registerWorkspaces(workspaceFolders: WorkspaceFolder[]): void { + for (const folder of workspaceFolders) { + registerWorkspaceFiles(URI.parse(folder.uri).fsPath); + } +} diff --git a/src/languageservice/services/yamlCodeLens.ts b/src/languageservice/services/yamlCodeLens.ts index b1ae69d3..722e61a3 100644 --- a/src/languageservice/services/yamlCodeLens.ts +++ b/src/languageservice/services/yamlCodeLens.ts @@ -13,14 +13,58 @@ import { Telemetry } from '../telemetry'; import { getSchemaUrls } from '../utils/schemaUrls'; import { convertErrorToTelemetryMsg } from '../utils/objects'; import { getSchemaTitle } from '../utils/schemaUtils'; +import { isMap, isPair, isScalar } from 'yaml'; +import { findUsages, toExportedPos, toExportedRange } from './gitlabciUtils'; +import { URI } from 'vscode-uri'; +import { SettingsState } from '../../yamlSettings'; export class YamlCodeLens { - constructor(private schemaService: YAMLSchemaService, private readonly telemetry?: Telemetry) {} + constructor( + private schemaService: YAMLSchemaService, + private readonly telemetry?: Telemetry, + private readonly settings?: SettingsState + ) {} async getCodeLens(document: TextDocument): Promise { const result = []; try { const yamlDocument = yamlDocumentsCache.getYamlDocument(document); + + if (this.settings?.gitlabci?.enabled && this.settings?.gitlabci?.codelensEnabled) { + // GitlabCI Job Usages + const usages = findUsages(yamlDocumentsCache.getAllDocuments()); + for (const doc of yamlDocument.documents) { + if (isMap(doc.internalDocument.contents)) { + for (const jobNode of doc.internalDocument.contents.items) { + // If at least one usage + if (isPair(jobNode) && isScalar(jobNode.key) && usages.has(jobNode.key.value as string)) { + const jobUsages = usages.get(jobNode.key.value as string); + const nodeRange = Range.create( + document.positionAt(jobNode.key.range[0]), + document.positionAt(jobNode.key.range[1]) + ); + const lens = CodeLens.create(nodeRange); + // Locations for all usages + const locations = []; + for (const loc of jobUsages) { + locations.push({ + uri: URI.parse(loc.targetUri), + range: toExportedRange(loc.targetRange), + }); + } + lens.command = { + title: jobUsages.length === 1 ? '1 usage' : `${jobUsages.length} usages`, + command: 'editor.action.peekLocations', + arguments: [URI.parse(document.uri), toExportedPos(nodeRange.end), locations], + }; + + result.push(lens); + } + } + } + } + } + let schemaUrls = new Map(); for (const currentYAMLDoc of yamlDocument.documents) { const schema = await this.schemaService.getSchemaForResource(document.uri, currentYAMLDoc); diff --git a/src/languageservice/services/yamlDefinition.ts b/src/languageservice/services/yamlDefinition.ts index b2f1b975..7abd3a81 100644 --- a/src/languageservice/services/yamlDefinition.ts +++ b/src/languageservice/services/yamlDefinition.ts @@ -6,23 +6,35 @@ import { DefinitionParams } from 'vscode-languageserver-protocol'; import { TextDocument } from 'vscode-languageserver-textdocument'; import { DefinitionLink, LocationLink, Range } from 'vscode-languageserver-types'; -import { isAlias } from 'yaml'; +import { isAlias, isSeq, isScalar, Node, Pair, Scalar, isMap } from 'yaml'; import { Telemetry } from '../telemetry'; import { yamlDocumentsCache } from '../parser/yaml-documents'; import { matchOffsetToDocument } from '../utils/arrUtils'; import { convertErrorToTelemetryMsg } from '../utils/objects'; import { TextBuffer } from '../utils/textBuffer'; +import { SettingsState } from '../../yamlSettings'; +import { dirname, resolve } from 'path'; +import { + findParentWithKey, + createDefinitionFromTarget, + findNodeFromPath, + findNodeFromPathRecursive, + findChildWithKey, +} from './gitlabciUtils'; export class YamlDefinition { - constructor(private readonly telemetry?: Telemetry) {} + constructor(private readonly telemetry?: Telemetry, private readonly settings?: SettingsState) {} getDefinition(document: TextDocument, params: DefinitionParams): DefinitionLink[] | undefined { try { + const all = yamlDocumentsCache.getAllDocuments(); const yamlDocument = yamlDocumentsCache.getYamlDocument(document); const offset = document.offsetAt(params.position); const currentDoc = matchOffsetToDocument(offset, yamlDocument); + let gitlabciExtendsNode = null; if (currentDoc) { const [node] = currentDoc.getNodeFromPosition(offset, new TextBuffer(document)); + const parent = currentDoc.getParent(node) as Pair | undefined; if (node && isAlias(node)) { const defNode = node.resolve(currentDoc.internalDocument); if (defNode && defNode.range) { @@ -30,6 +42,68 @@ export class YamlDefinition { const selectionRange = Range.create(document.positionAt(defNode.range[0]), document.positionAt(defNode.range[1])); return [LocationLink.create(document.uri, targetRange, selectionRange)]; } + } else if ( + this.settings?.gitlabci.enabled && + node && + isScalar(node) && + findParentWithKey(node, 'include', currentDoc, 2) + ) { + // include node + const path = node.value as string; + if (path.startsWith('./') && document.uri.startsWith('file://')) { + // Resolve relative path from document.uri + const curPath = new URL(document.uri).pathname; + const dirPath = dirname(curPath); + const absPath = resolve(dirPath, path); + + return [ + // First line of the document + LocationLink.create(absPath, Range.create(0, 0, 1, 0), Range.create(0, 0, 1, 0)), + ]; + } + } else if ( + this.settings?.gitlabci.enabled && + node && + ((isScalar(node) && findParentWithKey(node, 'extends', currentDoc, 2)) || + (isMap(parent.value) && (gitlabciExtendsNode = findChildWithKey(parent.value, 'extends')))) + ) { + // Name of the job to extend + const extendJob = gitlabciExtendsNode + ? (gitlabciExtendsNode.value.value as string) + : ((node as Scalar).value as string); + + const pathResults = findNodeFromPathRecursive(all, [extendJob]); + if (pathResults.length) { + const result = []; + for (const [uri, target, targetDocument] of pathResults) { + result.push(createDefinitionFromTarget(target as Pair, targetDocument, uri)); + } + return result; + } + } else if (this.settings?.gitlabci.enabled && node && isScalar(node) && findParentWithKey(node, 'needs', currentDoc, 2)) { + // needs tag + const pathResult = findNodeFromPath(all, [node.value as string]); + if (pathResult) { + const [uri, target, targetDocument] = pathResult; + return [createDefinitionFromTarget(target as Pair, targetDocument, uri)]; + } + } else if ( + this.settings?.gitlabci.enabled && + node && + isScalar(node) && + parent && + isSeq(parent) && + parent.tag === '!reference' + ) { + // !reference tag + const pathResult = findNodeFromPath( + all, + parent.items.map((item: Scalar) => item.value as string) + ); + if (pathResult) { + const [uri, target, targetDocument] = pathResult; + return [createDefinitionFromTarget(target as Pair, targetDocument, uri)]; + } } } } catch (err) { diff --git a/src/languageservice/services/yamlHover.ts b/src/languageservice/services/yamlHover.ts index a0e72ce3..47366988 100644 --- a/src/languageservice/services/yamlHover.ts +++ b/src/languageservice/services/yamlHover.ts @@ -19,14 +19,20 @@ import * as path from 'path'; import { Telemetry } from '../telemetry'; import { convertErrorToTelemetryMsg } from '../utils/objects'; import { ASTNode } from 'vscode-json-languageservice'; -import { stringify as stringifyYAML } from 'yaml'; +import { Scalar, stringify as stringifyYAML } from 'yaml'; +import { SettingsState } from '../../yamlSettings'; +import { findNodeFromPathRecursive } from './gitlabciUtils'; export class YAMLHover { private shouldHover: boolean; private indentation: string; private schemaService: YAMLSchemaService; - constructor(schemaService: YAMLSchemaService, private readonly telemetry?: Telemetry) { + constructor( + schemaService: YAMLSchemaService, + private readonly telemetry?: Telemetry, + private readonly settings?: SettingsState + ) { this.shouldHover = true; this.schemaService = schemaService; } @@ -87,6 +93,29 @@ export class YAMLHover { document.positionAt(hoverRangeNode.offset + hoverRangeNode.length) ); + if (this.settings?.gitlabci.enabled) { + const allDocuments = yamlDocumentsCache.getAllDocuments(); + + // Job title hover : Show hierarchy + // hoverRangeNode = job name, parent = key value pair, grandparent = full document, great grandparent = null + if (hoverRangeNode && hoverRangeNode.parent && hoverRangeNode.parent.parent && !hoverRangeNode.parent.parent.parent) { + const jobName = hoverRangeNode.value as string; + const hierarchy = findNodeFromPathRecursive(allDocuments, [jobName]); + if (hierarchy.length >= 2) { + const names = []; + for (const [, target, ,] of hierarchy) { + names.push((('`' + (target.key as Scalar).value) as string) + '`'); + } + + const result: Hover = { + contents: '### Gitlab Hierarchy\n' + names.join(' > '), + range: hoverRange, + }; + return Promise.resolve(result); + } + } + } + const createHover = (contents: string): Hover => { const markupContent: MarkupContent = { kind: MarkupKind.Markdown, diff --git a/src/languageservice/services/yamlValidation.ts b/src/languageservice/services/yamlValidation.ts index fd26af2e..d574aaa1 100644 --- a/src/languageservice/services/yamlValidation.ts +++ b/src/languageservice/services/yamlValidation.ts @@ -58,6 +58,9 @@ export class YAMLValidation { if (settings) { this.validationEnabled = settings.validate; this.customTags = settings.customTags; + if (settings.gitlabci?.enabled) { + this.customTags.push('!reference sequence'); + } this.disableAdditionalProperties = settings.disableAdditionalProperties; this.yamlVersion = settings.yamlVersion; // Add style validator if flow style is set to forbid only. diff --git a/src/languageservice/yamlLanguageService.ts b/src/languageservice/yamlLanguageService.ts index 877fde06..6255ba17 100644 --- a/src/languageservice/yamlLanguageService.ts +++ b/src/languageservice/yamlLanguageService.ts @@ -71,6 +71,11 @@ export interface SchemasSettings { versions?: SchemaVersions; } +export interface GitlabciSettings { + enabled?: boolean; + codelensEnabled?: boolean; +} + export interface LanguageSettings { validate?: boolean; //Setting for whether we want to validate the schema hover?: boolean; //Setting for whether we want to have hover results @@ -119,6 +124,10 @@ export interface LanguageSettings { * If set enforce alphabetical ordering of keys in mappings. */ keyOrdering?: boolean; + /** + * If set will enable gitlab-ci add-ons. + */ + gitlabci?: GitlabciSettings; } export interface WorkspaceContextService { @@ -191,14 +200,14 @@ export function getLanguageService(params: { }): 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 hover = new YAMLHover(schemaService, params.telemetry, params.yamlSettings); const yamlDocumentSymbols = new YAMLDocumentSymbols(schemaService, params.telemetry); const yamlValidation = new YAMLValidation(schemaService, params.telemetry); const formatter = new YAMLFormatter(); const yamlCodeActions = new YamlCodeActions(params.clientCapabilities); - const yamlCodeLens = new YamlCodeLens(schemaService, params.telemetry); + const yamlCodeLens = new YamlCodeLens(schemaService, params.telemetry, params.yamlSettings); const yamlLinks = new YamlLinks(params.telemetry); - const yamlDefinition = new YamlDefinition(params.telemetry); + const yamlDefinition = new YamlDefinition(params.telemetry, params.yamlSettings); new JSONSchemaSelection(schemaService, params.yamlSettings, params.connection); diff --git a/src/yamlServerInit.ts b/src/yamlServerInit.ts index 9640e48d..58d5bfdf 100644 --- a/src/yamlServerInit.ts +++ b/src/yamlServerInit.ts @@ -18,6 +18,7 @@ import { WorkspaceHandlers } from './languageserver/handlers/workspaceHandlers'; import { commandExecutor } from './languageserver/commandExecutor'; import { Telemetry } from './languageservice/telemetry'; import { registerCommands } from './languageservice/services/yamlCommands'; +import { registerWorkspaces } from './languageservice/services/gitlabciUtils'; export class YAMLServerInit { languageService: LanguageService; @@ -45,6 +46,10 @@ export class YAMLServerInit { if (this.yamlSettings.hasWsChangeWatchedFileDynamicRegistration) { this.connection.workspace.onDidChangeWorkspaceFolders((changedFolders) => { this.yamlSettings.workspaceFolders = workspaceFoldersChanged(this.yamlSettings.workspaceFolders, changedFolders); + + if (this.yamlSettings.gitlabci.enabled) { + registerWorkspaces(this.yamlSettings.workspaceFolders); + } }); } // need to call this after connection initialized @@ -129,6 +134,8 @@ export class YAMLServerInit { } private registerHandlers(): void { + registerWorkspaces(this.yamlSettings.workspaceFolders); + // Register all features that the language server has this.validationHandler = new ValidationHandler(this.connection, this.languageService, this.yamlSettings); this.settingsHandler = new SettingsHandler( diff --git a/src/yamlSettings.ts b/src/yamlSettings.ts index fc026034..56e2df1e 100644 --- a/src/yamlSettings.ts +++ b/src/yamlSettings.ts @@ -32,6 +32,10 @@ export interface Settings { keyOrdering: boolean; maxItemsComputed: number; yamlVersion: YamlVersion; + gitlabci: { + enabled: boolean; + codelensEnabled: boolean; + }; }; http: { proxy: string; @@ -89,6 +93,10 @@ export class SettingsState { }; keyOrdering = false; maxItemsComputed = 5000; + gitlabci = { + enabled: true, + codelensEnabled: true, + }; // File validation helpers pendingValidationRequests: { [uri: string]: NodeJS.Timer } = {}; diff --git a/test/yamlDefinition.test.ts b/test/yamlDefinition.test.ts index 9a80dca8..db57537b 100644 --- a/test/yamlDefinition.test.ts +++ b/test/yamlDefinition.test.ts @@ -8,10 +8,13 @@ import { expect } from 'chai'; import { YamlDefinition } from '../src/languageservice/services/yamlDefinition'; import { LocationLink, Position, Range } from 'vscode-languageserver-types'; import { Telemetry } from '../src/languageservice/telemetry'; +import { TextDocumentTestManager } from '../src/yamlSettings'; describe('YAML Definition', () => { it('should not provide definition for non anchor node', () => { const doc = setupTextDocument('foo: &bar some\naaa: *bar'); + const documents = new TextDocumentTestManager(); + (documents as TextDocumentTestManager).set(doc); const result = new YamlDefinition({} as Telemetry).getDefinition(doc, { position: Position.create(1, 2), textDocument: { uri: TEST_URI }, @@ -21,6 +24,8 @@ describe('YAML Definition', () => { it('should provide definition for anchor', () => { const doc = setupTextDocument('foo: &bar some\naaa: *bar'); + const documents = new TextDocumentTestManager(); + (documents as TextDocumentTestManager).set(doc); const result = new YamlDefinition({} as Telemetry).getDefinition(doc, { position: Position.create(1, 7), textDocument: { uri: TEST_URI },