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
6 changes: 4 additions & 2 deletions src/languageserver/handlers/languageHandlers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -156,7 +156,7 @@ export class LanguageHandlers {
* Called when auto-complete is triggered in an editor
* Returns a list of valid completion items
*/
completionHandler(textDocumentPosition: TextDocumentPositionParams): Promise<CompletionList> {
completionHandler(textDocumentPosition: TextDocumentPositionParams, gracefulMatches = false): Promise<CompletionList> {
const textDocument = this.yamlSettings.documents.get(textDocumentPosition.textDocument.uri);

const result: CompletionList = {
Expand All @@ -170,7 +170,9 @@ export class LanguageHandlers {
return this.languageService.doComplete(
textDocument,
textDocumentPosition.position,
isKubernetesAssociatedDocument(textDocument, this.yamlSettings.specificValidatorPaths)
isKubernetesAssociatedDocument(textDocument, this.yamlSettings.specificValidatorPaths),
true,
gracefulMatches
);
}

Expand Down
46 changes: 41 additions & 5 deletions src/languageservice/parser/jsonParser07.ts
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,7 @@ export const ProblemTypeMessages: Record<ProblemType, string> = {
[ProblemType.typeMismatchWarning]: 'Incorrect type. Expected "{0}".',
[ProblemType.constWarning]: 'Value must be {0}.',
};

export interface IProblem {
location: IRange;
severity: DiagnosticSeverity;
Expand Down Expand Up @@ -158,6 +159,7 @@ export abstract class ASTNodeImpl {
export class NullASTNodeImpl extends ASTNodeImpl implements NullASTNode {
public type: 'null' = 'null' as const;
public value = null;

constructor(parent: ASTNode, internalNode: Node, offset: number, length?: number) {
super(parent, internalNode, offset, length);
}
Expand Down Expand Up @@ -278,27 +280,36 @@ export enum EnumMatch {

export interface ISchemaCollector {
schemas: IApplicableSchema[];

add(schema: IApplicableSchema): void;

merge(other: ISchemaCollector): void;

include(node: ASTNode): boolean;

newSub(): ISchemaCollector;
}

class SchemaCollector implements ISchemaCollector {
schemas: IApplicableSchema[] = [];

constructor(
private focusOffset = -1,
private exclude: ASTNode = null
) {}

add(schema: IApplicableSchema): void {
this.schemas.push(schema);
}

merge(other: ISchemaCollector): void {
this.schemas.push(...other.schemas);
}

include(node: ASTNode): boolean {
return (this.focusOffset === -1 || contains(node, this.focusOffset)) && node !== this.exclude;
}

newSub(): ISchemaCollector {
return new SchemaCollector(-1, this.exclude);
}
Expand All @@ -308,22 +319,27 @@ class NoOpSchemaCollector implements ISchemaCollector {
private constructor() {
// ignore
}

// eslint-disable-next-line @typescript-eslint/no-explicit-any
get schemas(): any[] {
return [];
}

// eslint-disable-next-line @typescript-eslint/no-unused-vars
add(schema: IApplicableSchema): void {
// ignore
}

// eslint-disable-next-line @typescript-eslint/no-unused-vars
merge(other: ISchemaCollector): void {
// ignore
}

// eslint-disable-next-line @typescript-eslint/no-unused-vars
include(node: ASTNode): boolean {
return true;
}

newSub(): ISchemaCollector {
return this;
}
Expand Down Expand Up @@ -616,13 +632,15 @@ export class JSONDocument {
* @param focusOffset offsetValue
* @param exclude excluded Node
* @param didCallFromAutoComplete true if method called from AutoComplete
* @param gracefulMatches true if graceful matching should be done, meaning that if at least one property is validated in a sub schema, it's kept as a candidate
* @returns array of applicable schemas
*/
public getMatchingSchemas(
schema: JSONSchema,
focusOffset = -1,
exclude: ASTNode = null,
didCallFromAutoComplete?: boolean
didCallFromAutoComplete?: boolean,
gracefulMatches?: boolean
): IApplicableSchema[] {
const matchingSchemas = new SchemaCollector(focusOffset, exclude);
if (this.root && schema) {
Expand All @@ -631,17 +649,21 @@ export class JSONDocument {
disableAdditionalProperties: this.disableAdditionalProperties,
uri: this.uri,
callFromAutoComplete: didCallFromAutoComplete,
gracefulMatches: gracefulMatches,
});
}
return matchingSchemas.schemas;
}
}

interface Options {
isKubernetes: boolean;
disableAdditionalProperties: boolean;
uri: string;
callFromAutoComplete?: boolean;
gracefulMatches?: boolean;
}

function validate(
node: ASTNode,
schema: JSONSchema,
Expand All @@ -651,7 +673,7 @@ function validate(
options: Options
// eslint-disable-next-line @typescript-eslint/no-explicit-any
): any {
const { isKubernetes, callFromAutoComplete } = options;
const { isKubernetes, callFromAutoComplete, gracefulMatches } = options;
if (!node) {
return;
}
Expand Down Expand Up @@ -942,6 +964,7 @@ function validate(
});
}
}

function getExclusiveLimit(limit: number | undefined, exclusive: boolean | number | undefined): number | undefined {
if (isNumber(exclusive)) {
return exclusive;
Expand All @@ -951,12 +974,14 @@ function validate(
}
return undefined;
}

function getLimit(limit: number | undefined, exclusive: boolean | number | undefined): number | undefined {
if (!isBoolean(exclusive) || !exclusive) {
return limit;
}
return undefined;
}

const exclusiveMinimum = getExclusiveLimit(schema.minimum, schema.exclusiveMinimum);
if (isNumber(exclusiveMinimum) && val <= exclusiveMinimum) {
validationResult.problems.push({
Expand Down Expand Up @@ -1086,6 +1111,7 @@ function validate(
}
}
}

function _validateArrayNode(
node: ArrayASTNode,
schema: JSONSchema,
Expand Down Expand Up @@ -1247,7 +1273,12 @@ function validate(
for (const propertyName of schema.required) {
if (seenKeys[propertyName] === undefined) {
const keyNode = node.parent && node.parent.type === 'property' && node.parent.keyNode;
const location = keyNode ? { offset: keyNode.offset, length: keyNode.length } : { offset: node.offset, length: 1 };
const location = keyNode
? { offset: keyNode.offset, length: keyNode.length }
: {
offset: node.offset,
length: 1,
};
validationResult.problems.push({
location: location,
severity: DiagnosticSeverity.Warning,
Expand Down Expand Up @@ -1490,10 +1521,14 @@ function validate(
return bestMatch;
}

function gracefulMatchFilter(maxOneMatch: boolean, propertiesValueMatches: number): boolean {
return gracefulMatches && !maxOneMatch && callFromAutoComplete && propertiesValueMatches > 0;
}

//genericComparison tries to find the best matching schema using a generic comparison
function genericComparison(
node: ASTNode,
maxOneMatch,
maxOneMatch: boolean,
subValidationResult: ValidationResult,
bestMatch: IValidationMatch,
subSchema,
Expand Down Expand Up @@ -1522,7 +1557,8 @@ function validate(
};
} else if (
compareResult === 0 ||
((node.value === null || node.type === 'null') && node.length === 0) // node with no value can match any schema potentially
((node.value === null || node.type === 'null') && node.length === 0) || // node with no value can match any schema potentially
gracefulMatchFilter(maxOneMatch, subValidationResult.propertiesValueMatches)
) {
// there's already a best matching but we are as good
mergeValidationMatches(bestMatch, subMatchingSchemas, subValidationResult);
Expand Down
23 changes: 16 additions & 7 deletions src/languageservice/services/yamlCompletion.ts
Original file line number Diff line number Diff line change
Expand Up @@ -99,7 +99,13 @@ export class YamlCompletion {
this.parentSkeletonSelectedFirst = languageSettings.parentSkeletonSelectedFirst;
}

async doComplete(document: TextDocument, position: Position, isKubernetes = false, doComplete = true): Promise<CompletionList> {
async doComplete(
document: TextDocument,
position: Position,
isKubernetes = false,
doComplete = true,
gracefulMatches = false
): Promise<CompletionList> {
const result = CompletionList.create([], false);
if (!this.completionEnabled) {
return result;
Expand Down Expand Up @@ -534,7 +540,8 @@ export class YamlCompletion {
collector,
textBuffer,
overwriteRange,
doComplete
doComplete,
gracefulMatches
);

if (!schema && currentWord.length > 0 && text.charAt(offset - currentWord.length - 1) !== '"') {
Expand All @@ -549,7 +556,7 @@ export class YamlCompletion {

// proposals for values
const types: { [type: string]: boolean } = {};
this.getValueCompletions(schema, currentDoc, node, offset, document, collector, types, doComplete);
this.getValueCompletions(schema, currentDoc, node, offset, document, collector, types, doComplete, gracefulMatches);
} catch (err) {
this.telemetry?.sendError('yaml.completion.error', err);
}
Expand Down Expand Up @@ -689,9 +696,10 @@ export class YamlCompletion {
collector: CompletionsCollector,
textBuffer: TextBuffer,
overwriteRange: Range,
doComplete: boolean
doComplete: boolean,
gracefulMatches: boolean
): void {
const matchingSchemas = doc.getMatchingSchemas(schema.schema, -1, null, doComplete);
const matchingSchemas = doc.getMatchingSchemas(schema.schema, -1, null, doComplete, gracefulMatches);
const existingKey = textBuffer.getText(overwriteRange);
const lineContent = textBuffer.getLineContent(overwriteRange.start.line);
const hasOnlyWhitespace = lineContent.trim().length === 0;
Expand Down Expand Up @@ -891,7 +899,8 @@ export class YamlCompletion {
document: TextDocument,
collector: CompletionsCollector,
types: { [type: string]: boolean },
doComplete: boolean
doComplete: boolean,
gracefulMatches: boolean
): void {
let parentKey: string = null;

Expand All @@ -915,7 +924,7 @@ export class YamlCompletion {

if (node && (parentKey !== null || isSeq(node))) {
const separatorAfter = '';
const matchingSchemas = doc.getMatchingSchemas(schema.schema, -1, null, doComplete);
const matchingSchemas = doc.getMatchingSchemas(schema.schema, -1, null, doComplete, gracefulMatches);
for (const s of matchingSchemas) {
if (s.node.internalNode === node && !s.inverted && s.schema) {
if (s.schema.items) {
Expand Down
8 changes: 7 additions & 1 deletion src/languageservice/yamlLanguageService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -160,7 +160,13 @@ export interface CustomFormatterOptions {
export interface LanguageService {
configure: (settings: LanguageSettings) => void;
registerCustomSchemaProvider: (schemaProvider: CustomSchemaProvider) => void;
doComplete: (document: TextDocument, position: Position, isKubernetes: boolean) => Promise<CompletionList>;
doComplete: (
document: TextDocument,
position: Position,
isKubernetes: boolean,
doComplete?: boolean,
gracefulMatches?: boolean
) => Promise<CompletionList>;
doValidation: (document: TextDocument, isKubernetes: boolean) => Promise<Diagnostic[]>;
doHover: (document: TextDocument, position: Position) => Promise<Hover | null>;
findDocumentSymbols: (document: TextDocument, context?: DocumentSymbolsContext) => SymbolInformation[];
Expand Down
Loading