Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix: array item with existing property #882

Open
wants to merge 4 commits into
base: main
Choose a base branch
from
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
38 changes: 23 additions & 15 deletions src/languageservice/services/yamlCompletion.ts
Original file line number Diff line number Diff line change
Expand Up @@ -144,11 +144,11 @@ export class YamlCompletion {

this.arrayPrefixIndentation = '';
let overwriteRange: Range = null;
const isOnlyHyphen = lineContent.match(/^\s*(-)\s*($|#)/);
if (areOnlySpacesAfterPosition) {
overwriteRange = Range.create(position, Position.create(position.line, lineContent.length));
const isOnlyWhitespace = lineContent.trim().length === 0;
const isOnlyDash = lineContent.match(/^\s*(-)\s*$/);
if (node && isScalar(node) && !isOnlyWhitespace && !isOnlyDash) {
if (node && isScalar(node) && !isOnlyWhitespace && !isOnlyHyphen) {
const lineToPosition = lineContent.substring(0, position.character);
const matches =
// get indentation of unfinished property (between indent and cursor)
Expand All @@ -162,6 +162,8 @@ export class YamlCompletion {
Position.create(position.line, lineContent.length)
);
}
} else if (node && isScalar(node) && node.value === null && currentWord === '-') {
this.arrayPrefixIndentation = ' ';
}
} else if (node && isScalar(node) && node.value === 'null') {
const nodeStartPos = document.positionAt(node.range[0]);
Expand Down Expand Up @@ -357,6 +359,12 @@ export class YamlCompletion {
if (node) {
if (lineContent.length === 0) {
node = currentDoc.internalDocument.contents as Node;
} else if (isSeq(node) && isOnlyHyphen) {
const index = this.findItemAtOffset(node, document, offset);
const item = node.items[index];
if (isNode(item)) {
node = item;
}
} else {
const parent = currentDoc.getParent(node);
if (parent) {
Expand Down Expand Up @@ -717,19 +725,19 @@ export class YamlCompletion {
const propertySchema = schemaProperties[key];

if (typeof propertySchema === 'object' && !propertySchema.deprecationMessage && !propertySchema['doNotSuggest']) {
let identCompensation = '';
let indentCompensation = '';
if (nodeParent && isSeq(nodeParent) && node.items.length <= 1 && !hasOnlyWhitespace) {
// because there is a slash '-' to prevent the properties generated to have the correct
// indent
const sourceText = textBuffer.getText();
const indexOfSlash = sourceText.lastIndexOf('-', node.range[0] - 1);
if (indexOfSlash >= 0) {
// add one space to compensate the '-'
const overwriteChars = overwriteRange.end.character - overwriteRange.start.character;
identCompensation = ' ' + sourceText.slice(indexOfSlash + 1, node.range[1] - overwriteChars);
// because there is a slash '-' to prevent the properties generated to have the correct indent
const fromLastHyphenToPosition = lineContent.slice(
lineContent.lastIndexOf('-'),
overwriteRange.start.character
);
const hyphenFollowedByEmpty = fromLastHyphenToPosition.match(/-([ \t]*)/);
if (hyphenFollowedByEmpty) {
indentCompensation = ' ' + hyphenFollowedByEmpty[1];
}
}
identCompensation += this.arrayPrefixIndentation;
indentCompensation += this.arrayPrefixIndentation;

// if check that current node has last pair with "null" value and key witch match key from schema,
// and if schema has array definition it add completion item for array item creation
Expand Down Expand Up @@ -760,7 +768,7 @@ export class YamlCompletion {
key,
propertySchema,
separatorAfter,
identCompensation + this.indentation
indentCompensation + this.indentation
);
}
const isNodeNull =
Expand All @@ -787,13 +795,13 @@ export class YamlCompletion {
key,
propertySchema,
separatorAfter,
identCompensation + this.indentation
indentCompensation + this.indentation
),
insertTextFormat: InsertTextFormat.Snippet,
documentation: this.fromMarkup(propertySchema.markdownDescription) || propertySchema.description || '',
parent: {
schema: schema.schema,
indent: identCompensation,
indent: indentCompensation,
},
});
}
Expand Down
133 changes: 133 additions & 0 deletions test/autoCompletionFix.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -865,6 +865,30 @@ objB:
})
);
});
it('indent compensation for partial key with trailing spaces', async () => {
const schema: JSONSchema = {
type: 'object',
properties: {
array: {
type: 'array',
items: {
type: 'object',
properties: {
obj1: {
type: 'object',
},
},
},
},
},
};
schemaProvider.addSchema(SCHEMA_ID, schema);
const content = 'array:\n - obj| | ';
const completion = await parseCaret(content);

expect(completion.items.length).equal(1);
expect(completion.items[0].insertText).eql('obj1:\n ');
});

describe('partial value with trailing spaces', () => {
it('partial value with trailing spaces', async () => {
Expand Down Expand Up @@ -1085,6 +1109,77 @@ objB:
expect(result.items.length).to.be.equal(1);
expect(result.items[0].insertText).to.be.equal('objA:\n itemA: ');
});

it('array completion - should suggest correct indent when extra spaces after cursor followed by with different array item', async () => {
schemaProvider.addSchema(SCHEMA_ID, {
type: 'object',
properties: {
test: {
type: 'array',
items: {
type: 'object',
properties: {
objA: {
type: 'object',
required: ['itemA'],
properties: {
itemA: {
type: 'string',
},
},
},
},
},
},
},
});
const content = `
test:
- | |
- objA:
itemA: test`;
const result = await parseCaret(content);

expect(result.items.length).to.be.equal(1);
expect(result.items[0].insertText).to.be.equal('objA:\n itemA: ');
});

it('array completion - should suggest correct indent when cursor is just after hyphen with trailing spaces', async () => {
schemaProvider.addSchema(SCHEMA_ID, {
type: 'object',
properties: {
test: {
type: 'array',
items: {
type: 'object',
properties: {
objA: {
type: 'object',
required: ['itemA'],
properties: {
itemA: {
type: 'string',
},
},
},
},
},
},
},
});
const content = `
test:
-| |
`;
const result = await parseCaret(content);

expect(result.items.length).to.be.equal(1);
expect(result.items[0].textEdit).to.deep.equal({
newText: ' objA:\n itemA: ',
// range should contains all the trailing spaces
range: Range.create(2, 3, 2, 9),
});
});
it('array of arrays completion - should suggest correct indent when extra spaces after cursor', async () => {
schemaProvider.addSchema(SCHEMA_ID, {
type: 'object',
Expand Down Expand Up @@ -1161,6 +1256,44 @@ objB:
expect(result.items.length).to.be.equal(1);
expect(result.items[0].insertText).to.be.equal('objA:\n itemA: ');
});

describe('array item with existing property', () => {
const schema: JSONSchema = {
type: 'object',
properties: {
array1: {
type: 'array',
items: {
type: 'object',
properties: {
objA: {
type: 'object',
},
propB: {
const: 'test',
},
},
},
},
},
};
it('should get extra space compensation for the 1st prop in array object item', async () => {
schemaProvider.addSchema(SCHEMA_ID, schema);
const content = 'array1:\n - |\n| propB: test';
const result = await parseCaret(content);

expect(result.items.length).to.be.equal(1);
expect(result.items[0].insertText).to.be.equal('objA:\n ');
});
it('should get extra space compensation for the 1st prop in array object item - extra spaces', async () => {
schemaProvider.addSchema(SCHEMA_ID, schema);
const content = 'array1:\n - | | \n propB: test';
const result = await parseCaret(content);

expect(result.items.length).to.be.equal(1);
expect(result.items[0].insertText).to.be.equal('objA:\n ');
});
});
}); //'extra space after cursor'

it('should suggest from additionalProperties', async () => {
Expand Down
89 changes: 81 additions & 8 deletions test/yaml-documents.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -211,16 +211,89 @@ objB:
expect(((result as YAMLMap).items[0].key as Scalar).value).eqls('bar');
});

it('Find closes node: array', () => {
const doc = setupTextDocument('foo:\n - bar: aaa\n ');
const yamlDoc = documents.getYamlDocument(doc);
const textBuffer = new TextBuffer(doc);
describe('Array', () => {
// Problem in `getNodeFromPosition` function. This method doesn't give proper results for arrays
// for example, this yaml return nodes:
// foo:
// - # foo object is returned (should be foo[0])
// # foo object is returned (should be foo[0])
// item1: aaaf
// # foo[0] object is returned (OK)
// - # foo object is returned (should be foo[1])
// # foo[!!0!!] object is returned (should be foo[1])
// item2: bbb
// # foo[1] object is returned (OK)

it('Find closes node: array', () => {
const doc = setupTextDocument('foo:\n - bar: aaa\n ');
const yamlDoc = documents.getYamlDocument(doc);
const textBuffer = new TextBuffer(doc);

const result = yamlDoc.documents[0].findClosestNode(20, textBuffer);

expect(result).is.not.undefined;
expect(isSeq(result)).is.true;
expect((((result as YAMLSeq).items[0] as YAMLMap).items[0].key as Scalar).value).eqls('bar');
});
it.skip('Find first array item node', () => {
const doc = setupTextDocument(`foo:
-
item1: aaa
`);
const yamlDoc = documents.getYamlDocument(doc);
const textBuffer = new TextBuffer(doc);

const result = yamlDoc.documents[0].findClosestNode(9, textBuffer);

expect(result).is.not.undefined;
expect(isMap(result)).is.true;
expect(((result as YAMLMap).items[0].key as Scalar).value).eqls('item1');
});
it.skip('Find first array item node - extra indent', () => {
const doc = setupTextDocument(`foo:
-

item1: aaa
`);
const yamlDoc = documents.getYamlDocument(doc);
const textBuffer = new TextBuffer(doc);

const result = yamlDoc.documents[0].findClosestNode(9, textBuffer);

expect(result).is.not.undefined;
expect(isMap(result)).is.true;
expect(((result as YAMLMap).items[0].key as Scalar).value).eqls('item1');
});

it.skip('Find second array item node', () => {
const doc = setupTextDocument(`foo:
- item1: aaa
-
item2: bbb`);
const yamlDoc = documents.getYamlDocument(doc);
const textBuffer = new TextBuffer(doc);

const result = yamlDoc.documents[0].findClosestNode(24, textBuffer);

expect(result).is.not.undefined;
expect(isMap(result)).is.true;
expect(((result as YAMLMap).items[0].key as Scalar).value).eqls('item2');
});
it.skip('Find second array item node: - extra indent', () => {
const doc = setupTextDocument(`foo:
- item1: aaa
-

item2: bbb`);
const yamlDoc = documents.getYamlDocument(doc);
const textBuffer = new TextBuffer(doc);

const result = yamlDoc.documents[0].findClosestNode(20, textBuffer);
const result = yamlDoc.documents[0].findClosestNode(28, textBuffer);

expect(result).is.not.undefined;
expect(isSeq(result)).is.true;
expect((((result as YAMLSeq).items[0] as YAMLMap).items[0].key as Scalar).value).eqls('bar');
expect(result).is.not.undefined;
expect(isMap(result)).is.true;
expect(((result as YAMLMap).items[0].key as Scalar).value).eqls('item2');
});
});

it('Find closes node: root map', () => {
Expand Down
Loading