From f0b9a60a8f0d65a145cc0064c975b37534a91212 Mon Sep 17 00:00:00 2001 From: Alasdair McLeay Date: Mon, 18 Mar 2019 11:47:17 +0000 Subject: [PATCH 1/4] Node module resolution for CSS import Add support for `@import "~bootstrap/dist/css/bootstrap";` as per https://github.com/webpack-contrib/sass-loader#imports Fixes Microsoft/vscode-css-languageservice#136 --- .../server/src/utils/documentContext.ts | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/extensions/css-language-features/server/src/utils/documentContext.ts b/extensions/css-language-features/server/src/utils/documentContext.ts index 9858ca84367..810b7070028 100644 --- a/extensions/css-language-features/server/src/utils/documentContext.ts +++ b/extensions/css-language-features/server/src/utils/documentContext.ts @@ -8,6 +8,14 @@ import { endsWith, startsWith } from '../utils/strings'; import * as url from 'url'; import { WorkspaceFolder } from 'vscode-languageserver'; +function getModuleNameFromPath(path: string) { + // If a scoped module (starts with @) then get up until second instance of '/', otherwise get until first isntance of '/' + if (path[0] === '@') { + return path.substring(0, path.indexOf('/', path.indexOf('/') + 1)); + } + return path.substring(0, path.indexOf('/')); +} + export function getDocumentContext(documentUri: string, workspaceFolders: WorkspaceFolder[]): DocumentContext { function getRootFolder(): string | undefined { for (let folder of workspaceFolders) { @@ -32,6 +40,12 @@ export function getDocumentContext(documentUri: string, workspaceFolders: Worksp } } } + if (ref[0] === '~' && ref[1] !== '/') { + const moduleName = getModuleNameFromPath(ref.substring(1)); + const modulePath = require.resolve(moduleName, { paths: [base] }); + const pathInModule = ref.substring(moduleName.length + 2); + return url.resolve(modulePath, pathInModule); + } return url.resolve(base, ref); }, }; From 30b54690cfde74474ee8307d79bf65e870757b7d Mon Sep 17 00:00:00 2001 From: Alasdair McLeay Date: Mon, 18 Mar 2019 12:09:30 +0000 Subject: [PATCH 2/4] add some documentation --- .../server/src/utils/documentContext.ts | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/extensions/css-language-features/server/src/utils/documentContext.ts b/extensions/css-language-features/server/src/utils/documentContext.ts index 810b7070028..1de06b726ec 100644 --- a/extensions/css-language-features/server/src/utils/documentContext.ts +++ b/extensions/css-language-features/server/src/utils/documentContext.ts @@ -40,11 +40,15 @@ export function getDocumentContext(documentUri: string, workspaceFolders: Worksp } } } + // Following [css-loader](https://github.com/webpack-contrib/css-loader#url) + // 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] !== '/') { const moduleName = getModuleNameFromPath(ref.substring(1)); const modulePath = require.resolve(moduleName, { paths: [base] }); - const pathInModule = ref.substring(moduleName.length + 2); - return url.resolve(modulePath, pathInModule); + const pathWithinModule = ref.substring(moduleName.length + 2); + return url.resolve(modulePath, pathWithinModule); } return url.resolve(base, ref); }, From b1708589e1321e1968beb23d173cf2dee7c20dbf Mon Sep 17 00:00:00 2001 From: Alasdair McLeay Date: Mon, 18 Mar 2019 15:32:38 +0000 Subject: [PATCH 3/4] resolve to package root extract resolvePathToModule and ensure we resolve to package root --- .../server/src/utils/documentContext.ts | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/extensions/css-language-features/server/src/utils/documentContext.ts b/extensions/css-language-features/server/src/utils/documentContext.ts index 1de06b726ec..f9f0fc3bca7 100644 --- a/extensions/css-language-features/server/src/utils/documentContext.ts +++ b/extensions/css-language-features/server/src/utils/documentContext.ts @@ -16,6 +16,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 + return require + .resolve(`${moduleName}/package.json`, { paths: [relativeTo] }) + .slice(0, -12); // remove trailing `package.json` +} + export function getDocumentContext(documentUri: string, workspaceFolders: WorkspaceFolder[]): DocumentContext { function getRootFolder(): string | undefined { for (let folder of workspaceFolders) { @@ -46,7 +54,7 @@ export function getDocumentContext(documentUri: string, workspaceFolders: Worksp // *unless* it starts with "~/" as this refers to the user's home directory. if (ref[0] === '~' && ref[1] !== '/') { const moduleName = getModuleNameFromPath(ref.substring(1)); - const modulePath = require.resolve(moduleName, { paths: [base] }); + const modulePath = resolvePathToModule(moduleName, base); const pathWithinModule = ref.substring(moduleName.length + 2); return url.resolve(modulePath, pathWithinModule); } From 2e9bd074bb050545455422bc5286e557de036f96 Mon Sep 17 00:00:00 2001 From: Alasdair McLeay Date: Mon, 18 Mar 2019 15:51:37 +0000 Subject: [PATCH 4/4] handle require.resolve failure if require.resolve fails, defer to default behaviour -url.resolve(base, ref) --- .../server/src/utils/documentContext.ts | 18 +++++++++++++----- 1 file changed, 13 insertions(+), 5 deletions(-) diff --git a/extensions/css-language-features/server/src/utils/documentContext.ts b/extensions/css-language-features/server/src/utils/documentContext.ts index f9f0fc3bca7..ef2cddc4255 100644 --- a/extensions/css-language-features/server/src/utils/documentContext.ts +++ b/extensions/css-language-features/server/src/utils/documentContext.ts @@ -19,9 +19,15 @@ function getModuleNameFromPath(path: string) { 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 - return require - .resolve(`${moduleName}/package.json`, { paths: [relativeTo] }) - .slice(0, -12); // remove trailing `package.json` + let resolved; + try { + resolved = require + .resolve(`${moduleName}/package.json`, { paths: [relativeTo] }); + } + catch (ex) { + return null; + } + return resolved.slice(0, -12); // remove trailing `package.json` } export function getDocumentContext(documentUri: string, workspaceFolders: WorkspaceFolder[]): DocumentContext { @@ -55,8 +61,10 @@ export function getDocumentContext(documentUri: string, workspaceFolders: Worksp if (ref[0] === '~' && ref[1] !== '/') { const moduleName = getModuleNameFromPath(ref.substring(1)); const modulePath = resolvePathToModule(moduleName, base); - const pathWithinModule = ref.substring(moduleName.length + 2); - return url.resolve(modulePath, pathWithinModule); + if (modulePath) { + const pathWithinModule = ref.substring(moduleName.length + 2); + return url.resolve(modulePath, pathWithinModule); + } } return url.resolve(base, ref); },