From 0b7d6d559643d1fab2ecb56f4e9be248516840e6 Mon Sep 17 00:00:00 2001 From: Shalabh Gupta Date: Thu, 28 May 2026 13:35:19 +0530 Subject: [PATCH 1/3] fix(image-link): parse image refs explicitly and add GHCR/Quay support Refactor the regex-based matcher in ImageLinkProvider into an explicit Docker image-reference parser that follows the registry-resolution rules documented in github.com/distribution/reference: a path component before the first '/' is treated as a registry hostname when it contains '.', contains ':', or is exactly 'localhost'. The previous matchers worked correctly for the public Docker Hub / namespaced / MCR cases but silently produced no link for any image on another registry, even well-known public ones. With the new parser: - Docker Hub official, namespaced, and MCR cases keep their existing link targets and ranges (all 119 prior tests pass unchanged). - ghcr.io//[:tag] now links to the package page on github.com (.../pkgs/container/). - quay.io//[:tag] now links to its repository page. - Unrecognized private registries (e.g. nrt.vultrcr.com/..., localhost:5000/..., registry.gitlab.com/...) explicitly return no link rather than relying on the namespace-regex incidentally rejecting them. Adds three new test cases covering GHCR, Quay, and the unrecognized private-registry scenario from issue #179. Fixes #179 --- src/service/providers/ImageLinkProvider.ts | 170 ++++++++++++++----- src/test/providers/ImageLinkProvider.test.ts | 74 ++++++++ 2 files changed, 205 insertions(+), 39 deletions(-) diff --git a/src/service/providers/ImageLinkProvider.ts b/src/service/providers/ImageLinkProvider.ts index 7a1c3bd..698d593 100644 --- a/src/service/providers/ImageLinkProvider.ts +++ b/src/service/providers/ImageLinkProvider.ts @@ -10,9 +10,128 @@ import { getCurrentContext } from '../utils/ActionContext'; import { yamlRangeToLspRange } from '../utils/yamlRangeToLspRange'; import { ProviderBase } from './ProviderBase'; -const dockerHubImageRegex = /^(?[.\w-]+)(?:[.\w-]+)?$/i; -const dockerHubNamespacedImageRegex = /^(?[a-z0-9]+)\/(?[.\w-]+)(?:[.\w-]+)?$/i; -const mcrImageRegex = /^mcr.microsoft.com\/(?([a-z0-9]+\/)+)(?[.\w-]+)(?:[.\w-]+)?$/i; +// A path component (registry hostname, namespace segment, or repository name). +// Hostnames may include dots; repository names may include dots, underscores, dashes. +const PATH_COMPONENT_REGEX = /^[\w][.\w-]*$/; +const TAG_REGEX = /^[.\w-]+$/; + +interface ParsedImageRef { + /** Registry hostname (e.g. 'ghcr.io', 'mcr.microsoft.com'), or undefined for Docker Hub. */ + registry: string | undefined; + /** Namespace path between registry and repository (e.g. 'library', 'owner', 'dotnet/core'), or undefined. */ + namespace: string | undefined; + /** Repository / image name (e.g. 'alpine', 'sdk'). */ + imageName: string; + /** Optional tag (without the leading colon). */ + tag: string | undefined; + /** Length of everything except the optional ':tag' suffix — i.e. the part the link should cover. */ + pathLength: number; +} + +/** + * A path component before the first `/` is treated as a registry hostname iff it + * contains a `.` (domain), contains a `:` (host:port), or is exactly `localhost`. + * This matches Docker's reference resolution rules. + * @see https://github.com/distribution/reference + */ +function isRegistryHost(component: string): boolean { + return component.includes('.') || component.includes(':') || component === 'localhost'; +} + +export function parseImageRef(image: string): ParsedImageRef | undefined { + if (!image || /\s/.test(image)) { + return undefined; + } + + // Split off optional :tag — the tag is the suffix after the last ':' in the + // final '/'-separated component (so a ':' inside a registry host:port portion + // is not mistaken for a tag separator). + const lastSlash = image.lastIndexOf('/'); + const nameAndTag = lastSlash >= 0 ? image.substring(lastSlash + 1) : image; + const pathPrefix = lastSlash >= 0 ? image.substring(0, lastSlash) : ''; + + const tagColon = nameAndTag.indexOf(':'); + const imageName = tagColon >= 0 ? nameAndTag.substring(0, tagColon) : nameAndTag; + const tag = tagColon >= 0 ? nameAndTag.substring(tagColon + 1) : undefined; + + if (!PATH_COMPONENT_REGEX.test(imageName)) { + return undefined; + } + if (tag !== undefined && !TAG_REGEX.test(tag)) { + return undefined; + } + + const pathLength = image.length - (tag !== undefined ? tag.length + 1 : 0); + + const pathParts = pathPrefix ? pathPrefix.split('/') : []; + + // Reject path components that don't look like valid hostname/namespace segments. + // Registry hosts can include `:` (for port); namespace segments cannot. + for (let i = 0; i < pathParts.length; i++) { + const part = pathParts[i]; + const allowColon = i === 0 && isRegistryHost(part); + const partRegex = allowColon ? /^[\w][.\w:-]*$/ : PATH_COMPONENT_REGEX; + if (!partRegex.test(part)) { + return undefined; + } + } + + let registry: string | undefined; + let namespace: string | undefined; + + if (pathParts.length === 0) { + // Plain `alpine` — Docker Hub official image. + } else if (isRegistryHost(pathParts[0])) { + registry = pathParts[0]; + if (pathParts.length > 1) { + namespace = pathParts.slice(1).join('/'); + } + } else { + // No explicit registry — everything is a Docker Hub namespace path. + // Docker Hub only supports a single namespace level (`user/repo`), so reject deeper paths. + if (pathParts.length > 1) { + return undefined; + } + namespace = pathParts[0]; + } + + return { registry, namespace, imageName, tag, pathLength }; +} + +function buildLinkUri(ref: ParsedImageRef, imageTypes: Set): string | undefined { + // Docker Hub — no registry hostname in the reference. + if (ref.registry === undefined) { + if (ref.namespace === undefined) { + imageTypes.add('dockerHub'); + return `https://hub.docker.com/_/${ref.imageName}`; + } + imageTypes.add('dockerHubNamespaced'); + return `https://hub.docker.com/r/${ref.namespace}/${ref.imageName}`; + } + + // Microsoft Container Registry — images are mirrored to a Docker Hub page + // under the `microsoft--` convention. + if (ref.registry === 'mcr.microsoft.com' && ref.namespace !== undefined) { + imageTypes.add('mcr'); + return `https://hub.docker.com/_/microsoft-${ref.namespace.replace(/\//g, '-')}-${ref.imageName}`; + } + + // GitHub Container Registry — link to the package page on github.com. + // Only the `ghcr.io//` form maps cleanly; deeper paths are skipped. + if (ref.registry === 'ghcr.io' && ref.namespace !== undefined && !ref.namespace.includes('/')) { + imageTypes.add('ghcr'); + return `https://github.com/${ref.namespace}/${ref.imageName}/pkgs/container/${ref.imageName}`; + } + + // Quay.io — link to the public repository page. + if (ref.registry === 'quay.io' && ref.namespace !== undefined && !ref.namespace.includes('/')) { + imageTypes.add('quay'); + return `https://quay.io/repository/${ref.namespace}/${ref.imageName}`; + } + + return undefined; +} + export class ImageLinkProvider extends ProviderBase { public on(params: DocumentLinkParams & ExtendedParams, token: CancellationToken): Promise { @@ -51,43 +170,16 @@ export class ImageLinkProvider extends ProviderBase): { uri: string, start: number, length: number } | undefined { - let match: RegExpExecArray | null; - let namespace: string | undefined; - let imageName: string | undefined; - - if ((match = dockerHubImageRegex.exec(image)) && - (imageName = match.groups?.imageName)) { - - imageTypes.add('dockerHub'); + const ref = parseImageRef(image); + if (!ref) { + return undefined; + } - return { - uri: `https://hub.docker.com/_/${imageName}`, - start: match.index, - length: imageName.length - }; - } else if ((match = dockerHubNamespacedImageRegex.exec(image)) && - (namespace = match.groups?.namespace) && - (imageName = match.groups?.imageName)) { - - imageTypes.add('dockerHubNamespaced'); - - return { - uri: `https://hub.docker.com/r/${namespace}/${imageName}`, - start: match.index, - length: namespace.length + 1 + imageName.length // 1 is the length of the '/' after namespace - }; - } else if ((match = mcrImageRegex.exec(image)) && - (namespace = match.groups?.namespace?.replace(/\/$/, '')) && - (imageName = match.groups?.imageName)) { - - imageTypes.add('mcr'); - - return { - uri: `https://hub.docker.com/_/microsoft-${namespace.replace('/', '-')}-${imageName}`, - start: match.index, - length: 18 + namespace.length + 1 + imageName.length // 18 is the length of 'mcr.microsoft.com/', 1 is the length of the '/' after namespace - }; + const uri = buildLinkUri(ref, imageTypes); + if (!uri) { + return undefined; } - return undefined; + + return { uri, start: 0, length: ref.pathLength }; } } diff --git a/src/test/providers/ImageLinkProvider.test.ts b/src/test/providers/ImageLinkProvider.test.ts index 58e54fe..0c1a4a0 100644 --- a/src/test/providers/ImageLinkProvider.test.ts +++ b/src/test/providers/ImageLinkProvider.test.ts @@ -139,6 +139,80 @@ services: await requestImageLinksAndCompare(testConnection, uri, expected); }); + it('Should provide links for GitHub Container Registry images', async () => { + const testObject = { + services: { + a: { + image: 'ghcr.io/microsoft/playwright-mcp' + }, + b: { + image: 'ghcr.io/owner/repo:v1.2.3' + }, + } + }; + + const expected = [ + { + range: Range.create(2, 11, 2, 43), + target: 'https://github.com/microsoft/playwright-mcp/pkgs/container/playwright-mcp' + }, + { + range: Range.create(4, 11, 4, 29), + target: 'https://github.com/owner/repo/pkgs/container/repo' + }, + ]; + + const uri = testConnection.sendObjectAsYamlDocument(testObject); + await requestImageLinksAndCompare(testConnection, uri, expected); + }); + + it('Should provide links for Quay.io images', async () => { + const testObject = { + services: { + a: { + image: 'quay.io/prometheus/node-exporter' + }, + b: { + image: 'quay.io/coreos/etcd:v3.5.0' + }, + } + }; + + const expected = [ + { + range: Range.create(2, 11, 2, 43), + target: 'https://quay.io/repository/prometheus/node-exporter' + }, + { + range: Range.create(4, 11, 4, 30), + target: 'https://quay.io/repository/coreos/etcd' + }, + ]; + + const uri = testConnection.sendObjectAsYamlDocument(testObject); + await requestImageLinksAndCompare(testConnection, uri, expected); + }); + + it('Should NOT provide links for unrecognized private registries', async () => { + // Reproduces the scenario reported in https://github.com/microsoft/compose-language-service/issues/179 + const testObject = { + services: { + a: { + image: 'nrt.vultrcr.com/wulicoco/code-sync' + }, + b: { + image: 'localhost:5000/myimg' + }, + c: { + image: 'registry.gitlab.com/group/project/image' + }, + } + }; + + const uri = testConnection.sendObjectAsYamlDocument(testObject); + await requestImageLinksAndCompare(testConnection, uri, []); + }); + it('Should NOT provide links for services with `build` section', async () => { const testObject = { services: { From 99ae5693be5244a0ecc773d29c98f9e4b7293529 Mon Sep 17 00:00:00 2001 From: "Brandon Waterloo [MSFT]" <36966225+bwateratmsft@users.noreply.github.com> Date: Thu, 16 Jul 2026 11:47:41 -0400 Subject: [PATCH 2/3] fix(image-link): disable delegated imageLinks and cover registry edge cases Set imageLinks to false in AlternateYamlLanguageServiceClientFeature so Compose LS always provides its own image links rather than delegating to the Docker/Container Tools extension, which incorrectly links private registries to Docker Hub (see #179). Add ImageLinkProvider tests for GHCR/Quay refs with a deeper namespace and for registry refs without a namespace, both of which should produce no link. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: 55b29a5c-6dcc-41bf-8893-1772fe221ab7 --- src/test/providers/ImageLinkProvider.test.ts | 36 +++++++++++++++++++ ...ternateYamlLanguageServiceClientFeature.ts | 2 +- 2 files changed, 37 insertions(+), 1 deletion(-) diff --git a/src/test/providers/ImageLinkProvider.test.ts b/src/test/providers/ImageLinkProvider.test.ts index 0c1a4a0..c99a842 100644 --- a/src/test/providers/ImageLinkProvider.test.ts +++ b/src/test/providers/ImageLinkProvider.test.ts @@ -256,6 +256,42 @@ services: await requestImageLinksAndCompare(testConnection, uri, []); }); + it('Should NOT provide links for GHCR or Quay images with a deeper namespace', async () => { + // GHCR/Quay pages only map cleanly for a single `/` form + const testObject = { + services: { + a: { + image: 'ghcr.io/owner/team/repo' + }, + b: { + image: 'quay.io/org/team/repo:v1.0.0' + }, + } + }; + + const uri = testConnection.sendObjectAsYamlDocument(testObject); + await requestImageLinksAndCompare(testConnection, uri, []); + }); + + it('Should NOT provide links for registry images without a namespace', async () => { + const testObject = { + services: { + a: { + image: 'ghcr.io/repo' + }, + b: { + image: 'quay.io/repo' + }, + c: { + image: 'mcr.microsoft.com/sdk' + }, + } + }; + + const uri = testConnection.sendObjectAsYamlDocument(testObject); + await requestImageLinksAndCompare(testConnection, uri, []); + }); + it('Should NOT provide links for services without image tag', async () => { const testObject = { services: { diff --git a/src/vscode/AlternateYamlLanguageServiceClientFeature.ts b/src/vscode/AlternateYamlLanguageServiceClientFeature.ts index 2094a97..18e66c7 100644 --- a/src/vscode/AlternateYamlLanguageServiceClientFeature.ts +++ b/src/vscode/AlternateYamlLanguageServiceClientFeature.ts @@ -33,7 +33,7 @@ export class AlternateYamlLanguageServiceClientFeature implements StaticFeature, basicCompletions: redhat || docker, advancedCompletions: false, // The other extensions do not have advanced completions for Compose docs hover: redhat || docker, // Compose spec has descriptions - imageLinks: docker, // Docker's extension supports Docker Hub, GHCR, MAR, and Quay.io + imageLinks: false, // Keep Compose image links local so private registries aren't incorrectly linked to Docker Hub (see #179) serviceStartupCodeLens: false, // The other extensions do not provide any code lens formatting: false, // The other extensions do support formatting, but we enable it regardless so that an explicitly-chosen formatter always works }; From dc3f29d722b99929d4183802fe3b065d0428eb68 Mon Sep 17 00:00:00 2001 From: "Brandon Waterloo [MSFT]" <36966225+bwateratmsft@users.noreply.github.com> Date: Thu, 16 Jul 2026 12:04:59 -0400 Subject: [PATCH 3/3] fix(image-link): support docker.io/digest refs and case-insensitive registries Address panel-review findings on the image-link parser: - Emit Docker Hub links for explicit hostnames (docker.io, index.docker.io, registry-1.docker.io), matching Docker's familiar-name normalization. - Parse digest-pinned refs (image@sha256:...), excluding the digest from the highlighted link range. - Match known registries case-insensitively, and treat an uppercase first component as a registry host so private registries aren't mislinked to Docker Hub (see #179). - Tighten positive test assertions to exact membership and add coverage for the above cases. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: 55b29a5c-6dcc-41bf-8893-1772fe221ab7 --- src/service/providers/ImageLinkProvider.ts | 50 ++++++--- src/test/providers/ImageLinkProvider.test.ts | 108 ++++++++++++++++++- 2 files changed, 145 insertions(+), 13 deletions(-) diff --git a/src/service/providers/ImageLinkProvider.ts b/src/service/providers/ImageLinkProvider.ts index 698d593..7549ebb 100644 --- a/src/service/providers/ImageLinkProvider.ts +++ b/src/service/providers/ImageLinkProvider.ts @@ -14,6 +14,11 @@ import { ProviderBase } from './ProviderBase'; // Hostnames may include dots; repository names may include dots, underscores, dashes. const PATH_COMPONENT_REGEX = /^[\w][.\w-]*$/; const TAG_REGEX = /^[.\w-]+$/; +const DIGEST_REGEX = /^[\w.+-]+:[0-9a-f]+$/i; + +// Docker Hub's explicit registry hostnames. A reference using any of these is treated +// the same as a bare Docker Hub reference (e.g. `docker.io/library/alpine` === `library/alpine`). +const DOCKER_HUB_REGISTRIES = new Set(['docker.io', 'index.docker.io', 'registry-1.docker.io']); interface ParsedImageRef { /** Registry hostname (e.g. 'ghcr.io', 'mcr.microsoft.com'), or undefined for Docker Hub. */ @@ -30,12 +35,14 @@ interface ParsedImageRef { /** * A path component before the first `/` is treated as a registry hostname iff it - * contains a `.` (domain), contains a `:` (host:port), or is exactly `localhost`. - * This matches Docker's reference resolution rules. + * contains a `.` (domain), contains a `:` (host:port), is exactly `localhost`, or + * contains an uppercase character (repository namespaces are always lowercase, so an + * uppercase first component signals a registry host). This matches Docker's reference + * resolution rules. * @see https://github.com/distribution/reference */ function isRegistryHost(component: string): boolean { - return component.includes('.') || component.includes(':') || component === 'localhost'; + return component.includes('.') || component.includes(':') || component === 'localhost' || component !== component.toLowerCase(); } export function parseImageRef(image: string): ParsedImageRef | undefined { @@ -43,12 +50,22 @@ export function parseImageRef(image: string): ParsedImageRef | undefined { return undefined; } + // Split off an optional @digest suffix first (e.g. `@sha256:...`). The digest never + // forms part of the link target, so it is validated then dropped. + const atIndex = image.indexOf('@'); + const digest = atIndex >= 0 ? image.substring(atIndex + 1) : undefined; + const ref = atIndex >= 0 ? image.substring(0, atIndex) : image; + + if (digest !== undefined && !DIGEST_REGEX.test(digest)) { + return undefined; + } + // Split off optional :tag — the tag is the suffix after the last ':' in the // final '/'-separated component (so a ':' inside a registry host:port portion // is not mistaken for a tag separator). - const lastSlash = image.lastIndexOf('/'); - const nameAndTag = lastSlash >= 0 ? image.substring(lastSlash + 1) : image; - const pathPrefix = lastSlash >= 0 ? image.substring(0, lastSlash) : ''; + const lastSlash = ref.lastIndexOf('/'); + const nameAndTag = lastSlash >= 0 ? ref.substring(lastSlash + 1) : ref; + const pathPrefix = lastSlash >= 0 ? ref.substring(0, lastSlash) : ''; const tagColon = nameAndTag.indexOf(':'); const imageName = tagColon >= 0 ? nameAndTag.substring(0, tagColon) : nameAndTag; @@ -61,7 +78,9 @@ export function parseImageRef(image: string): ParsedImageRef | undefined { return undefined; } - const pathLength = image.length - (tag !== undefined ? tag.length + 1 : 0); + // The link covers the registry/namespace/name path only — excluding both the + // :tag and @digest suffixes. + const pathLength = ref.length - (tag !== undefined ? tag.length + 1 : 0); const pathParts = pathPrefix ? pathPrefix.split('/') : []; @@ -99,32 +118,39 @@ export function parseImageRef(image: string): ParsedImageRef | undefined { } function buildLinkUri(ref: ParsedImageRef, imageTypes: Set): string | undefined { - // Docker Hub — no registry hostname in the reference. - if (ref.registry === undefined) { + // Registry hostnames are case-insensitive; normalize before matching known registries. + const registry = ref.registry?.toLowerCase(); + + // Docker Hub — either no registry hostname in the reference, or one of Docker Hub's + // explicit hostnames. Docker Hub only supports a single namespace level, so skip deeper paths. + if (registry === undefined || DOCKER_HUB_REGISTRIES.has(registry)) { if (ref.namespace === undefined) { imageTypes.add('dockerHub'); return `https://hub.docker.com/_/${ref.imageName}`; } + if (ref.namespace.includes('/')) { + return undefined; + } imageTypes.add('dockerHubNamespaced'); return `https://hub.docker.com/r/${ref.namespace}/${ref.imageName}`; } // Microsoft Container Registry — images are mirrored to a Docker Hub page // under the `microsoft--` convention. - if (ref.registry === 'mcr.microsoft.com' && ref.namespace !== undefined) { + if (registry === 'mcr.microsoft.com' && ref.namespace !== undefined) { imageTypes.add('mcr'); return `https://hub.docker.com/_/microsoft-${ref.namespace.replace(/\//g, '-')}-${ref.imageName}`; } // GitHub Container Registry — link to the package page on github.com. // Only the `ghcr.io//` form maps cleanly; deeper paths are skipped. - if (ref.registry === 'ghcr.io' && ref.namespace !== undefined && !ref.namespace.includes('/')) { + if (registry === 'ghcr.io' && ref.namespace !== undefined && !ref.namespace.includes('/')) { imageTypes.add('ghcr'); return `https://github.com/${ref.namespace}/${ref.imageName}/pkgs/container/${ref.imageName}`; } // Quay.io — link to the public repository page. - if (ref.registry === 'quay.io' && ref.namespace !== undefined && !ref.namespace.includes('/')) { + if (registry === 'quay.io' && ref.namespace !== undefined && !ref.namespace.includes('/')) { imageTypes.add('quay'); return `https://quay.io/repository/${ref.namespace}/${ref.imageName}`; } diff --git a/src/test/providers/ImageLinkProvider.test.ts b/src/test/providers/ImageLinkProvider.test.ts index c99a842..3e60cbb 100644 --- a/src/test/providers/ImageLinkProvider.test.ts +++ b/src/test/providers/ImageLinkProvider.test.ts @@ -193,6 +193,112 @@ services: await requestImageLinksAndCompare(testConnection, uri, expected); }); + it('Should provide links for explicit Docker Hub registry hostnames', async () => { + const testObject = { + services: { + a: { + image: 'docker.io/library/alpine' + }, + b: { + image: 'docker.io/alpine' + }, + c: { + image: 'index.docker.io/someuser/someimage:1.0' + }, + } + }; + + const expected = [ + { + range: Range.create(2, 11, 2, 35), + target: 'https://hub.docker.com/r/library/alpine' + }, + { + range: Range.create(4, 11, 4, 27), + target: 'https://hub.docker.com/_/alpine' + }, + { + range: Range.create(6, 11, 6, 45), + target: 'https://hub.docker.com/r/someuser/someimage' + }, + ]; + + const uri = testConnection.sendObjectAsYamlDocument(testObject); + await requestImageLinksAndCompare(testConnection, uri, expected); + }); + + it('Should provide links for digest-pinned images, excluding the digest from the link range', async () => { + const testObject = { + services: { + a: { + image: 'alpine@sha256:1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef' + }, + b: { + image: 'ghcr.io/owner/repo@sha256:abcdefabcdefabcdefabcdefabcdefabcdefabcdefabcdefabcdefabcdefabcd' + }, + c: { + image: 'redis:7@sha256:fedcbafedcbafedcbafedcbafedcbafedcbafedcbafedcbafedcbafedcbafedc' + }, + } + }; + + const expected = [ + { + range: Range.create(2, 11, 2, 17), + target: 'https://hub.docker.com/_/alpine' + }, + { + range: Range.create(4, 11, 4, 29), + target: 'https://github.com/owner/repo/pkgs/container/repo' + }, + { + range: Range.create(6, 11, 6, 16), + target: 'https://hub.docker.com/_/redis' + }, + ]; + + const uri = testConnection.sendObjectAsYamlDocument(testObject); + await requestImageLinksAndCompare(testConnection, uri, expected); + }); + + it('Should provide links for registry hostnames case-insensitively', async () => { + const testObject = { + services: { + a: { + image: 'MCR.microsoft.com/dotnet/sdk' + }, + } + }; + + const expected = [ + { + range: Range.create(2, 11, 2, 39), + target: 'https://hub.docker.com/_/microsoft-dotnet-sdk' + }, + ]; + + const uri = testConnection.sendObjectAsYamlDocument(testObject); + await requestImageLinksAndCompare(testConnection, uri, expected); + }); + + it('Should NOT link an uppercase first component to Docker Hub', async () => { + // An uppercase first component is a registry host per Docker's rules (namespaces are + // always lowercase), so it must not be mistaken for a Docker Hub namespace. + const testObject = { + services: { + a: { + image: 'MyRegistry.example/foo' + }, + b: { + image: 'MyRegistry/foo' + }, + } + }; + + const uri = testConnection.sendObjectAsYamlDocument(testObject); + await requestImageLinksAndCompare(testConnection, uri, []); + }); + it('Should NOT provide links for unrecognized private registries', async () => { // Reproduces the scenario reported in https://github.com/microsoft/compose-language-service/issues/179 const testObject = { @@ -407,7 +513,7 @@ async function requestImageLinksAndCompare(testConnection: TestConnection, uri: if (expected.length > 0) { result.should.not.be.empty; - result.should.deep.include.members(expected); + result.should.have.deep.members(expected); } else { result.should.be.empty; }