diff --git a/CHANGELOG.md b/CHANGELOG.md index 71c0434..5c9f28e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,8 @@ All notable changes to this project will be documented in this file. ## [Unreleased] ### Added +- textdocument/documentlink + - create links to hub.docker.com for base image names in FROM ([#204](https://github.com/rcjsuen/dockerfile-language-server-nodejs/issues/204)) - textDocument/publishDiagnostics - flag HEALTHCHECK durations that include a hyphen as an error ([rcjsuen/dockerfile-utils#18](https://github.com/rcjsuen/dockerfile-utils/issues/18)) - warn if ADD has more than two arguments and its last argument is not a directory ([rcjsuen/dockerfile-utils#17](https://github.com/rcjsuen/dockerfile-utils/issues/17)) diff --git a/README.md b/README.md index 7e62dc8..c6890a0 100644 --- a/README.md +++ b/README.md @@ -11,6 +11,7 @@ To [install and run](#installation-instructions) this language server, you will - definition - diagnostics - document highlight +- document links - document symbols - formatting - hovers diff --git a/src/dockerLinks.ts b/src/dockerLinks.ts new file mode 100644 index 0000000..fa955dc --- /dev/null +++ b/src/dockerLinks.ts @@ -0,0 +1,33 @@ +/* -------------------------------------------------------------------------------------------- + * Copyright (c) Remy Suen. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + * ------------------------------------------------------------------------------------------ */ +'use strict'; + +import { TextDocument, DocumentLink } from 'vscode-languageserver'; +import { DockerfileParser } from 'dockerfile-ast'; + +export class DockerLinks { + + public getLinks(document: TextDocument): DocumentLink[] { + let dockerfile = DockerfileParser.parse(document.getText()); + let links = []; + for (let from of dockerfile.getFROMs()) { + let name = from.getImageName(); + if (name) { + if (name.indexOf('/') === -1) { + links.push({ + range: from.getImageNameRange(), + target: "https://hub.docker.com/_/" + name + "/" + }); + } else { + links.push({ + range: from.getImageNameRange(), + target: "https://hub.docker.com/r/" + name + "/" + }); + } + } + } + return links; + } +} diff --git a/src/server.ts b/src/server.ts index 937211e..a5ae6c7 100644 --- a/src/server.ts +++ b/src/server.ts @@ -12,7 +12,7 @@ import { DocumentFormattingParams, DocumentRangeFormattingParams, DocumentOnTypeFormattingParams, DocumentHighlight, RenameParams, WorkspaceEdit, Location, DidChangeTextDocumentParams, DidOpenTextDocumentParams, DidCloseTextDocumentParams, TextDocumentContentChangeEvent, - DidChangeConfigurationNotification, ProposedFeatures + DidChangeConfigurationNotification, ProposedFeatures, DocumentLinkParams, DocumentLink } from 'vscode-languageserver'; import { ConfigurationItem } from 'vscode-languageserver-protocol/lib/protocol.configuration.proposed'; import { format, validate, ValidatorSettings, ValidationSeverity } from 'dockerfile-utils'; @@ -28,6 +28,7 @@ import { DockerHighlight } from './dockerHighlight'; import { DockerRename } from './dockerRename'; import { DockerDefinition } from './dockerDefinition'; import { DockerRegistryClient } from './dockerRegistryClient'; +import { DockerLinks } from './dockerLinks'; let markdown = new MarkdownDocumentation(); let hoverProvider = new DockerHover(markdown); @@ -37,6 +38,7 @@ let formatterProvider = new DockerFormatter(); let definitionProvider = new DockerDefinition(); let documentationResolver = new PlainTextDocumentation(); let signatureHelp = new DockerSignatures(); +let linksProvider = new DockerLinks(); /** * The settings to use for the validator if the client doesn't support @@ -133,6 +135,9 @@ connection.onInitialize((params: InitializeParams): InitializeResult => { ' ', '=' ] + }, + documentLinkProvider: { + resolveProvider: false } } } @@ -415,6 +420,14 @@ connection.onDocumentOnTypeFormatting((onTypeFormattingParams: DocumentOnTypeFor return []; }); +connection.onDocumentLinks((documentLinkParams: DocumentLinkParams): DocumentLink[] => { + let document = documents[documentLinkParams.textDocument.uri]; + if (document) { + return linksProvider.getLinks(document); + } + return null; +}); + connection.onDidOpenTextDocument((didOpenTextDocumentParams: DidOpenTextDocumentParams): void => { let document = TextDocument.create(didOpenTextDocumentParams.textDocument.uri, didOpenTextDocumentParams.textDocument.languageId, didOpenTextDocumentParams.textDocument.version, didOpenTextDocumentParams.textDocument.text); documents[didOpenTextDocumentParams.textDocument.uri] = document; diff --git a/test/dockerLinks.test.ts b/test/dockerLinks.test.ts new file mode 100644 index 0000000..6c0b128 --- /dev/null +++ b/test/dockerLinks.test.ts @@ -0,0 +1,66 @@ +/* -------------------------------------------------------------------------------------------- + * Copyright (c) Remy Suen. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + * ------------------------------------------------------------------------------------------ */ +import * as assert from "assert"; + +import { TextDocument, DocumentLink } from 'vscode-languageserver'; +import { DockerLinks } from '../src/dockerLinks'; + +let linksProvider = new DockerLinks(); + +function createDocument(content: string): any { + return TextDocument.create("uri://host/Dockerfile.sample", "dockerfile", 1, content); +} + +function assertLink(documentLink: DocumentLink, target: string, startLine: number, startCharacter: number, endLine: number, endCharacter: number) { + assert.equal(documentLink.target, target); + assert.equal(documentLink.range.start.line, startLine); + assert.equal(documentLink.range.start.character, startCharacter); + assert.equal(documentLink.range.end.line, endLine); + assert.equal(documentLink.range.end.character, endCharacter); +} + +describe("Dockerfile links", function() { + it("FROM node", function() { + let document = createDocument("FROM node"); + let links = linksProvider.getLinks(document); + assert.equal(links.length, 1); + assertLink(links[0], "https://hub.docker.com/_/node/", 0, 5, 0, 9); + }); + + it("FROM node:latest", function() { + let document = createDocument("FROM node:latest"); + let links = linksProvider.getLinks(document); + assert.equal(links.length, 1); + assertLink(links[0], "https://hub.docker.com/_/node/", 0, 5, 0, 9); + }); + + it("FROM node@sha256:613685c22f65d01f2264bdd49b8a336488e14faf29f3ff9b6bf76a4da23c4700", function() { + let document = createDocument("FROM node@sha256:613685c22f65d01f2264bdd49b8a336488e14faf29f3ff9b6bf76a4da23c4700"); + let links = linksProvider.getLinks(document); + assert.equal(links.length, 1); + assertLink(links[0], "https://hub.docker.com/_/node/", 0, 5, 0, 9); + }); + + it("FROM microsoft/dotnet", function() { + let document = createDocument("FROM microsoft/dotnet"); + let links = linksProvider.getLinks(document); + assert.equal(links.length, 1); + assertLink(links[0], "https://hub.docker.com/r/microsoft/dotnet/", 0, 5, 0, 21); + }); + + it("FROM microsoft/dotnet:sdk", function() { + let document = createDocument("FROM microsoft/dotnet:sdk"); + let links = linksProvider.getLinks(document); + assert.equal(links.length, 1); + assertLink(links[0], "https://hub.docker.com/r/microsoft/dotnet/", 0, 5, 0, 21); + }); + + it("FROM microsoft/dotnet@sha256:5483e2b609c0f66c3ebd96666de7b0a74537613b43565879ecb0d0a73e845d7d", function() { + let document = createDocument("FROM microsoft/dotnet@sha256:5483e2b609c0f66c3ebd96666de7b0a74537613b43565879ecb0d0a73e845d7d"); + let links = linksProvider.getLinks(document); + assert.equal(links.length, 1); + assertLink(links[0], "https://hub.docker.com/r/microsoft/dotnet/", 0, 5, 0, 21); + }); +});