diff --git a/extensions/css-language-features/server/src/test/links.test.ts b/extensions/css-language-features/server/src/test/links.test.ts index 02070b1b41d..fe359d62648 100644 --- a/extensions/css-language-features/server/src/test/links.test.ts +++ b/extensions/css-language-features/server/src/test/links.test.ts @@ -54,7 +54,7 @@ suite('Links', () => { } function getTestResource(path: string) { - return Uri.file(resolve(__dirname, '../../test/linkTestFixtures', path)).toString(); + return Uri.file(resolve(__dirname, '../../test/linksTestFixtures', path)).toString(); } test('url links', function () { @@ -66,4 +66,14 @@ suite('Links', () => { [{ offset: 29, value: '"hello.html"', target: getTestResource('hello.html') }], testUri, folders ); }); + + test('url links', function () { + + let testUri = getTestResource('about.css'); + let folders = [{ name: 'x', uri: getTestResource('') }]; + + assertLinks('html { background-image: url("~foo/hello.html|")', + [{ offset: 29, value: '"~foo/hello.html"', target: getTestResource('node_modules/foo/hello.html') }], testUri, folders + ); + }); }); \ No newline at end of file diff --git a/extensions/css-language-features/server/src/utils/documentContext.ts b/extensions/css-language-features/server/src/utils/documentContext.ts index 7b253add20f..41c29989f02 100644 --- a/extensions/css-language-features/server/src/utils/documentContext.ts +++ b/extensions/css-language-features/server/src/utils/documentContext.ts @@ -7,6 +7,9 @@ import { DocumentContext } from 'vscode-css-languageservice'; import { endsWith, startsWith } from '../utils/strings'; import * as url from 'url'; import { WorkspaceFolder } from 'vscode-languageserver'; +import URI from 'vscode-uri'; +import { join, dirname } from 'path'; +import { existsSync } from 'fs'; function getModuleNameFromPath(path: string) { // If a scoped module (starts with @) then get up until second instance of '/', otherwise get until first isntance of '/' @@ -16,19 +19,14 @@ function getModuleNameFromPath(path: string) { return path.substring(0, path.indexOf('/')); } -function resolvePathToModule(_moduleName: string, _relativeTo: string) { - // if we require.resolve('my-module') then it will follow the main property in the linked package.json - // but we want the root of the module so resolve to the package.json and then trim - let resolved; - try { - // resolved = require - // .resolve(`${moduleName}/package.json`, { paths: [relativeTo] }); - throw new Error(); +function resolvePathToModule(_moduleName: string, _relativeTo: string): string | undefined { + // resolve the module relative to the document. We can't use `require` here as the code is webpacked. + const documentFolder = dirname(URI.parse(_relativeTo).fsPath); + const packPath = join(documentFolder, 'node_modules', _moduleName, 'package.json'); + if (existsSync(packPath)) { + return URI.file(packPath).toString(); } - catch (ex) { - return null; - } - return resolved.slice(0, -12); // remove trailing `package.json` + return undefined; } export function getDocumentContext(documentUri: string, workspaceFolders: WorkspaceFolder[]): DocumentContext { @@ -59,7 +57,7 @@ export function getDocumentContext(documentUri: string, workspaceFolders: Worksp // and [sass-loader's](https://github.com/webpack-contrib/sass-loader#imports) // convention, if an import path starts with ~ then use node module resolution // *unless* it starts with "~/" as this refers to the user's home directory. - if (ref[0] === '~' && ref[1] !== '/') { + if (ref[0] === '~' && ref[1] !== '/' && startsWith(base, 'file://')) { const moduleName = getModuleNameFromPath(ref.substring(1)); const modulePath = resolvePathToModule(moduleName, base); if (modulePath) {