forked from redhat-developer/yaml-language-server
-
Notifications
You must be signed in to change notification settings - Fork 1
Expand file tree
/
Copy pathyamlValidation.ts
More file actions
160 lines (139 loc) · 5.77 KB
/
yamlValidation.ts
File metadata and controls
160 lines (139 loc) · 5.77 KB
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
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
/*---------------------------------------------------------------------------------------------
* Copyright (c) Red Hat, Inc. All rights reserved.
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
'use strict';
import { Diagnostic, Position } from 'vscode-languageserver';
import { LanguageSettings } from '../yamlLanguageService';
import { YAMLDocument, YamlVersion } from '../parser/yamlParser07';
import { SingleYAMLDocument } from '../parser/yamlParser07';
import { YAMLSchemaService } from './yamlSchemaService';
import { YAMLDocDiagnostic } from '../utils/parseUtils';
import { TextDocument } from 'vscode-languageserver-textdocument';
import { JSONValidation } from 'vscode-json-languageservice/lib/umd/services/jsonValidation';
import { YAML_SOURCE } from '../parser/jsonParser07';
import { TextBuffer } from '../utils/textBuffer';
import { yamlDocumentsCache } from '../parser/yaml-documents';
import { convertErrorToTelemetryMsg } from '../utils/objects';
import { AdditionalValidator } from './validation/types';
import { UnusedAnchorsValidator } from './validation/unused-anchors';
/**
* Convert a YAMLDocDiagnostic to a language server Diagnostic
* @param yamlDiag A YAMLDocDiagnostic from the parser
* @param textDocument TextDocument from the language server client
*/
export const yamlDiagToLSDiag = (yamlDiag: YAMLDocDiagnostic, textDocument: TextDocument): Diagnostic => {
const start = textDocument.positionAt(yamlDiag.location.start);
const range = {
start,
end: yamlDiag.location.toLineEnd
? Position.create(start.line, new TextBuffer(textDocument).getLineLength(start.line))
: textDocument.positionAt(yamlDiag.location.end),
};
return Diagnostic.create(range, yamlDiag.message, yamlDiag.severity, yamlDiag.code, YAML_SOURCE);
};
export class YAMLValidation {
private validationEnabled: boolean;
private customTags: string[];
private jsonValidation;
private disableAdditionalProperties: boolean;
private yamlVersion: YamlVersion;
private additionalValidation: AdditionalValidation;
private MATCHES_MULTIPLE = 'Matches multiple schemas when only one must validate.';
constructor(schemaService: YAMLSchemaService) {
this.validationEnabled = true;
this.jsonValidation = new JSONValidation(schemaService, Promise);
this.additionalValidation = new AdditionalValidation();
}
public configure(settings: LanguageSettings): void {
if (settings) {
this.validationEnabled = settings.validate;
this.customTags = settings.customTags;
this.disableAdditionalProperties = settings.disableAdditionalProperties;
this.yamlVersion = settings.yamlVersion;
}
}
public async doValidation(textDocument: TextDocument, isKubernetes = false): Promise<Diagnostic[]> {
if (!this.validationEnabled) {
return Promise.resolve([]);
}
const validationResult = [];
try {
const yamlDocument: YAMLDocument = yamlDocumentsCache.getYamlDocument(
textDocument,
{ customTags: this.customTags, yamlVersion: this.yamlVersion },
true
);
let index = 0;
for (const currentYAMLDoc of yamlDocument.documents) {
currentYAMLDoc.isKubernetes = isKubernetes;
currentYAMLDoc.currentDocIndex = index;
currentYAMLDoc.disableAdditionalProperties = this.disableAdditionalProperties;
const validation = await this.jsonValidation.doValidation(textDocument, currentYAMLDoc);
const syd = (currentYAMLDoc as unknown) as SingleYAMLDocument;
if (syd.errors.length > 0) {
// TODO: Get rid of these type assertions (shouldn't need them)
validationResult.push(...syd.errors);
}
if (syd.warnings.length > 0) {
validationResult.push(...syd.warnings);
}
validationResult.push(...validation);
validationResult.push(...this.additionalValidation.validate(textDocument, currentYAMLDoc));
index++;
}
} catch (err) {
console.error(convertErrorToTelemetryMsg(err));
}
let previousErr: Diagnostic;
const foundSignatures = new Set();
const duplicateMessagesRemoved: Diagnostic[] = [];
for (let err of validationResult) {
/**
* A patch ontop of the validation that removes the
* 'Matches many schemas' error for kubernetes
* for a better user experience.
*/
if (isKubernetes && err.message === this.MATCHES_MULTIPLE) {
continue;
}
if (Object.prototype.hasOwnProperty.call(err, 'location')) {
err = yamlDiagToLSDiag(err, textDocument);
}
if (!err.source) {
err.source = YAML_SOURCE;
}
if (
previousErr &&
previousErr.message === err.message &&
previousErr.range.end.line === err.range.start.line &&
Math.abs(previousErr.range.end.character - err.range.end.character) >= 1
) {
previousErr.range.end = err.range.end;
continue;
} else {
previousErr = err;
}
const errSig = err.range.start.line + ' ' + err.range.start.character + ' ' + err.message;
if (!foundSignatures.has(errSig)) {
duplicateMessagesRemoved.push(err);
foundSignatures.add(errSig);
}
}
return duplicateMessagesRemoved;
}
}
class AdditionalValidation {
private validators: AdditionalValidator[] = [];
constructor() {
this.validators.push(new UnusedAnchorsValidator());
}
validate(document: TextDocument, yarnDoc: SingleYAMLDocument): Diagnostic[] {
const result = [];
for (const validator of this.validators) {
result.push(...validator.validate(document, yarnDoc));
}
return result;
}
}