Skip to content

Commit

Permalink
WIP #44 Suggest build stage completion items in COPY
Browse files Browse the repository at this point in the history
When copying content from a build stage, users need to enter in the
build stage's into the --from= flag of a COPY instruction. We should
parse the Dockerfile and suggest any build stage names that are found
as completion items.

Signed-off-by: Remy Suen <[email protected]>
  • Loading branch information
rcjsuen committed Jul 16, 2017
1 parent 1e3fc46 commit aa03ea0
Show file tree
Hide file tree
Showing 3 changed files with 83 additions and 12 deletions.
48 changes: 38 additions & 10 deletions src/dockerAssist.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import {
} from 'vscode-languageserver';
import { Util, KEYWORDS, DIRECTIVE_ESCAPE } from './docker';
import { DockerfileParser } from './parser/dockerfileParser';
import { Copy } from './parser/instructions/copy';

export class DockerAssist {

Expand Down Expand Up @@ -68,20 +69,38 @@ export class DockerAssist {

let previousWord = "";

for (let instruction of dockerfile.getInstructions()) {
instructionsCheck: for (let instruction of dockerfile.getInstructions()) {
if (Util.isInsideRange(position, instruction.getInstructionRange())) {
break;
} else if (Util.isInsideRange(position, instruction.getRange())) {
if (instruction.getKeyword() !== "ONBUILD") {
return [];
}

let args = instruction.getArguments();
if (args.length === 0 || Util.isInsideRange(position, args[0].getRange())) {
previousWord = "ONBUILD";
break;
switch (instruction.getKeyword()) {
case "COPY":
let copy = instruction as Copy;
let copyArgs = instruction.getArguments();
if (copyArgs.length !== 0 && copyArgs[0].getValue().indexOf("--from=") === 0 && copy.getFromValueRange().start.character === position.character) {
let items: CompletionItem[] = [];
for (let from of dockerfile.getFROMs()) {
let stage = from.getBuildStage();
if (stage) {
items.push(this.createSourceImageCompletionItem(stage, "", offset));
}
}
items.sort((item: CompletionItem, item2: CompletionItem) => {
return item.label.localeCompare(item2.label);
});
return items;
}
return [];
case "ONBUILD":
let onbuildArgs = instruction.getArguments();
if (onbuildArgs.length === 0 || Util.isInsideRange(position, onbuildArgs[0].getRange())) {
previousWord = "ONBUILD";
break instructionsCheck;
}
return [];
default:
return [];
}
return [];
}
}

Expand Down Expand Up @@ -349,6 +368,15 @@ export class DockerAssist {
};
}

private createSourceImageCompletionItem(label: string, prefix: string, offset: number): CompletionItem {
return {
textEdit: this.createTextEdit(prefix, offset, label),
label: label,
kind: CompletionItemKind.Reference,
insertTextFormat: InsertTextFormat.PlainText,
};
}

createTextEdit(prefix: string, offset: number, content: string): TextEdit {
if (prefix === "") {
return TextEdit.insert(this.document.positionAt(offset), content);
Expand Down
3 changes: 2 additions & 1 deletion src/parser/instructions/from.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,8 @@ export class From extends Instruction {
}

public getBuildStage(): string | null {
return this.getRangeContent(this.getBuildStageRange());
let range = this.getBuildStageRange();
return range === null ? null : this.getRangeContent(range);
}

public getBuildStageRange(): Range | null {
Expand Down
44 changes: 43 additions & 1 deletion test/dockerAssist.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
import * as assert from "assert";

import {
TextDocument, CompletionItem, CompletionItemKind, InsertTextFormat
TextDocument, Position, CompletionItem, CompletionItemKind, InsertTextFormat
} from 'vscode-languageserver';
import { KEYWORDS } from '../src/docker';
import { DockerAssist } from '../src/dockerAssist';
Expand All @@ -23,6 +23,15 @@ function compute(content: string, offset: number, snippetSupport?: boolean): Com
return assist.computeProposals(document, document.positionAt(offset));
}

function computePosition(content: string, line: number, character: number, snippetSupport?: boolean): CompletionItem[] {
if (snippetSupport === undefined) {
snippetSupport = true;
}
let document = createDocument(content);
let assist = new DockerAssist(document, snippetSupport);
return assist.computeProposals(document, Position.create(line, character));
}

function assertOnlyFROM(proposals: CompletionItem[], line: number, number: number, prefixLength: number) {
assert.equal(proposals.length, 1);
assertFROM(proposals[0], line, number, prefixLength);
Expand Down Expand Up @@ -338,6 +347,17 @@ function assertWORKDIR(item: CompletionItem, line: number, character: number, pr
assert.equal(item.textEdit.range.end.character, character + prefixLength);
}

function assertSourceImage(item: CompletionItem, sourceImage: string, startLine: number, startCharacter: number, endLine: number, endCharacter: number) {
assert.equal(item.label, sourceImage);
assert.equal(item.kind, CompletionItemKind.Reference);
assert.equal(item.insertTextFormat, InsertTextFormat.PlainText);
assert.equal(item.textEdit.newText, sourceImage);
assert.equal(item.textEdit.range.start.line, startLine);
assert.equal(item.textEdit.range.start.character, startCharacter);
assert.equal(item.textEdit.range.end.line, endLine);
assert.equal(item.textEdit.range.end.character, endCharacter);
}

function assertOnlyDirectiveEscape(items: CompletionItem[], line: number, character: number, prefixLength: number) {
assert.equal(1, items.length);
assertDirectiveEscape(items[0], line, character, prefixLength);
Expand Down Expand Up @@ -1197,6 +1217,28 @@ describe('Docker Content Assist Tests', function() {
});
})

describe("COPY", function() {
describe("--from=", function() {
it("no sources", function() {
var proposals = computePosition("FROM busybox\nCOPY --from=", 1, 12);
assert.equal(proposals.length, 0);
});

it("source image", function() {
var proposals = computePosition("FROM busybox AS source\nCOPY --from=", 1, 12);
assert.equal(proposals.length, 1);
assertSourceImage(proposals[0], "source", 1, 12, 1, 12);
});

it("source images alphabetical", function() {
var proposals = computePosition("FROM busybox AS setup\nFROM busybox AS dev\nCOPY --from=", 2, 12);
assert.equal(proposals.length, 2);
assertSourceImage(proposals[0], "dev", 2, 12, 2, 12);
assertSourceImage(proposals[1], "setup", 2, 12, 2, 12);
});
});
});

describe('ONBUILD nesting', function() {
it('all', function() {
var proposals = compute("FROM node\nONBUILD ", 18);
Expand Down

0 comments on commit aa03ea0

Please sign in to comment.