Skip to content

Commit

Permalink
Fix #122 Allow navigating definitions of heredocs
Browse files Browse the repository at this point in the history
Add support for jumping to a heredoc's declared delimiter.

Signed-off-by: Remy Suen <[email protected]>
  • Loading branch information
rcjsuen committed Sep 17, 2023
1 parent 9b647d6 commit 50d0912
Show file tree
Hide file tree
Showing 3 changed files with 150 additions and 3 deletions.
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,10 @@
# Changelog
All notable changes to this project will be documented in this file.

## [Unreleased]
### Added
- support navigating the definitions of heredocs ([#122](https://github.com/rcjsuen/dockerfile-language-service/issues/122))

## [0.11.0] - 2023-09-10
### Added
- support parsing the new `--start-interval` flag for HEALTHCHECK instructions ([rcjsuen/dockerfile-utils#115](https://github.com/rcjsuen/dockerfile-utils/issues/115))
Expand Down
38 changes: 36 additions & 2 deletions src/dockerDefinition.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import { Position, Range, Location, TextDocumentIdentifier } from 'vscode-langua
import { Util } from './docker';
import {
DockerfileParser, Dockerfile, ImageTemplate,
Property, Arg, Env
Arg, Env, Heredoc, Property, Copy, Run
} from 'dockerfile-ast';

export class DockerDefinition {
Expand Down Expand Up @@ -127,6 +127,37 @@ export class DockerDefinition {
return DockerDefinition.computeVariableDefinition(image, position);
}

private static checkHeredocs(heredocs: Heredoc[], position: Position): Range | null {
for (const heredoc of heredocs) {
const nameRange = heredoc.getNameRange();
if (Util.isInsideRange(position, nameRange)) {
return Util.isEmpty(nameRange) ? null : nameRange;

Check failure on line 134 in src/dockerDefinition.ts

View workflow job for this annotation

GitHub Actions / build (16.x)

Property 'isEmpty' does not exist on type 'typeof Util'.

Check failure on line 134 in src/dockerDefinition.ts

View workflow job for this annotation

GitHub Actions / build (18.x, LTS)

Property 'isEmpty' does not exist on type 'typeof Util'.

Check failure on line 134 in src/dockerDefinition.ts

View workflow job for this annotation

GitHub Actions / build (20.x)

Property 'isEmpty' does not exist on type 'typeof Util'.
}
const delimiterRange = heredoc.getDelimiterRange();
if (delimiterRange !== null && Util.isInsideRange(position, delimiterRange)) {
return nameRange;
}
}
return null;
}

private static computeHeredocDefinition(dockerfile: Dockerfile, position: Position): Range | null {
for (const instruction of dockerfile.getInstructions()) {
if (instruction instanceof Copy) {
const range = DockerDefinition.checkHeredocs(instruction.getHeredocs(), position);
if (range !== null) {
return range;
}
} else if (instruction instanceof Run) {
const range = DockerDefinition.checkHeredocs(instruction.getHeredocs(), position);
if (range !== null) {
return range;
}
}
}
return null;
}

private computeVariableDefinition(dockerfile: Dockerfile, position: Position): Range | null {
const property = DockerDefinition.findDefinition(dockerfile, position);
return property ? property.getNameRange() : null;
Expand All @@ -148,7 +179,10 @@ export class DockerDefinition {
if (range !== null) {
return Location.create(textDocument.uri, range);
}

range = DockerDefinition.computeHeredocDefinition(dockerfile, position);
if (range !== null) {
return Location.create(textDocument.uri, range);
}
return null;
}
}
111 changes: 110 additions & 1 deletion test/dockerDefinition.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
* ------------------------------------------------------------------------------------------ */
import * as assert from "assert";

import { Position, Location, TextDocumentIdentifier } from 'vscode-languageserver-types';
import { Location, Position, TextDocumentIdentifier } from 'vscode-languageserver-types';
import { DockerfileLanguageServiceFactory } from '../src/main';

const URI = "uri://host/Dockerfile.sample";
Expand All @@ -23,6 +23,7 @@ function findDefinition(content: string, line: number, character: number): Locat
}

function assertLocation(location: Location, startLine: number, startCharacter: number, endLine: number, endCharacter: number) {
assert.notStrictEqual(location, null);
assert.strictEqual(location.uri, URI);
assert.strictEqual(location.range.start.line, startLine);
assert.strictEqual(location.range.start.character, startCharacter);
Expand Down Expand Up @@ -1816,4 +1817,112 @@ describe("Dockerfile Document Definition tests", function () {
});
});
});

function createRegularHeredocTests(instruction: string, offset: number) {
const tests = [
{
testName: `<<file`,
content: `FROM alpine\n${instruction} echo <<file\nabc\nfile`,
offset: 8
},
{
testName: `<<-file`,
content: `FROM alpine\n${instruction} echo <<-file\nabc\nfile`,
offset: 9
},
{
testName: `<<'file'`,
content: `FROM alpine\n${instruction} echo <<'file'\nabc\nfile`,
offset: 9
},
{
testName: `<<-'file'`,
content: `FROM alpine\n${instruction} echo <<-'file'\nabc\nfile`,
offset: 10
},
{
testName: `<<"file"`,
content: `FROM alpine\n${instruction} echo <<"file"\nabc\nfile`,
offset: 9
},
{
testName: `<<-"file"`,
content: `FROM alpine\n${instruction} echo <<-"file"\nabc\nfile`,
offset: 10
}
];

describe("regular", () => {
tests.forEach((test) => {
describe(test.testName, () => {
it("definition", () => {
const location = findDefinition(test.content, 1, offset + 11);
assertLocation(location, 1, offset + test.offset, 1, offset + test.offset + 4);
});

it("delimiter", () => {
const location = findDefinition(test.content, 3, 2);
assertLocation(location, 1, offset + test.offset, 1, offset + test.offset + 4);
});
});
});
});
}

function createEmptyHeredocTests(instruction: string, offset: number) {
const tests = [
{
testName: `<<`,
content: `FROM alpine\n${instruction} echo <<`,
offset: 8
},
{
testName: `<<-`,
content: `FROM alpine\n${instruction} echo <<-`,
offset: 9
},
{
testName: `<<''`,
content: `FROM alpine\n${instruction} echo <<''`,
offset: 9
},
{
testName: `<<''`,
content: `FROM alpine\n${instruction} echo <<-''`,
offset: 10
},
{
testName: `<<""`,
content: `FROM alpine\n${instruction} echo <<""`,
offset: 9
},
{
testName: `<<-""`,
content: `FROM alpine\n${instruction} echo <<-""`,
offset: 10
}
];

describe("empty", () => {
tests.forEach((test) => {
it(test.testName, () => {
const location = findDefinition(test.content, 1, offset + test.offset);
assert.strictEqual(location, null);
});
});
});
}

function createHeredocTests(instruction: string) {
describe(instruction, () => {
const offset = instruction.length;
createRegularHeredocTests(instruction, offset);
createEmptyHeredocTests(instruction, offset);
});
}

describe("Heredoc", () => {
createHeredocTests("COPY");
createHeredocTests("RUN");
});
});

0 comments on commit 50d0912

Please sign in to comment.